首页 > 代码库 > 《Programming in Lua 3》读书笔记(二十三)

《Programming in Lua 3》读书笔记(二十三)

日期:2014.8.7
PartⅣ The C API

27 Calling C from Lua

     在这里说Lua调用C函数,并不意味着Lua可以调用任意的C函数。与之前C调用Lua函数一样,在这里同样需要遵循一些规则:传递参数,得到结果。不仅如此,Lua要调用C函数,我们首先需要注册这个函数,即需要将该函数的地址传递给Lua。
     当Lua调用C函数的时候,也是使用栈来做参数和返回结果的传递。C函数从栈中得到参数,然后将结果push至栈中。
     在这里一个重要的概念是:栈不是一个全局结构;每个函数都有它自己的局部栈。当Lua调用C函数的时候,第一个参数总是栈中index为1的那个值。甚至当一个C函数调用了Lua代码,然后Lua再调用一个C函数,每次调用都只可以访问到各自私有的栈,第一个参数都是栈中index为1的那个。


27.1 C Functions
     以书上的例子,如何实现一个简单的返回一个给定数的sin值的函数:

e.g.
static int l_sin (lua_State *L)
{
     double d = lua_tonumber(L,1);               /* 得到一个参数 */
     lua_pushnumber(L,sin(d));                   /* 将结果push 进去 */
     return 1;                                   /* 返回结果的数量*/
}
     任何注册到Lua的函数都有一个同样的原型,在头文件lua.h 中定义为lua_CFuntion :
typedef int (*lua_CFuntion) (lua_State *L)
     从C的角度来看,一个C函数以一个Lua state作为其唯一参数,然后返回一个代表函数返回值数量的整型数。从这点来看,函数不需要在push结果之前将栈清空,在函数返回之后,Lua会自动存储其结果然后将整个栈清空。
     在Lua使用这个函数之前,我们需要先注册这个函数。我们使用lua_pushcfunction 来实现这个功能:从C函数中得到一个指针,然后创建一个function类型的变量来在Lua中代表这个函数。一旦注册了这个函数,那么C函数就跟Lua中的函数表现一致了:
e.g.
lua_pushcfunction(L,l_sin);
lua_setglobal(L,"mysin");     /* 在lua中创建一个全局变量mysin ,然后将c函数赋值给mysin*/
     在这之后,就可以在Lua中使用mysin这个函数了。
     将函数处理得更为标准点,我们需要检查参数类型。此时辅助函数luaL_checknumber 将会做到这点,该函数检查给定的参数是否是一个number,假如遇到错误,那么该函数将会抛出错误信息,否则将会返回检查的这个数。修改之后的例子:
e.g.
static int l_sin(lua_State *L)
{
     double d = luaL_checknumber(L,1)     /* 检查参数 */
     lua_pushnumber(L,sin(d));
     return 1;
}
     如果传递的参数为非数值类型,此时将会得到错误信息:
bad argument #1 to ‘myosin‘ (number expected,got string)
     此时这里错误信息是怎么得到传递的参数,函数名字,函数需要的参数类型和实际的参数类型的,这个需要注意



27.2 Continuations
     通过lua_pcall 和 lua_call,一个被Lua调用的C函数可以再回调一个Lua函数。标准库中的几个函数支持这个操作:table.sort 可以调用一个排序函数;string.gusb 可以调用一个替代函数;pcall 和xpcall 可以在安全模式下调用一个函数。在这里提一下Lua的主程序是从C中(宿主程序)调用的,这里就有一个队列了:C(宿主)调用Lua(脚本),Lua脚本调用C(标准库),C(标准库)再调用Lua(回调)。
     通常Lua处理这个队列是没有问题的;但是在协同程序中这个就有点困难了。
     Lua中的每个协同程序都有自己的栈,用来保存那些未执行调用协同程序的信息。特别的是,这个栈将会存储每次访问的返回地址,参数,和一些局部变量。在调用Lua函数的时候,解释器使用合适的数据结构来实现这个栈(soft stack);而在调用C函数的时候,解释器就必须使用C的栈了。毕竟C函数的返回地址和局部变量是存储在C栈中的。
     解释器是很容易拥有多个sofe stack的,但是ANSI C的runtime却只能有一个内部栈;这就意味着Lua不能延迟C函数的执行:假如有一个在从resume状态到其他状态的过程中访问的C函数,Lua是不能为下一次resume恢复而存储C函数的状态的。这里的一个例子:
