Lua的C API

Lua可以与C很好地互通。主要是通过栈来通信。

引入Lua提供的C API

lauxlib.h   lua.h   lua.hpp   luaconf.h   lualib.h   

以下实现一个Lua的简单的解释器

#include <stdio.h>
#include <string.h>
extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
int main (void)
{
    char buff[256];
    int error;
    lua_State *L = lua_open();  /* opens Lua */
    if (NULL == L)
    {
        printf("can not open lua_State");
    }
    
    luaopen_base(L);         /* opens the basic library */
    
    luaopen_table(L);        /* opens the table library */
    
    //luaopen_io(L);           /* opens the I/O library */  
    int x = lua_cpcall(L, luaopen_io, NULL);  //luaopen_io在5.1版本被删除了,这里用lua_cpcall

    luaopen_string(L);       /* opens the string lib. */
    luaopen_math(L);         /* opens the math lib. */
    
    while (fgets(buff, sizeof(buff), stdin) != NULL) {    
        error = luaL_loadbuffer(L, buff, strlen(buff),
            "line") || lua_pcall(L, 0, 0, 0);
        if (error) {
            fprintf(stderr, "...%s\n", lua_tostring(L, -1));
            lua_pop(L, 1);//pop error message from the stack
        }
    }
    
    lua_close(L);
    
    return 0;
}

函数lua_open创建一个新环境(或state)。lua_open创建一个新的环境时,这个环境并不包括预定义的函数,甚至是print。为了保持Lua的苗条,所有的标准库以单独的包提供,所以如果你不需要就不会强求你使用它们。头文件lualib.h定义了打开这些库的函数。例如,调用luaopen_io,以创建io table并注册I/O函数(io.read,io.write等等)到Lua环境中。

创建一个state并将标准库载入之后,就可以着手解释用户的输入了。对于用户输入的每一行,C程序首先调用luaL_loadbuffer编译这些Lua代码。如果没有错误,这个调用返回零并把编译之后的chunk压入栈。之后,C程序调用lua_pcall,它将会把chunk从栈中弹出并在保护模式下运行它。和luaL_laodbuffer一样,lua_pcall在没有错误的情况下返回零。在有错误的情况下,这两个函数都将一条错误消息压入栈;我们可以用lua_tostring来得到这条信息、输出它,用lua_pop将它从栈中删除。

注意luaopen_io函数在5.1的版本中被删除了,有以下三种解决方法

1. lua_cpcall(L, luaopen_io, NULL);

 2. lua_pushcfunction(L, luaopen_io);
     lua_pushstring(L, LUA_IOLIBNAME);
     lua_pcall(L, 1, 0, 0);      

 3. lua_pushcfunction(L, luaopen_io);
     lua_pushstring(L, LUA_IOLIBNAME);
     lua_call(L, 1, 0);

lua_call函数没有返回值,如果函数运行出错程序会崩溃,所以应该用前两种情况。

lua_cpcall只能用来调用lua_CFunction类型的C函数,而lua_pcall和lua_call可以调用Lua函数也可以调用lua_CFunction类型的C函数

 

堆栈:

