programming in lua (8)

第 26 章。

扩展 lua 的一个基本方法是使用 c 语言编写可供 lua 使用的函数。

当我们说 lua“调用”c 函数时,其实并不是真正地调用 c 函数,而是 c 函数遵循一定的约定从 lua 中获取参数并且把结果返回给 lua,或许叫“使用”更合适。在 lua 调用 c 函数前需要先注册 c 函数,即把 c 函数的地址传递给 lua。事实上,lua 使用 c 函数也是通过类似的栈机制,为了避免取了错误的返回值,c 函数需要返回它返回给 lua 的返回值个数(也就是说 lua 应该从栈里取几个值)。

和在 c 中使用 lua 函数不同的是,这里的栈并不是全局的,每个 c 函数都有各自独立的栈。当 lua 调用一个 c 函数时,c 函数的第一个参数索引总是 1。即使一个函数 func 调用了一段 lua 代码,而这段 lua 代码又调用了 func,第二次调用的 func 拥有的栈是一个新的独立的栈,而不是调用 lua 代码的 func 的栈。

还是看看例子比较容易明白……

可被 lua 使用的 c 函数

下面的函数接收一个整数 n 作为参数,返回 (n+5) 的结果:

static int l_add5(lua_State* l)
{
    int n = lua_tonumber(l, 1); /* get argument */
    lua_pushnumber(l, n + 5);   /* push result */
    return 1;                   /* number of results */
}

所有能被 lua 调用的函数都有一个统一的函数原型,这个函数原型在 lua.h 中定义:

typedef int (*lua_CFunction) (lua_State *L);

函数接收一个 lua_State* 参数,返回值是 c 函数放到栈里的结果个数。c 函数不需要对栈进行清理,在 c 函数返回后 lua 会把栈清空(因为每个函数都有各自独立的栈,当函数返回后这个栈就没用了)。

要先把函数注册到 lua 的环境中,lua 才能调用这个函数。我们使用函数

void lua_pushcfunction (lua_State *L, lua_CFunction f);

把一个 c 函数 f() 注册到 lua 环境中,然后把函数赋值给 lua 中的变量:

void lua_setglobal (lua_State *L, const char *name);

这个函数把栈顶的元素赋值给 lua 中名为 name 的全局变量。例如

lua_setglobal(l, "add5");

就把 c 函数 l_add5() 赋值给 lua 中的全局变量 add5,之后在 lua 中就可以通过 add5() 来使用这个 c 函数了。

为了使函数更健壮,在函数执行前应该先对参数进行检查:

static int l_add5(lua_State* l)
{
    int n = luaL_checknumber(l, 1);
    lua_pushnumber(l, n + 5);
    return 1; /* number of results */
}

函数

lua_Number luaL_checknumber (lua_State *L, int narg);

检查某个 c 函数栈中的第 narg 个函数是否是数值类型,如果不是的话会抛出异常。如果我们调用函数 add5('a'),lua 会抛出一个异常信息“bad argument #1 to 'add5' (number expected, got string)”。

接着看一个复杂一点的列子。有一个 lua 脚本 dir.lua,它调用一个函数 mydir() 获取当前目录下的项并且打印,和 ls 的作用差不多:

-- dir.lua

entry = mydir(".")

i = 1
while entry[i] do
    print(entry[i])
    i = i + 1
end

直接运行这个脚本将会得到错误信息,因为 mydir() 并没有在 lua 脚本中定义,而是由 c 程序向 lua 环境中注册一个名叫“mydir”的 lua 函数供其使用。下面是完整的 c 程序:

#include <stdio.h>
#include <errno.h>
#include <string.h>

#include <sys/types.h>
#include <dirent.h>

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

