几种Lua和C交叉编程的程序写法

ronald2年前职场5510

Lua程序调用C接口

//另一个待Lua调用的C注册函数。
static int sub2(lua_State* L)
{
    double op1 = luaL_checknumber(L,1);
    double op2 = luaL_checknumber(L,2);
    lua_pushnumber(L,op1 - op2);
    return 1;
}

const char* testfunc = "print(sub2(20.1,19))";

int main() {
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    lua_register(L, "sub2", sub2);
    if (luaL_dostring(L,testfunc))
        printf("Failed to invoke.\n");
    lua_close(L);
    return 0;
}

    基本场景,在Lua中调用C接口,最关键就是调用接口lua_register将C接口注册到注册表上并关联C函数指针,在Lua中正常用就可以了。而在C的接口实现中需要从栈中取数据,并进行对应的运算。

C程序调用Lua接口

function test()
        print("lua 666")
end

int main(){
    lua_State* l = luaL_newstate();
    luaL_openlibs(l);
    luaL_dofile(l,"/home/ronaldwang/lua/demo/test2.lua");
    lua_pcall(l,0,0,0);
    lua_getglobal(l,"test");
    lua_pcall(l,0,0,0);
    lua_close(l);
    return 0;
}

    C调用Lua接口核心就在于将lua文件加载进来,并调用lua_pcall进行运行,这样就可以将lua文件里面定义的方法在全局表里面建立映射关系,将接口全部load到表之后,先通过lua_getglobal把接口放到栈顶,然后push相关参数,最后就是lua_pcall进行调用。

    介绍了两类比较简单的Lua和C的互调方式之后,我们考虑两种比较复杂的调用场景:

Lua中使用具有C回调的C接口(1)

    设想场景:底层框架提供了一个带C回调的C接口,但是上层业务都是Lua写的,我们可以实现在Lua的上下文中实现接口的调用和回调的定义。

如下:

typedef (int*) CallbackFunc(int)
int PrintHello(int data, CallbackFunc cb){
    printf("%d:%d\n",data,cb(data))
    return 0;    
}

    设想,底层框架提供接口PrintHello,并且此PrintHello接口需要接收一个函数指针作为参数;在实际场景中,可能我们的外层业务都是用Lua编写,我们并不希望因为这个回调就去侵入到底层框架,使用C去写这样一个接口,更多的,希望像调用Lua接口一样调用这个C接口,如下:

PrintHello(100,function(a) return a+10 end)
PrintHello(120,function(b) return b*10 end)

    在此例中,我们可以很灵活的变换回调接口的实现,在用的地方定义并使用,不用在C中单独去定义一个接口。

    针对这种情况,具体实现见如下代码:

/***************test.lua**********/
PrintHello(100,function(a) return a+10 end)
PrintHello(120,function(b) return b*10 end)
    
/**************C代码部分***********/
typedef (int* Callback)(int);    
int PrintHello(int data,Callback cb){
    printf("%d:%d\n",data,cb(data));
    return 0;
}    

int test_cb(int data){
    lua_rawgetp(L, LUA_REGISTRYINDEX, (void*)test_cb);
    lua_pushinteger(l,data);
    lua_pcall(l,1,1,0);
    int ret = lua_checknumber(l,-1);
    lua_pop(l);
    return ret;
}

int test_func(lua_State* l){
    int first = lua_checkinteger(l,1);
    lua_pushvalue(l,2);
    lua_rawsetp(L, LUA_REGISTRYINDEX, (void *)test_cb);
    PrintHello(first,test_cb);
    return 0;
}
int main(){
    lua_State* L = newluastate();
    lua_openlibs(L);
    lua_register(L,"PrintHello",test_func);
    lua_dostring(L,"test.lua");
    lua_close(L);
    return 0;
}

    在上述代码中,虽然回调PrintHello的回调是C格式的,但是我们可以在Lua中自由定义回调的格式,并能被C接口“接收”并正确执行,这里我们分析一下代码的执行流程:

    1)在main函数中,我们通过调用接口lua_register注册了接口PrintHello,这样在Lua的全局表上建立了一个PrintHello到函数test_func地址的映射关系;

    2)因为全局表可以被lua层访问,当调用lua_dostring(L,test.lua)的时候,执行PrintHello(10,function(a) return a+10 end)的时候,栈里面有两个元素,一个是10,一个是定义的function(a)这个函数,并且走到test_func这个接口里面;

    3)test_func接口依次取出两个栈元素,由于第二个是函数C无法接收这样一个元素,因此将这个对象通过接口lua_rawsetp插入到注册表中(也可以通过lua_setglobal插入到全局表,但是由于全局表可以被lua层看到有被Lua层修改的风险,所以放注册表安全一点);

    4)lua_rawsetp/lua_rawgetp这些接口的主要作用就是把C接口地址放到注册表中方便后续取到,本例中代码逻辑比较简单,其实可以不用。

    通过这样一个接口设计,我们针对提供的C库接口可以很方便的进行回调接口的扩展和定制化。

Lua中使用C回调的C接口(2)

    设想另外一种场景,我们的PrintHello需要接收一个回调,但是这个回调已经在C中定义并实现了,我们不希望代码重复,如何能在Lua中传递C的回调呢?

/******************test.lua**********************/
PrintHello(100,test_func_c)
    
/******************main.cpp**********************/
lua_State* l;
typedef int (*Callback)(int);


void PrintHello(int data,Callback cb){
     printf("%d:%d\n",data,cb(data));
}

void TestHello(int data,Callback cb){
     printf("test:%d:%d\n",data,cb(10));
}

int test_func_c(int data){
    printf("data:%d\n",data);
    return data;
}

int test_func(lua_State* l) {
    int first = luaL_checkinteger(l, 1);
    Callback cb = (Callback) lua_touserdata(l,2);
    PrintHello(first,cb);
    return 0;
}

int main(){
    l = luaL_newstate();
    luaL_openlibs(l);
    lua_register(l,"PrintHello",test_func);
    lua_pushlightuserdata(l,(void*) test_func_c);
    lua_setglobal(l,"test_func_c");
    luaL_dofile(l,"/home/ronaldwang/lua/demo/test.lua");
    lua_close(l);
    return 0;
}

    在本例中,我们PrintHello需要接收形参格式为:一个int类型变量和一个Callback类型的函数指针,我们在Lua中需要用到这个接口,同时需要用C中定义的回调test_func_c,和前面场景不同,此时回调是C中已经有的,我们是如下实现的:

    1)通过接口lua_pushlightuserdata和lua_setglobal,将C函数指针test_func_c存入到全局表;

    2)lua的执行指令PrintHello(100,test_func_c),涉及两个符号,一个是PrintHello,一个是test_func_c,这时候实际已经调用到test_func接口里面,并且栈的索引1位置为100,索引2的位置是函数指针(userdata类型);

    3)通过接口lua_touserdata,将指定的函数指针给取出来并强转为Callback类型,并在里面调用接口PrintHello达到函数调用的效果。

    通过这种嫁接形式,我们可以在Lua中充分利用库提供的C接口。

相关文章

Lua和C

Lua和C

一.背景    在实际游戏业务运营过程中,经常会出现一些紧急的配置修改、bug修复等;这种规划外的变更行为希望能做到用户无感知,否则对于游戏体验和产品口碑有很大影响,在此背景下,热更新能力成为成熟上线游戏的标准配置。    一般情况下,后台逻辑热更新能力的技术方案有两种:    ...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。