lua 与 C 交互
lua和C交互的核心就是lua栈,lua和C的所有数据交互都是通过lua栈来完成的。
一. C调用lua
C调用lua很简单,通常C以lua作为配置脚本,在运行时读取脚本数据,主要步骤:
- 加载脚本 luaL_loadfile
- 运行脚本 lua_pcall
- 获取数据 lua_getglobal ….
- 使用数据 lua_tostring lua_pcall …
二. 在lua脚本中调用C:
在C程序中,使用lua作为脚本,但是要在运行脚本时,访问C中定义的一些变量或函数。
- 将C变量或函数(遵从指定函数原型,见下面三 Step 1)push到lua栈中
- 通过lua_setglobal为当前lua栈顶的函数或变量命名,这样在lua中可通过该名字完成对变量或函数的使用
- 之后可在加载的lua脚本中使用C变量或函数
三. 将C函数封装为一个库,为lua所用
将C函数编译为动态库文件,这样可以在lua主程序中,加载这个库文件,并使用其中的C函数。
Step 1. 在mylib.c中定义给lua调用的C函数 函数原型为: int (lua_State*)
如:
static int c_addsub(lua_State* L)
{
double a = luaL_checknumber(L,1); // 获取参数1
double b = luaL_checknumber(L,2); // 获取参数2
lua_pushnumber(L, a+b); // 压入返回值1
lua_pushnumber(L, a-b); // 压入返回值2
return 2; // 两个返回值
}
Step 2. 在mylib.c中定义一个注册函数,用于lua在加载C动态库时,调用该函数完成对库中所导出的C函数的注册。
如:
// 将C模块中要导出的C函数放入到luaL_Reg结构体数组内
static const struct luaL_Reg l[] = {
{"addsub", c_addsub},
{NULL, NULL} // 以NULL标识结束
};
// 该函数在导入C库时调用 完成对库中导出的函数的注册
// 必须是non-static
int luaopen_mylib(lua_State* L)
{
// 完成实际的注册工作
// 注册方式一: luaL_openlib(lua_State* L, const char* name, const luaL_Reg* l, int nup)
// L : lua_State
// name: 表明该C库被加载后,所导出的函数位于哪一个全局table之下
// 这里是"clib" 那么之后lua中通过clib.addsub完成对C函数的调用
// l : 要导出的函数的lua_Reg结构体数组
// luaL_openlib自动将该数组内的name-function对注册并填充到第二参数指定的table下
// nup : upvalue的个数,如果不为0,则注册的所有函数都共享这些upvalues
luaL_openlib(L, "clib", l, 0);
// 注册方式二: luaL_newlibtable + luaL_setfuncs (等价于lua_newlib)
// luaL_newlibtable(L, l);
// luaL_setfuncs(L, l, 0);
// 前两句等价于:
// luaL_newlib(L, l);
// 将包含name-cfunction键值对的table返回给lua
return 1;
}
注意上面方式一和方式二的主要区别:前者(luaL_openlib)为name-cfunction对在lua中注册了一个名字(“clib”)。而后者(luaL_newlib)没有,它只是将这个table返回给了lua。可在lua层通过赋值为其命名。自然,通过 luaL_openlib
和 return 1
可以将name-cfuncton对注册到两个lua table下。
关于luaL_openlib函数,在官方文档中没有找到它,lua5.2文档中给出的是luaL_newlibtable和lua_setfuncs等新API用以替代以前的luaL_register,而事实上根据前面lua和C交互的基本元素,我们可以自己实现一个类似lua_openlib的注册函数:
int luaopen_mylib(lua_State* L)
{
// luaL_openlib(L, "clib", clib, 0);
int i = 0;
lua_newtable(L); // push a new table
while(clib[i].name != NULL)
{
lua_pushstring(L, clib[i].name); // push name
lua_pushcfunction(L, clib[i].func); // push function
lua_settable(L, -3); // table[name] = function
++i;
}
lua_setglobal(L, "clib"); // set table name
return 1;
}
因此实际上将C作为动态库和前面二中的交互核心是一样的,只是将C作为动态库时,需要提供一个”入口函数”,用以在加载该动态库后执行,完成对库中所有导出函数的注册。
Step 3. 将相关C文件编译成动态链接库:
需要说明的是Mac OS X需要使用gcc将mylualib.c编译为动态库,编译选项不同于Linux。
具体编译命令(粗体部分不同于Linux,如果不使用这些选项,liblua将会被编译到so文件中并引起“multiple lua vms detected”错误, bundle是Mac使用的文件格式):
gcc -c mylib.c
gcc -O2 -bundle -undefined dynamic_lookup -o mylib.so mylib.o
Step 4. 在lua中加载C动态库
方式一 : 使用 loadlib
--加载C动态库 并将luaopen_mylib函数 导出到mylib变量中
mylib = loadlib("./mylib.so", "luaopen_mylib")
–调用mylib() 将执行lua_openmylib函数 完成对C动态库中所有导出函数的注册
–将C中返回的name-cfunction table赋给clualib变量
clualib = mylib()
–通过clualib完成C函数的调用
sum, diff = clualib.addsub(5.6, 2.4);
–针对于Step 2中的注册方式一,还可以通过luaL_openlib中传入的clib来使用C函数
sum, diff = clib.addsub(5.6, 2.4)
loadlib会读取动态库文件的符号表,得到luaopen_mylib函数的实现,并导出到mylib变量中,通过执行mylib(),即可执行luaopen_mylib完成对整个C库导出函数的注册。luaopen_mylib将注册完成后的name-cfunction对返回给lua,lua可以通过clualib = mylib()
为这个注册完成之后的table命名。之后可通过clualib调用C函数。
另外,luaL_openlib函数可以直接导出name-cfunction对并为其在lua中注册一个名字,因此通过clib也可以完成对C函数的调用。
方式二 : 使用 require
clualib = require("mylib")
sum,diff = clualib.addsub(5.6, 2.4)
– 对于luaL_openlib完成的注册,仍然可以通过clib来访问C函数
sum, diff = clib.addsub(5.6, 2.4)
require的工作原理:
当你在脚本中使用require加载一个模块xxx的时候,首先它会在Lua的环境路径中寻找以xxx命名的DLL,如果找到了,则会在这个DLL中寻找luaopen_xxx的函数用于加载模块。我们只需要将自己需要导出给Lua调用的C内容通过这个函数导出就可以了。
比如我们通过require(“mylib”)来导入模块,lua找到mylib.so库文件,并查找luaopen_mylib函数,然后调用该函数。因此我们需要注意两点:
- 设置好库文件路径 确保库文件存在
- 确保库定义了luaopen_mylib函数(而不像前一个方法一样,可以通过loadlib函数手动指定入口函数)
require的优势在于自动化,而loadlib方式则更加灵活,loadlib可以指定注册函数名字,注册函数可以无需按照luaopen_xxx格式命名。
在一些库中,使用require(“mylib.core”)之类的格式来导入C模块,没有任何库文件时,通过require的报错可以看到其查找路径和规则:
lua: testmylib.lua:1: module 'mylib.core' not found:
no field package.preload['mylib.core']
no file '/usr/local/share/lua/5.2/mylib/core.lua'
no file '/usr/local/share/lua/5.2/mylib/core/init.lua'
no file '/usr/local/lib/lua/5.2/mylib/core.lua'
no file '/usr/local/lib/lua/5.2/mylib/core/init.lua'
no file './mylib/core.lua'
no file '/usr/local/lib/lua/5.2/mylib/core.so'
no file '/usr/local/lib/lua/5.2/loadall.so'
no file './mylib/core.so'
no file '/usr/local/lib/lua/5.2/mylib.so'
no file '/usr/local/lib/lua/5.2/loadall.so'
no file './mylib.so'
先查找 PATH/mylib/core.so 如果没有,则直接使用 PATH/mylib.so。而C中的导出函数命名则必须为: luaopen_mylib_core(lua_State* L)。