第二十六课 从Lua调用C

来源:互联网 发布:c语言培训 编辑:程序博客网 时间:2024/05/18 19:46
扩展Lua的一项基本含义就是,应用程序将新的C函数注册到Lua中。
Lua能调用C函数,但并不意味着Lua可以调用任意C函数。在上一章中,当C语言调用Lua函数时,它必须 遵循一个简单的协议,以此来向Lua传递参数,并从Lua获取结果。同样,对于一个能被Lua调用的C函数,它也必须遵循一个获取参数和返回结果的协议。此外,还必须注册C函数,以便用某种适当的方式将函数地址告诉Lua。
当Lua调用C函数时,也使用了一个与C语言调用Lua时相同的栈。C函数从栈中获取函数参数,并将结果压入栈。为了在栈中将函数结果与其他值区分开,C函数还应返回其压入栈中的结果数量。
栈不是一个全局性的结构,这是一个重要概念。每个函数都有自己的局部私有栈。当Lua调用一个C函数时,第一个参数总是这个局部栈的索引1。即使这个C函数调用了Lua代码,并且Lua代码有调用了相同的C函数,这些C函数调用只看到自己的私有栈,它们的第一个参数都是索引1。

C函数
第一个示例实现了一个简化版的 正弦函数:
static int l_sin (lua_State *L)
{
double d =lua_tonumber(L, 1); //获取参数
lua_pushnumber(L, sin(d)); //压入结果
return 1;
}
所有注册到Lua中的函数都具有相同的原型,该原型就是定义在lua.h中的 lua_CFunction:
typedef int (*lua_CFunction) (lua_State *L);
从C语言的观点来看,这个C 函数仅有一个参数,即Lua的状态。它返回一个整数,表示其压入栈中的返回值数量。因此,这个函数无须 在压入结果前清空栈。在它返回后,Lua会自动删除栈中结果之下的内容。
在Lua使用这函数之前,必须注册这个函数。可以用lua_pushcfunction来进行注册,这个函数要求传入一个指向C函数的指针,它会在Lua中创建一个“函数”类型的值,该值就表示这个C函数。当注册完后,这个C函数就具有了与其他Lua函数一样的行为。
为了方便测试l_sin,可以将l_sin的代码直接放入文件lua.c中,并将下列内容添加到 luaL_openlibs调用后面:
lua_pushcfunction(L, l_sin);
lua_setglobal(L, "mysin");
第一行压入一个函数类型的值,第二行将这个值赋予全局变量mysin。修改完后,需要重新编译Lua的执行程序。然后便可以在Lua程序中使用这个新函数mysin了。
一个更专业的正弦函数还应该检查参数的类型。辅助库中的luaL_checknumber可以检查某个参数是否为一个数字。如果不是,它会抛出一个错误消息;反之,就返回这个数字。对上面这个正弦函数所做的修改很小,如下 所示:
static int l_sin (lua_State *L)
{
double d = luaL_checknumber(L, 1);
lua_pushnumber(L, sin(d));
return 1;
}
修改后,若调用mysin('a'),就会得到消息:
bad argument #1 to 'mysin' (number excepted, got string)
luaL_checknumber会自动填充消息中的参数序号、函数名、期望的参数类型和实际的参数类型。
下面是一个更复杂的示例,这个函数可以返回指定目录下所有的子目录。Lua在其标准库中没有提供这样的函数,这是因为ANSI C中没有目录访问方面的函数。但可以假设拥有一套POSIX兼容的系统。这个函数从其字符串参数中获取目录路径,并返回一个数组,记录所有的子目录。例如,调用dir("/home/lua")会返回table{"." , "..", "src","bin","lib"}。若发生错误,函数返回nil以及包含错误消息的字符串。以下是该函数的完整代码:
#include <dirent.h>
#include <errno.h>
static int l_dir (lua_Static *L)
{
DIR *dir;
struct dirent *entry;
int i;
const char *path = luaL_checkstring(L, 1);
//打开目录
dir = opendir(path);
if (NULL == dir)
{
lua_pushnil(L);
lua_pushstring(L, strerror(errno));
return 2;
}
//创建结果table
lua_newtable(L);
i = 1;
while (NULL != (entry = readdir(dir)))
{
lua_pushnumber(L, i++); //压入key
lua_pushstring(L, entry->d_name); //压入value
lua_settable(L, -3);
}
closedir(dir);
return 1; //table已位于栈顶
}
注意,其中用到的luaL_checkstring类似于luaL_checknumber,也来自辅助库,可用于检查字符串。
在特殊情况中,以上这个l_dir实现可能会造成一些较小的内存泄露。它所调用的3个Lua函数 lua_newtable、 lua_pushstring和lua_settable会由于内存不足而失败。如果这些函数失败 了,它们就会引发raise一个错误,并中断l_dir的执行。因此也不会调用closedir了。就像之前所说的,许多程序可以解决这个问题。如果发生内存不足,一个程序所能做的最佳处理就是结束运行。

