第26章 调用C函数

26 调用C函数
扩展 Lua 的基本方法之一就是为应用程序注册新的 C 函数到 Lua 中去。
当我们提到 Lua 可以调用 C 函数,不是指 Lua 可以调用任何类型的 C 函数(有一些包可以让 Lua 调用任意的 C 函数,但缺乏便捷和健壮性)。正如我们前面所看到的,当 C 调用 Lua 函数的时候,必须遵循一些简单的协议来传递参数和获取返回结果。相似的,从 Lua 中调用 C 函数,也必须遵循一些协议来传递参数和获得返回结果。另外,从 Lua 调用 C 函数我们必须注册函数,也就是说,我们必须把 C 函数的地址以一个适当的方式传递给 Lua 解释器。
Lua 调用 C 函数的时候,使用和 C 调用 Lua 相同类型的栈来交互。 C 函数从栈中获取她的参数,调用结束后将返回结果放到栈中。为了区分返回结果和栈中的其他的值,每个 C 函数还会返回结果的个数( the function returns (in C) the number of results it is leaving on the stack. )。这儿有一个重要的概念:用来交互的栈不是全局变量,每一个函数都有他自己的私有栈。当 Lua 调用 C 函数的时候,第一个参数总是在这个私有栈的 index=1 的位置。甚至当一个 C 函数调用 Lua 代码( Lua 代码调用同一个 C 函数或者其他的 C 函数),每一个 C 函数都有自己的独立的私有栈,并且第一个参数在 index=1 的位置。
26.1 C 函数
先看一个简单的例子,如何实现一个简单的函数返回给定数值的 sin 值(更专业的实现应该检查他的参数是否为一个数字):
static int l_sin (lua_State *L) {
    double d = lua_tonumber(L, 1); /* get argument */
    lua_pushnumber(L, sin(d));      /* push result */
    return 1;                       /* number of results */
}
任何在 Lua 中注册的函数必须有同样的原型,这个原型声明定义就是 lua.h 中的 lua_CFunction
typedef int (*lua_CFunction) (lua_State *L);
C 的角度来看,一个 C 函数接受单一的参数 Lua state ,返回一个表示返回值个数的数字。所以,函数在将返回值入栈之前不需要清理栈,函数返回之后, Lua 自动的清除栈中返回结果下面的所有内容。
我们要想在 Lua 使用这个函数,还必须首先注册这个函数。我们使用 lua_pushcfunction 来完成这个任务:他获取指向 C 函数的指针,并在 Lua 中创建一个 function 类型的值来表示这个函数。一个 quick-and-dirty 的解决方案是将这段代码直接放到 lua.c 文件中,并在调用 lua_open 后面适当的位置加上下面两行:
lua_pushcfunction(l, l_sin);
lua_setglobal(l, "mysin");
第一行将类型为 function 的值入栈,第二行将 function 赋值给全局变量 mysin 。这样修改之后,重新编译 Lua ,你就可以在你的 Lua 程序中使用新的 mysin 函数了。在下面一节,我们将讨论以比较好的方法将新的 C 函数添加到 Lua 中去。
对于稍微专业点的 sin 函数,我们必须检查 sin 的参数的类型。有一个辅助库中的 luaL_checknumber 函数可以检查给定的参数是否为数字:当有错误发生的时候,将抛出一个错误信息;否则返回作为参数的那个数字。将上面我们的函数稍作修改:
static int l_sin (lua_State *L) {
    double d = luaL_checknumber(L, 1);
    lua_pushnumber(L, sin(d));
    return 1; /* number of results */
}
根据上面的定义,如果你调用 mysin('a'), 会得到如下信息:
bad argument #1 to 'mysin' (number expected, got string)
注意看看 luaL_checknumber 是如何自动使用:参数 number 1 ),函数名( "mysin" ),期望的参数类型( "number" ),实际的参数类型( "string" )来拼接最终的错误信息的。
下面看一个稍微复杂的例子:写一个返回给定目录内容的函数。 Lua 的标准库并没有提供这个函数,因为 ANSI C 没有可以实现这个功能的函数。在这儿,我们假定我们的系统符合 POSIX 标准。我们的 dir 函数接受一个代表目录路径的字符串作为参数,以数组的形式返回目录的内容。比如,调用 dir("/home/lua") 可能返回 {".", "..", "src", "bin", "lib"} 。当有错误发生的时候,函数返回 nil 加上一个描述错误信息的字符串。
#include <dirent.h>
#include <errno.h>
 
