几种Lua和C交叉编程的程序写法
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接口。