C模块
Lua模块是一个程序块(chunk),其中定义了一些Lua函数,这些函数通常存储为table的条目。一个为Lua编写的C模块可以模仿这种行为。除了C函数的定义外,C模块还必须定义一个特殊的函数,这个函数相当于一个Lua模块的主程序块。它应该注册模块中所有 的C函数,并将它们存储在一个适当的地方。并且,这个函数还应该初始化模块中所有需要初始化的东西。
Lua通过这个注册过程记录下C函数,然后使用这些函数地址直接调用它。也就是说,Lua调用C函数时,并不依赖于函数名、包的位置或可见性规则,而只依赖于注册时传入的函数地址。通常,C模块中只有一个公共(外部)函数,用于创建C模块。而其他所有函数都是私有的,在C语言中声明为static。
当用C函数扩展Lua时,最好将代码设计为一个C模块。因为,即使现在只注册一个函数,但说之后可能会需要更多的函数。辅助库为这项工作提供了一个函数 luaL_register,这个函数接收一些C函数以及其名称,并将这些函数注册到一个与模块同名的table中。例如,假设创建一个模块,其中包含了上节定义的l_dir函数。首先,必须定义这个模块函数:
static int l_dir (lua_State *L)
{
<如前>
}
然后,声明一个数组,其中包括模块中所有的函数及其名称。这个数组元素的类型为 luaL_Reg结构,该结构有两个字段,一个字符串和一个函数指针:
static const struct luaL_Reg mylib [] = {
{"dir", l_dir},
{NULL, NULL}
};
在示例中,只声明了一个函数l_dir。数组的最后一个元素总是{NULL, NULL},并以此标识结尾。最后,声明 一个主函数,其中用到了 luaL_register:
int luaopen_mylib (lua_State *L)
{
luaL_register(L, "mylib", mylib);
return 1;
}
luaL_register根据给定的名称(“mylib”)创建(或复用)一个table,并用数组mylib中的信息填充这个table。在 luaL_register返回时,会将这个table留在栈中。最后, luaopen_mylib函数返回1,表示将这个table返回给Lua。
当写完C模块后,必须将其链接到解释器。如果Lua解释器支持动态链接的话,那么最简便的方式是使用动态链接机制。在这种情况中,必须将C代码编译成动态链接库,并将这个库放入C路径(LUA_CPATH)中。然后,便可以用require从Lua加载这个模块:
require "mylib"
这句调用会将动态库mylib链接到Lua,并会寻找luaopen_mylib函数,将其注册为一个Lua函数,然后调用它以打开模块。
如果解释器不支持动态链接,那么就必须用新的模块来重新编译Lua。此外,还需要以某中方式来告诉解释器,它应在打开一个新状态的同时打开这个模块。最简单的做法是,将luaopen_mylib加到 luaL_openlibs会打开的标准库列表中,这个列表在文件 linit.c中。

































0 0