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是如何自动使用:参数number1),函数名("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_newtablelua_pushstringlua_settable可能由于没有足够的内存而失败。其中任何一个调用失败都会抛出错误并且终止l_dir,这种情况下,不会调用closedir。正如前面我们所讨论过的,对于大多数程序来说这不算个问题:如果程序导致内存不足,最好的处理方式是立即终止程序。另外,在29章我们将看到另外一种解决方案可以避免这个问题的发生)


相关链接:
lua程序设计目录 - 中国lua开发者 - lua论坛