扩展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中。