lua 与 C 交互

lua和C交互的核心就是lua栈,lua和C的所有数据交互都是通过lua栈来完成的。

一. C调用lua

C调用lua很简单,通常C以lua作为配置脚本,在运行时读取脚本数据,主要步骤:

  1. 加载脚本 luaL_loadfile
  2. 运行脚本 lua_pcall
  3. 获取数据 lua_getglobal ….
  4. 使用数据 lua_tostring lua_pcall …

二. 在lua脚本中调用C:

在C程序中,使用lua作为脚本,但是要在运行脚本时,访问C中定义的一些变量或函数。

  1. 将C变量或函数(遵从指定函数原型,见下面三 Step 1)push到lua栈中
  2. 通过lua_setglobal为当前lua栈顶的函数或变量命名,这样在lua中可通过该名字完成对变量或函数的使用
  3. 之后可在加载的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_openlibreturn 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函数,然后调用该函数。因此我们需要注意两点:

  1. 设置好库文件路径 确保库文件存在
  2. 确保库定义了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)。