static int l_dir(lua_State* l)
{
    DIR* dir;
    struct dirent* de;
    int i;
    const char* path = luaL_checkstring(l, 1);

    dir = opendir(path);
    if (!dir) {
        lua_pushnil(l); /* return nil */
        lua_pushstring(l, strerror(errno)); /* and error message */
        return 2; /* number of results */
    }

    lua_newtable(l);
    i = 1;
    while ((de = readdir(dir))) {
        lua_pushnumber(l, i++); /* push key */
        lua_pushstring(l, de->d_name); /* push value */
        lua_settable(l, -3);
    }

    closedir(dir);
    return 1;   /* table is already on top */
}

int main(void)
{
    lua_State* l;

    l = luaL_newstate();
    luaL_openlibs(l);

    lua_pushcfunction(l, l_dir);
    lua_setglobal(l, "mydir"); /* register new function */

    if (luaL_loadfile(l, "./dir.lua") != 0) {
        fprintf(stderr, "luaL_loadfile err: %s.\n", lua_tostring(l, -1));
        goto end;
    }

    if (lua_pcall(l, 0, 0, 0) != 0)
        fprintf(stderr, "lua_pcall err: %s.\n", lua_tostring(l, -1));

end:
    lua_close(l);
    return 0;
}

函数 l_dir() 先创建一个空的 table 并将其放在栈顶:

void lua_newtable (lua_State *L);

然后使用 readdir() 读取一个目录项,并将索引和其名字压入栈中,然后使用函数

void lua_settable (lua_State *L, int index);

把元素加入到 table 中。lua_settable() 相当于语句“t[k]=v”,其中 t 是位于 index 的一个 table,v 是位于栈顶的值,k 是 v 下面的元素。最后得到一个包含所有目录项名称的 table。

主程序中,先用

void lua_pushcfunction (lua_State *L, lua_CFunction f);

把 c 函数压入栈中,然后用 lua_setglobal() 把 c 函数赋值给名为“mydir”的全局变量,最后再读入脚本执行。这时程序应该把当前目录的目录项打印到屏幕上。

在某些特殊情况下,l_dir() 可能会造成内存泄漏。如果程序在执行 lua_newtable(),lua_pushstring() 或 lua_settable() 的时候出错了,l_dir() 就会产生一个错误并且立刻退出,closedir() 就不会被执行。

用 c 编写 lua 的模块

一个 lua 模块就是一个定义了若干个 lua 函数的 chunk,这个 chunk 通常作为一个 table 的元素。除了要定义实现主要功能的 c 函数外,还需定义一些

c 语言通过注册函数来让 lua 知道这些函数的存在。一旦一个 c 函数被注册到 lua 环境中,lua 使用这个函数的地址来访问它,而不管它叫什么名字,函数所在的模块,或者它的可见性(没读懂,原文是 visibility rules)。一般来说,每个 c 模块只需要一个对外接口,这个接口的功能就是加载所有模块,其它的模块函数都可以用 static 关键字隐藏起来。

当你准备使用 c 编写函数来扩展 lua 时,最好把这些扩展做成一个模块,尽管很可能目前只增加了一个函数,但是很快你就会要增加其它函数。lua 提供了一个函数来批量注册 c 函数:

void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup);

其中 luaL_Reg 的定义为:

typedef struct luaL_Reg {
  const char *name;
  lua_CFunction func;
} luaL_Reg;

第三个参数 nup 表示 upvalue(就是闭包用的那个值)的个数。如果不为 0,所有的函数都使用相同的 upvalues 作为初始值。初始化后 luaL_Reg 和 upvalues 都会被弹出栈。在 5.2.0 的 manual 中说 luaL_register() 已经不再使用了。

例如我们可以把上面的 l_dir() 作为一个模块:

static const luaL_Reg mylib [] = {
   {"dir", l_dir},
   {NULL, NULL} /* sentinel */
};

然后把这个模块中的所有函数注册到 lua 环境中:

luaL_setfuncs(l, mylib, 0);

你可以把这个 c 文件编译成动态库或者静态库,在 c 程序编译的时候链接它。如果不支持动态或静态链接,也可以把函数放到 linit.c 中,在 luaL_openlibs() 中加上 luaL_setfuncs(),然后重新编译 lua。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注