static int l_dir (lua_State *L) {
    DIR *dir;
    struct dirent *entry;
    int i;
    const char *path = luaL_checkstring(L, 1);
 
    /* open directory */
    dir = opendir(path);
    if (dir == NULL) {   /* error opening the directory? */
       lua_pushnil(L);   /* return nil and ... */
       lua_pushstring(L, strerror(errno)); /* error message */
       return 2; /* number of results */
    }
 
    /* 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);
    }
 
    closedir(dir);
    return 1;         /* table is already on top */
}
辅助库的 luaL_checkstring 函数用来检测参数是否为字符串,与 luaL_checknumber 类似。(在极端情况下,上面的 l_dir 的实现可能会导致小的内存泄漏。调用的三个 Lua 函数 lua_newtable lua_pushstring lua_settable 可能由于没有足够的内存而失败。其中任何一个调用失败都会抛出错误并且终止 l_dir ,这种情况下,不会调用 closedir 。正如前面我们所讨论过的,对于大多数程序来说这不算个问题:如果程序导致内存不足,最好的处理方式是立即终止程序。另外,在 29 章我们将看到另外一种解决方案可以避免这个问题的发生)
26.2 C 函数库
一个 Lua 库实际上是一个定义了一系列 Lua 函数的 chunk ,并将这些函数保存在适当的地方,通常作为 table 的域来保存。 Lua C 库就是这样实现的。除了定义 C 函数之外,还必须定义一个特殊的用来和 Lua 库的主 chunk 通信的特殊函数。一旦调用,这个函数就会注册库中所有的 C 函数,并将他们保存到适当的位置。像一个 Lua chunk 一样,她也会初始化其他一些在库中需要初始化的东西。
Lua 通过这个注册过程,就可以看到库中的 C 函数。一旦一个 C 函数被注册之后并保存到 Lua 中,在 Lua 程序中就可以直接引用他的地址(当我们注册这个函数的时候传递给 Lua 的地址)来访问这个函数了。换句话说,一旦 C 函数被注册之后, Lua 调用这个函数并不依赖于函数名,包的位置,或者调用函数的可见的规则。通常 C 库都有一个外部( public/extern )的用来打开库的函数。其他的函数可能都是私有的,在 C 中被声明为 static
当你打算使用 C 函数来扩展 Lua 的时候,即使你仅仅只想注册一个 C 函数,将你的 C 代码设计为一个库是个比较好的思想:不久的将来你就会发现你需要其他的函数。一般情况下,辅助库对这种实现提供了帮助。 luaL_openlib 函数接受一个 C 函数的列表和他们对应的函数名,并且作为一个库在一个 table 中注册所有这些函数。看一个例子,假定我们想用一个我们前面提过的 l_dir 函数创建一个库。首先,我们必须定义库函数:
static int l_dir (lua_State *L) {
    ...    /* as before */
}
第二步,我们声明一个数组,保存所有的函数和他们对应的名字。这个数组的元素类型为 luaL_reg :是一个带有两个域的结构体,一个字符串和一个函数指针。
static const struct luaL_reg mylib [] = {
    {"dir", l_dir},
    {NULL, NULL} /* sentinel */
};
在我们的例子中,只有一个函数 l_dir 需要声明。注意数组中最后一对必须是 {NULL, NULL} ,用来表示结束。第三步,我们使用 luaL_openlib 声明主函数:
int luaopen_mylib (lua_State *L) {
    luaL_openlib(L, "mylib", mylib, 0);
    return 1;
}
luaL_openlib 的第二个参数是库的名称。这个函数按照指定的名字创建 ( 或者 reuse) 一个表,并使用数组 mylib 中的 name-function 对填充这个表。 luaL_openlib 还允许我们为库中所有的函数注册公共的 upvalues 。例子中不需要使用 upvalues ,所以最后一个参数为 0 luaL_openlib 返回的时候,将保存库的表放到栈内。 luaL_openlib 函数返回 1 ,返回这个值给 Lua 。( The luaopen_mylib function returns 1 to return this value to Lua )(和 Lua 库一样,这个返回值是可选的,因为库本身已经赋给了一个全局变量。另外,像在 Lua 标准库中的一样,这个返回不会有额外的花费,在有时候可能是有用的。)
完成库的代码编写之后,我们必须将它链接到 Lua 解释器。最常用的方式使用动态连接库,如果你的 Lua 解释器支持这个特性的话(我们在 8.2 节已经讨论过了动态连接库)。在这种情况下,你必须用你的代码创建动态连接库( windows .dll 文件, linux .so 文件)。到这一步,你就可以在 Lua 中直接使用 loadlib 加载你刚才定义的函数库了,下面这个调用:
mylib = loadlib("fullname-of-your-library", "luaopen_mylib")
luaopen_mylib 函数转换成 Lua 中的一个 C 函数,并将这个函数赋值给 mylib (那就是为什么 luaopen_mylib 必须和其他的 C 函数有相同的原型的原因所在)。然后,调用 mylib() ,将运行 luaopen_mylib 打开你定义的函数库。
如果你的解释器不支持动态链接库,你必须将你的新的函数库重新编译到你的 Lua 中去。除了这以外,还不要一些方式告诉独立运行的 Lua 解释器,当他打开一个新的状态的时候必须打开这个新定义的函数库。宏定义可以很容易实现这个功能。第一,你必须使用下面的内容创建一个头文件(我们可以称之为 mylib.h ):
int luaopen_mylib (lua_State *L);
 
#define LUA_EXTRALIBS { "mylib", luaopen_mylib },
第一行声明了打开库的函数。第二行定义了一个宏 LUA_EXTRALIBS 作为函数数组的新的入口,当解释器创建新的状态的时候会调用这个宏。(这个函数数组的类型为 struct luaL_reg[] ,因此我们需要将名字也放进去)
为了在解释器中包含这个头文件,你可以在你的编译选项中定义一个宏 LUA_USERCONFIG 。对于命令行的编译器,你只需添加一个下面这样的选项即可:
-DLUA_USERCONFIG=/"mylib.h/"
(反斜线防止双引号被 shell 解释,当我们在 C 中指定一个头文件时,这些引号是必需的。)在一个整合的开发环境中,你必须在工程设置中添加类似的东西。然后当你重新编译 lua.c 的时候,它包含 mylib.h ,并且因此在函数库的列表中可以用新定义的 LUA_EXTRALIBS 来打开函数库。
 
 
 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值