Q:Lua调用C函数的两种方式?
A:
1、程序主体在C中运行,C函数注册到Lua中。C调用Lua,Lua调用C注册的函数,C得到函数的执行结果。
2、程序主体在Lua中运行,C函数作为库函数供Lua使用。
第一种方式看起来很罗嗦,也很奇怪。既然程序主体运行在C中,而且最终使用的也是C中定义的函数,那么为何要将函数注册给Lua,然后再通过Lua调用函数呢?
相比于第一种方式,第二种方式使用的更加普遍。
一个Lua库(Lua本身所提供的库)实际上是一个定义了若干Lua函数的”chunk”,这些函数通常作为”table”的域来保存。一个C库(C语言编写,注册给Lua使用的库)的实现方式类似于Lua库的实现方式。首先C库中定义提供给Lua使用的函数,其次还需要一个“特殊函数”,它的作用是注册所有C库中的函数,并将它们存储在适当的位置(类似于Lua库中的函数作为”table”的域来保存)。
Lua可以调用C库中的函数,就是通过这个注册的过程实现的。一旦C函数注册到Lua中,Lua就可以直接通过C函数的引用获取到C函数的地址(这也是我们注册的意义,将C函数的地址提供给Lua)。换句话说,一旦C函数注册,Lua调用他们不依赖于函数名,”package”位置,或者是可见规则。
以上两种方式下面都会列举对应的例子,理解第一种方式,将有助于你理解第二种方式的实现流程。
Q:从Lua中调用C所遵循的规则?
A:当C调用Lua函数的时候,必须遵循一些简单的协议来传递参数和获取返回结果。同样的,从Lua中调用C函数,也必须遵循一些协议来传递参数和获得返回结果。此外,从Lua调用C函数我们必须注册函数,也就是说,我们必须把C函数的地址以一个适当的方式传递给Lua解释器。
任何在Lua中注册的C函数必须有同样的原型,
typedef int (*lua_CFunction) (lua_State *L); // 定义在"lua.h"中。
被注册的C函数接收一个单一的lua_State
类型的参数,同时返回一个表示返回值个数的数字。函数在将返回值入栈之前无需清理栈,在函数返回之后,Lua会自动清除栈中返回结果下面的所有内容。
Q:如何在C中调用注册给Lua的C函数?
A:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
static int l_sin(lua_State *L)
{
// 如果给定虚拟栈中索引处的元素可以转换为数字,则返回转换后的数字,否则报错。
double d = luaL_checknumber(L, 1);
lua_pushnumber(L, sin(d)); /* push result */
/* 这里可以看出,C可以返回给Lua多个结果,
* 通过多次调用lua_push*(),之后return返回结果的数量。
*/
return 1; /* number of results */
}
int main(void)
{
lua_State *L = luaL_newstate(); // 创建Lua状态机。
luaL_openlibs(L); // 打开Lua状态机"L"中的所有Lua标准库。
/* 这两句话还有更简单的方法:
* lua_register(L, "mysin", l_sin)
* 将C函数"l_sin"定义为Lua的全局变量"mysin"。
* 其实现是如下宏:
* #define lua_register(L,n,f) \
* (lua_pushcfunction(L, f), lua_setglobal(L, n))
*/
lua_pushcfunction(L, l_sin); // 将C函数转换为Lua的"function"并压入虚拟栈。
lua_setglobal(L, "mysin"); // 弹出栈顶元素,并在Lua中用名为"mysin"的全局变量存储。
const char* testfunc = "print(mysin(3.14 / 2))";
if(luaL_dostring(L, testfunc)) // 执行Lua命令。
printf("Failed to invoke.\n");
lua_close(L); // 关闭Lua状态机。
return 0;
}
prompt> gcc main.c -llua -ldl -lm -Wall
prompt> ./a.out
0.99999968293183
另一个例子(假定我们的系统符合”POSIX”标准)的功能类似于ls
,将指定目录中的所有文件以数组的形式返回。当有错误发生时,返回nil
加上一个描述错误信息的字符串。
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <dirent.h>
#include <errno.h>
static int l_dir(lua_State *L)
{
DIR *dir;
struct dirent *entry;
int i = 0;
// 如果给定虚拟栈中索引处的元素可以转换为字符串,则返回转换后的字符串,否则报错。
const char *path = luaL_checkstring(L, 1);
/* open directory */
dir = opendir(path);
if(dir == NULL) {
// 出错返回"nil"加上一个描述错误信息的字符串。
lua_pushnil(L);
lua_pushstring(L, strerror(errno));
return 2; // "nil"加上字符串,共两个返回值。
}
/* create result table */
lua_newtable(L);
i = 1;
while((entry = readdir(dir)) != NULL) // 逐一读取目录中的文件。
{
lua_pushnumber(L, i++); /* push key */
lua_pushstring(L, entry->d_name); /* push value */
lua_settable(L, -3); // t[k] = v
}
closedir(dir);
return 1; // 返回值只有一个,"table"。
}
int main(void)
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
// 将C函数"l_dir"定义为Lua的全局变量"mydir"。
lua_register(L, "mydir", l_dir);
// 打印"/home/"目录下的所有文件。
const char* testfunc = "for i, v in pairs(mydir('/home')) do print(i, v) end";
if(luaL_dostring(L, testfunc)) // 执行Lua命令。
printf("Failed to invoke.\n");
lua_close(L);
return 0;
}
prompt> gcc main.c -llua -ldl -lm -Wall
prompt> ./a.out
1 vermilliontear
2 git
3 .
4 ..
5 lost+found
Q:如何在Lua中调用作为库函数提供给Lua的C函数?
A:我们使用上面的第一个例子,将其改造为C库的方式。
“mylib.c”文件中:
#include <stdio.h>
#include <math.h>
#include <stdarg.h>
#include <stdlib.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
/* 所有注册给Lua的C函数具有
* "typedef int (*lua_CFunction) (lua_State *L);"的原型。
*/
static int l_sin(lua_State *L)
{
// 如果给定虚拟栈中索引处的元素可以转换为数字,则返回转换后的数字,否则报错。
double d = luaL_checknumber(L, 1);
lua_pushnumber(L, sin(d)); /* push result */
/* 这里可以看出,C可以返回给Lua多个结果,
* 通过多次调用lua_push*(),之后return返回结果的数量。
*/
return 1; /* number of results */
}
/* 需要一个"luaL_Reg"类型的结构体,其中每一个元素对应一个提供给Lua的函数。
* 每一个元素中包含此函数在Lua中的名字,以及该函数在C库中的函数指针。
* 最后一个元素为“哨兵元素”(两个"NULL"),用于告诉Lua没有其他的函数需要注册。
*/
static const struct luaL_Reg mylib[] = {
{"mysin", l_sin},
{NULL, NULL}
};
/* 此函数为C库中的“特殊函数”。
* 通过调用它注册所有C库中的函数,并将它们存储在适当的位置。
* 此函数的命名规则应遵循:
* 1、使用"luaopen_"作为前缀。
* 2、前缀之后的名字将作为"require"的参数。
*/
extern int luaopen_mylib(lua_State* L)
{
/* void luaL_newlib (lua_State *L, const luaL_Reg l[]);
* 创建一个新的"table",并将"l"中所列出的函数注册为"table"的域。
*/
luaL_newlib(L, mylib);
return 1;
}
将”mylib.c”编译为动态连接库,
prompt> gcc mylib.c -fPIC -shared -o mylib.so -Wall
prompt> ls
mylib.c mylib.so a.lua
“a.lua”文件中:
--[[ 这里"require"的参数对应C库中"luaopen_mylib()"中的"mylib"。
C库就放在"a.lua"的同级目录,"require"可以找到。]]
local mylib = require "mylib"
-- 结果与上面的例子中相同,但是这里是通过调用C库中的函数实现。
print(mylib.mysin(3.14 / 2)) --> 0.99999968293183
附加:
1、每一个与Lua通信的C函数都有其独有的虚拟栈。
2、在极端情况下,打印指定目录中文件的例子可能会造成小小的内存泄漏。在内存空间不足的情况下,l_dir()
中的lua_newtable()
、lua_pushstring()
和lua_settable()
都会立即抛出错误并终止程序的运行,这将导致closdir()
无法被调用。
3、通常C库中“特殊的函数”都被定义为公有的(使用extern
修饰),而其他函数均被定义为私有的(使用static
修饰)。
4、当你想要使用C函数扩展你的Lua程序时,即使只有一个C函数,也最好使用C库的方式。因为在不久的将来(通常来说会很快),你将需要其他的C函数。