Lua与C进行通信时采用堆栈作为中转站(由于篇幅问题就不赘述使用堆栈的原因了,http://book.luaer.cn/ 24.2 堆栈一章有详细的解释)

无论C要调用Lua的函数还是Lua要调用C函数,都是把数据先压入栈,然后再从栈顶取出数据

正如上面的例子,先由lua_loadBuffer运行一个chunk,并把结果压入栈(被编译了的chunk或一条错误信息), lua_pcall从栈中获取这个chunk并把一些临时的错误信息压入栈

Lua的栈严格先进后出,在Lua中只能对栈顶进行操作。但在C中,可以对栈中的任何位置操作,甚至可以在栈中任一位置插入和删除元素

压入元素:

API有一系列压栈的函数,它将每种可以用C来描述的Lua类型压栈:空值(nil)用lua_pushnil,数值型(double)用lua_pushnumber,布尔型(在C中用整数表示)用lua_pushboolean,任意的字符串(char*类型,允许包含'\0'字符)用lua_pushlstringC语言风格(以'\0'结束)的字符串(const char*)用lua_pushstring

void lua_pushnil (lua_State *L);
void lua_pushboolean (lua_State *L, int bool);
void lua_pushnumber (lua_State *L, double n);
void lua_pushlstring (lua_State *L, const char *s, size_t length);
void lua_pushstring (lua_State *L, const char *s);
int lua_checkstack (lua_State *L, int sz); // 用于扩大栈空间
int lua_gettop(Lua_State *L) //获取栈顶的序号,同时也相当于返回栈内元素的个数

Lua从来不保持一个指向外部字符串(或任何其它对象,除了C函数——它总是静态指针)的指针。对于它保持的所有字符串,Lua要么做一份内部的拷贝要么重新利用已经存在的字符串。因此,一旦这些函数返回之后你可以自由的修改或是释放你的缓冲区。

Lua默认的栈空间为20,因为Lua不会自己去检测栈空间是否足够,所以我们需要确保不会栈溢出(一般情况下只要不是不断向栈中压入数据都不会栈溢出),lua_checkstack可以用于扩大栈空间。

Lua的栈起始序号是1,所以lua_gettop函数返回栈顶的序号相当于栈内的元素个数。

查询元素:

API用索引来访问栈中的元素。在栈中的第一个元素(也就是第一个被压入栈的)有索引1,下一个有索引2,以此类推。我们也可以用栈顶作为参照来存取元素,利用负索引。在这种情况下,-1指出栈顶元素(也就是最后被压入的),-2指出它的前一个元素,以此类推。

API提供了一套lua_is*函数来检查一个元素是否是一个指定的类型,*可以是任何Lua类型。因此有lua_isnumber,lua_isstring,lua_istable以及类似的函数。所有这些函数都有同样的原型:

int lua_is... (lua_State *L, int index);

实际上,这些函数都是调用了lua_type返回的变量类型,并且与宏类型常量比较(LUA_TNILLUA_TBOOLEANLUA_TNUMBERLUA_TSTRINGLUA_TTABLELUA_TFUNCTIONLUA_TUSERDATA以及LUA_TTHREAD)

为了从栈中获得值,这里有lua_to*函数:

int           lua_toboolean (lua_State *L, int index);
double        lua_tonumber (lua_State *L, int index);
const char *  lua_tostring (lua_State *L, int index);
size_t        lua_strlen (lua_State *L, int index);

Lua_tostring函数返回一个指向字符串的内部拷贝的指针。你不能修改它(使你想起那里有一个const)。只要这个指针对应的值还在栈内,Lua会保证这个指针一直有效。当一个C函数返回后,Lua会清理他的栈,所以,有一个原则:永远不要将指向Lua字符串的指针保存到访问他们的外部函数中。

Lua_string返回的字符串结尾总会有一个字符结束标志0,但是字符串中间也可能包含0,lua_strlen返回字符串的实际长度。

其他堆栈操作:

int  lua_gettop (lua_State *L);

void lua_settop (lua_State *L, int index);

void lua_pushvalue (lua_State *L, int index);

void lua_remove (lua_State *L, int index);

void lua_insert (lua_State *L, int index);

void lua_replace (lua_State *L, int index);

settop可以用于设置栈顶,#define lua_pop(L,n)  lua_settop(L, -(n)-1)  这个宏可用于弹出n个元素。

为了说明这些函数的用法,这里有一个有用的帮助函数,它dump整个堆栈的内容:

static void stackDump (lua_State *L) {
    int i;
    int top = lua_gettop(L);
    for (i = 1; i <= top; i++) {  /* repeat for each level */
       int t = lua_type(L, i);
       switch (t) {
 
       case LUA_TSTRING:  /* strings */
           printf("`%s'", lua_tostring(L, i));
           break;
 
       case LUA_TBOOLEAN:  /* booleans */
           printf(lua_toboolean(L, i) ? "true" : "false");
           break;
 
       case LUA_TNUMBER:  /* numbers */
           printf("%g", lua_tonumber(L, i));
           break;
   
       default:  /* other values */
           printf("%s", lua_typename(L, t));
           break;
 
       }
       printf("  ");  /* put a separator */
    }
    printf("\n");     /* end the listing */
}

这个函数从栈底到栈顶遍历了整个堆栈,依照每个元素自己的类型打印出其值。它用引号输出字符串;以%g的格式输出数字;对于其它值(table,函数,等等)它仅仅输出它们的类型(lua_typename转换一个类型码到类型名)。

下面的函数利用stackDump更进一步的说明了API堆栈的操作。

#include <stdio.h>
#include <lua.h>
 
static void stackDump (lua_State *L) {
    ...
}
 
int main (void) {
    lua_State *L = lua_open();
    lua_pushboolean(L, 1); lua_pushnumber(L, 10);
    lua_pushnil(L); lua_pushstring(L, "hello");
    stackDump(L);
           /* true  10  nil  `hello'  */
 
    lua_pushvalue(L, -4); stackDump(L);
           /* true  10  nil  `hello'  true  */
 
    lua_replace(L, 3); stackDump(L);
           /* true  10  true  `hello'  */
 
    lua_settop(L, 6); stackDump(L);
           /* true  10  true  `hello'  nil  nil  */
 
    lua_remove(L, -3); stackDump(L);
           /* true  10  true  nil  nil  */
 
    lua_settop(L, -5); stackDump(L);
           /* true  */
 
    lua_close(L);
    return 0;
}

 

 Lua调用C函数

需要先把C函数注册到Lua中(相当于把C函数的地址通过栈传递到Lua中)。

Lua调用C函数,会将参数放入栈中,C函数通过栈获取参数并返回结果。同时,C函数将会返回结果的个数。

Lua调用C函数所用的栈是私有的,每个C函数都会有一个独立的栈

任何在Lua中注册的函数必须有同样的原型,这个原型声明定义就是lua.h中的lua_CFunction:

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

假设现有这样一个函数

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_pushcfunction(l, l_sin); // push function into stack

lua_setglobal(l, "mysin"); // named as "mysin" , finish reg

lua_pushcfunction会把l_sin压入栈,lua_setglobal会把l_sin重新编译并赋值给全局变量'mysin' ,  完成注册后即可调用。^o^

采用前面提到的简单Lua解释器的C实现就可以轻松调用了。

未完待续

 

posted on 2014-04-11 20:46  Elenno  阅读(942)  评论(0编辑  收藏  举报

导航