e.g.
co = coroutine.wrap(function ( a )
                              return pcall(function ( x )
                                   coroutine.yield(x[1])
                                   return x[3]
                              end,a)
end)
print(co({10,3,-8,15}))
     这个例子我在sublime中执行没有问题,书本上是说这个有问题的,需要再注意。书上对这个例子的解释是:pcall是一个c函数,因此Lua不能延迟这个函数的执行,因为在ANSI C中没有方法延迟一个C函数的执行然后在某个时间点再恢复这个函数。
     在Lua5.2中使用continuations 对这个问题做了修正(现在我用的就是5.2版,可能此时已经有修正处理了),lua5.2使用long jumps来实现yields,与处理错误使用的是同一个方法。一个long jump仅仅是将C栈中的C函数的信息抛出,因此是可以再恢复这个函数的。



27.3 C Modules
     Lua中的模块就是定义多个函数然后存储在一个合适的地方,一般是存储在table中。一个C的模块也差不多是这个意思。当然除了要定义C函数,还一定需要定义一个特殊的函数,其功能类似于Lua中的main chunk。这个函数用来注册模块中的所有C函数,然后将这些函数存储到一个合适的地方,这里也一般是table。和Lua的main chunk一样,该函数也应该初始化模块需要的任何东西。
     Lua通过C函数的注册过程监听C函数。一旦一个C函数存储至了Lua中,那么Lua便可以立即通过其地址(注册函数时得到)调用该函数。这也表明,Lua并不是依靠函数名字,包的位置,等来调用C函数的。特别的,一个C模块有一个公有函数,该函数用来打开这个模块的,而模块中其余的函数可以是私有的,以static 来声明。
     当你想用C函数来扩展Lua的时候,将扩展代码封装为C模块是一个不错的选择,即便仅有一个函数:因为最终你都会需要其他的函数的。和往常一样,辅助函数库提供了一个辅助函数:luaL_newlib 以各自函数名得到一个C函数列表然后将所有函数注册并存储至一个新的table中。以一个例子讲解:假如此时我们想创建一个库,有一个函数l_dir,首先我们需要定义这个库函数:
e.g.
static int l_dir(lua_State *L)
{ 
     /*code*/
}
     下一步是需要声明一个数据结构以各自函数的名字存储需要加入到这个库中的所有函数,该数据结构的类型为:luaL_Reg,数组的数据结构有两个字段:一个字符串代表函数的名字,一个函数指针
e.g.
static const struct luaL_Reg mylib[] = 
{
     {"dir",l_dir),
     {NULL,NULL}
};
     以{NULL,NULL}结尾是必须的,用来作为结尾符号。最后声明一个主函数,使用luaL_newlib:
e.g.
int luaopen_mylib(lua_State *L)
{
     luaL_newlib(L,mylib);
     return 1;
}
     创建完之后,便是将库链接到编译器了。最方便的方式便是采用动态链接的特性(windows中创建mylib.dll,linux中创建mylib.so )然后加入到C路径中去。经过这些步骤,便可以从Lua中加载这个库了,使用require实现:
e.g.
local mylib = require "mylib"
     以上这步将动态链接库mylib链接到了lua中,找到luaopen_mylib函数,以一个C函数注册该函数然后调用该函数以打开模块。
     动态链接器要求需要知道luaopen_mylib函数的名字以便能找到该函数。通常,以luaopen_+模块的名字来做寻找,因此假如模块的名字叫做mylib,那么函数的名字就必须为luaopen_mylib。
     假如编译器不支持动态链接,那么就需要重新编译Lua以加入你新的库了。而且也需要告诉编译器需要打开你新加入的库,通常这里是将新的库加入到标准库的打开列表中去,在linit.c中,以便让luaL_openlibs打开这些库。

《Programming in Lua 3》读书笔记(二十三)