Lua编程入门

 主页   资讯   文章   代码   电子书 

Lua - 常用的 C API

基础概念

states

Lua连接库是完全可重入的,因为它没有全局变量。Lua解释器的整个state(如全局变量、堆栈等)都存储在一个结构类型为Lua_State动态分配的对象里。指向这一对象的指针必须作为第一个参数传递给所有连接库的API,除了用来生成一个Lua state的函数——lua_open。在调用所有的API函数之前,你必须先用lua_open以生成一个state:

lua_State* lua_open(void);

可以通过调用lua_close来释放一个通过lua_open生成的state:

void lua_close (lua_State *L);

这一函数销毁给定的Lua_State中的所有对象并释放state所占用的动态内存(如果有必要的话将通过调用对应的垃圾收集元方法来完成),在某些平台上,你不必调用这个函数,因为当宿主程序退出时会释放所有的资源,换句话说,长期运行的程序,如守护进程或web服务器,应尽快释放state所占的资源,以避免其过于庞大。

堆栈与索引

Lua使用虚拟堆栈机制和C程序互相传值,所有的堆栈中的元素都可以看作一个Lua值(如nil, number, string等)。

当Lua调用C函数时,被调用的C函数将得到一个新的堆栈。这一堆栈与之前调用此函数的堆栈无关,也有其它C函数的堆栈无关。这一新的堆栈用调用C函数要用到的参数初始化,同时,这一堆栈也被用以返回函数调用结果。

为了便于操作,在API的中大量操作都并不依从堆栈只能操作栈顶元素的严格规则。而通过索引引用堆栈的任一元素。一个正整数索引可以看作某一元素在堆栈中的绝对位置(从1开始计数),一个负整数索引可以看作某一元素相对于栈顶的偏移量。

特别地,如果堆栈中有n个元素,那么索引1指向第一个元素(即第一个压入栈的元素)索引n指向最后一个元素;反过来,索引-1指向最后一个元素(即栈顶元素)索引-n指向第一个元素。当一个索引大于1并小于n时我们称其为一个有效索引(即1 <= abs(index) <= top)。

接口解析

lua_newstate

lua_State *lua_newstate (lua_Alloc f, void *ud);

创建一个新的独立 state,不能创建返回 NULL。形参 f 是 allocator 函数,Lua 通过这个函数来为这个 state 分配内存。第二个形参 ud,是一个透明指针,每次调用时,Lua简单地传给 allocator 函数。

lua_open/lua_close

lua_open 被 lua_newstate 替换,可以使用luaL_newstate从标准库中创建一个标准配置的 state,如: lua_State *L = luaL_newstate(); 。

void lua_close (lua_State *L);

销毁指定的 state 中所有的对象,并释放指定的 state 中使用的所有动态内存。

lua_load/lua_call/lua_pcall/lua_cpcall

这些函数的目的就是让我们能够执行压入栈中的函数,该函数可能是lua中定义的函数,可能是C++重定义的函数,当然我们一般是用来执行lua中执行的函数,C++中定义的基本上可以直接调用的。

int lua_load (lua_State *L,
              lua_Reader reader,
              void *data,
              const char *chunkname);

void lua_call(lua_State *L, int nargs, int nresults);
void lua_pcall(lua_State *L, int nargs, int nresults, int errfunc);
void lua_cpcall(lua_State *L, int nargs, int nresults, int errfunc, void *ud);

L是执行环境,可以理解为当前栈,nargs参数个数,nresults返回值个数。lua_pcall和该函数区别是多一个参数,用于发生错误处理时的代码返回。lua_cpcall则又多一个用于传递用户自定义的数据结构的指针。

lua_call的运行是无保护的,他与lua_pcall相似,但是在错误发生的时候她抛出错误而不是返回错误代码。当你在应用程序中写主流程的代码时,不应该使用 lua_call,因为你应该捕捉任何可能发生的错误。当你写一个函数的代码时,使用lua_call是比较好的想法,如果有错误发生,把错误留给关心她的人去处理。所以,写应用程序主流程代码用lua_pcall,写C Native Function代码时用lua_call。

示例1:

Lua 代码:

 a = f("how", t.x, 14)

C 代码:

 lua_getfield(L, LUA_GLOBALSINDEX, "f"); /* function to be called */
 lua_pushstring(L, "how");                        /* 1st argument */
 lua_getfield(L, LUA_GLOBALSINDEX, "t");   /* table to be indexed */
 lua_getfield(L, -1, "x");        /* push result of t.x (2nd arg) */
 lua_remove(L, -2);                  /* remove 't' from the stack */
 lua_pushinteger(L, 14);                          /* 3rd argument */
 lua_call(L, 3, 1);     /* call 'f' with 3 arguments and 1 result */
 lua_setfield(L, LUA_GLOBALSINDEX, "a");        /* set global 'a' */

在上面的例子除了描述了lua_call的使用外,还对lua_getfield的使用有一定的参考价值。特别是学习如何在一个表中获取他的值。

在上面的例子中,可能再调用lua_getfield时就会忘记调用lua_remove,当然这是我想象自己使用时会犯下的错。lua_getfield函数功能是从指定表中取出指定元素的值并压栈。上面获取t.x的值的过程就是先调用:

lua_getfield(L, LUA_GLOBALSINDEX, "t");

从全局表中获取t的值,然而t本身是一个表,现在栈顶的值是t表。于是再一次调用:

lua_getfield(L, -1, "x"); 

从t中取出x的值放到栈上,-1表示栈顶。那该函数执行完成后t的位置由-1就变成-2了,所以下面一句 lua_remove 索引的是-2,必须把t给remove掉,否则栈中就是4个参数了。上面的最后一句 lua_setfield 的目的是把返回值取回赋给全局变量a,因为在lua_call执行完成后,栈顶的就是返回值了

示例2:

//test.lua
function printmsg()  
    print("hello world")  
end
x = 10  

//test.c
#include <stdio.h>  
#include <unistd.h>  

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

int main(int argc, const char *argv[]) {  
    lua_State *L;  
    if(NULL == (L = luaL_newstate())) {  
        perror("luaL_newstate failed");  
        return -1;  
    }  
    luaL_openlibs(L);  
    if(luaL_loadfile(L, "./test.lua")) {  
        perror("loadfile failed");  
        return -1;  
    }  
    lua_pcall(L, 0, 0, 0);

    lua_getglobal(L, "printmsg");  
    lua_pcall(L, 0, 0, 0);  

    lua_close(L);     
    return 0;  
} 

上面的代码就是在test.c中调用test.lua的函数printmsg函数。

对于上面的C代码,我想大家都知道几个函数的大概作用:

  • luaL_newstate():创建一个新的Lua虚拟机
  • luaL_openlibs():打开一些必要的库,比如print等
  • luaL_loadfile():手册上写的是"This function uses lua_load to load the chunk in the filenamed filename." 而lua_load就是把编译过的chunk放在stack的顶部。理解chunk很重要,后面会具体讲到
  • lua_pcall:执行栈上的函数调用

一开始我一直认为既然 luaL_loadfile 执行以后,就可以直接用 lua_getglobal 获得test.lua中的函数,其实不然。手册中明确提到,lua_load把一个lua文件当作一个chunk编译后放到stack的栈顶,而什么是chunk呢?chunk就是一个可执行语句的组合,可以是一个文件也可以是一个string,“Lua handles a chunk as the body of an anonymous function with a variable number of arguments”这是Lua对chunk也就是lua文件的处理方式,就是认为是一个可变参数的匿名函数。也就是说,调用后栈上有一个匿名函数,这个函数的body就是文件中所有的内容。

在 luaL_loadfile 后,调用 lua_gettop 以及 lua_type 可以知道栈的大小为1,放在栈上的是一个 function 类型的value。为什么 loadfile 后我们不能直接获取到 printmsg 这个函数呢,那是因为刚才提到的,loadfile仅仅视编译lua文件,并不执行这个文件,也就是说只是在栈上形成了一个匿名函数。只有执行这个函数一次,才会使得printmsg可以通过 lua_getglobal 获取,否则,全局变量是空的。我在手册上看到这样一句话:Lua在执行函数的时候,函数会实例化,获得的 closure 也是这个函数的最终值。其实不管是函数,还是其他类型,如果不执行的话,它们只是被编译,并不能在进程的空间种获取到他们,感觉就像c的库一样,他们的编译文件.so已经存在,但是如果你不调用它,那么库中所有的变量不能被实例化,调用者也就无法访问。其实pringmsg和x本质是一样的,只是他们类型不同而已。

lua_getfield/lua_setfield

void lua_getfield (lua_State *L, int index, const char *k);

把值 t[k] 压入堆栈,t 是给定有效的索引 index 的值,和在 Lua 中一样,这个函数可能会触发元方法 index 事件。

void lua_setfield (lua_State *L, int index, const char *k);

相当于 t[k] = v,t 是给定的有效索引 index 的值,v 是堆栈顶部的值,这个函数会弹出这个值,和在 Lua 中一样,这个函数可能会触发 newindex 元方法事件。

lua_getglobal/lua_setglobal

lua_getglobal

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

把全局 name 的值压入栈顶,它被定义为宏(macro):

#define lua_getglobal(L,s)  lua_getfield(L, LUA_GLOBALSINDEX, s)  

lua_setglobal

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

从栈中弹出一个值并赋值给全局 name,它被定义成宏(macro):

#define lua_setglobal(L,s)   lua_setfield(L, LUA_GLOBALSINDEX, s)

lua_gettop/lua_settop/lua_pop

在任何时候,你都可以通过调用lua_gettop函数取得栈顶元素的索引:

int lua_gettop (lua_State *L);

因为索引从1开始计数,lua_gettop的返回值等于这个堆栈的元素个数(当堆栈为空时返回值为0)

void lua_settop (lua_State* L, int index );

lua_settop用于把堆栈的栈顶索引设置为指定的数值,它可以接受所有可接受索引。如果新的栈顶索引比原来的大,则新的位置用nil填充。如果index为0,则将删除堆栈中的所有元素。在lua.h中定义了如下一个宏:

#define lua_pop(L,n) lua_settop(L,-(n)-1)

用以把堆栈上部的n个元素删除。

lua_pushvalue/lua_insert/lua_remove/lua_replace

 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);

lua_pushvalue压入一个元素的值拷贝到指定的索引处,相反地,lua_remove删除给定索引的元素,并将之一索引之上的元素来填补空缺。同样地,lua_insert在上移给定索引之上的所有元素后再在指定位置插入新元素。Lua_replace将栈顶元素压入指定位置而不移动任何元素(因此指定位置的元素的值被替换)。这些函数都仅接受有效索引(你不应当使用假索引调用lua_remove或lua_insert,因为它不能解析为一个堆栈位置)。下面是一个例子,栈的初始状态为10 20 30 40 50 (从栈底到栈顶,“”标识为栈顶,有:

lua_pushvalue(L, 3)    --> 10 20 30 40 50 30*
lua_pushvalue(L, -1)   --> 10 20 30 40 50 30 30*
lua_remove(L, -3)      --> 10 20 30 40 30 30*
lua_remove(L,  6)      --> 10 20 30 40 30*
lua_insert(L,  1)      --> 30 10 20 30 40*
lua_insert(L, -1)      --> 30 10 20 30 40*  (没影响)
lua_replace(L, 2)      --> 30 40 20 30*
lua_settop(L, -3)      --> 30 40*
lua_settop(L,  6)      --> 30 40 nil nil nil nil*

lua_gettable/lua_settable

void lua_gettable (lua_State *L, int index);

把 t[k] 压入堆栈,t 是给出的有效的索引 index 的值,k 是栈顶的值,这个函数会从堆栈中弹出 key,并将结果值放到它的位置,和在 Lua 一样,函数可能会触发一个元方法 index 事件。

void lua_settable (lua_State *L, int index);

相当于 t[k]=v,t 是给出的有效的索引 index 的值,v 是堆栈顶部的值,k 是堆栈顶部下面的值。这个函数会从堆栈中弹出 key 和 value 的值,和在 Lua 中一样,函数可能会触发元方法 newindex 事件。

lua_concat

void lua_concat (lua_State *L, int n);

用来连接字符串,等价于Lua中的..操作符:自动将数字转换成字符串,如果有必要的时候还会自动调用metamethods。另外,她可以同时连接多个字符串。调用lua_concat(L,n)将连接(同时会出栈)栈顶的n个值,并将最终结果放到栈顶。

lua_type/lua_typename

int lua_type (lua_State *L, int index);

lua_type返回堆栈元素的值类型,当使用无效索引时返回LUA_TNONE(如当堆栈为空的时候),lua_type返回的类型代码为如下在lua.h中定义的常量:LUA_TNIL,LUA_TNUMBER,LUA_TBOOLEAN,LUA_TSTRING,LUA_TTABLE,LUA_TFUNCTION,LUA_USERDATA,LUA_TTHEARD,LUA_TLIGHTUSERDATA。下面的函数可以将这些常量转换为字符串:

const char* lua_typename (lua_State* L, int type);

lua_checkstack

当你使用Lua API的时候,你有责任控制堆栈溢出。函数

int lua_checkstack (lua_State *L, ine extra);

将把堆栈的尺寸扩大到可以容纳top+extra个元素;当不能扩大堆栈尺寸到这一尺寸时返回假。这一函数从不减小堆栈的尺寸;当前堆栈的尺寸大于新的尺寸时,它将保留原来的尺寸,并不变化。

lua_is***

int lua_isnumber(lua_State *L, int index);
int lua_isboolean(lua_State *L, int index);
int lua_isfunction(lua_State *L, int index);
int lua_istable(lua_State *L, int index);
int lua_isstring(lua_State *L, int index);
int lua_isnil(lua_State *L, int index);
int lua_iscfunction(lua_State *L, int index);

带lua_is*前辍的函数在当堆栈元素对象与给定的类型兼容时返回1,否则返回0。Lua_isboolean是个例外,它仅在元素类型为布尔型时成功(否则没有意思,因为任何值都可看作布尔型)。当使用无效索引时,它们总返回0。Lua_isnumber接受数字或者全部为数字的字符串;lua_isstring打接受字符串和数值,lua_isfunction接受lua函数和C函数;lua_isuserdata也可接受完全和轻量级两种userdata。如果想区分C函数和lua函数,可以使用lua_iscfunction函数;同样地,想区分完全和轻量级userdata可以使用lua_islightuserdata;区分数字和数字组成的字符串可以使用lua_type。

API函数中还有比较堆栈中的两个值 的大小的函数:

int lua_equal(lua_State *L, int index1, int index2);
int lua_rawequal(lua_State *L, int index1, int index2);
int lua_lessthan(lua_State *L, int index1, int index2);

lua_equal和lua_lessthan与相对应的lua操作符等价(参考2.5.2)。lua_rawequal直接判断两个值的原始值,而非通过调用元方法来比较。以上的函数当索引无效时返回0。

lua_to***

int lua_toboolean(lua_State *L, int index);
lua_CFunction lua_tocfunction(lua_State *L, int index);
lua_Integer lua_tointeger(lua_State *L, int index);
const char *lua_tolstring(lua_State *L, int index);
lua_Number lua_tonumber(lua_State *L, int index);
void *lua_topointer(lua_State *L, int index);
lua_State *lua_tothread(lua_State *L, int index);
const char *lua_tostring(lua_State *L, int index);

这些函数可通过任意可接受索引调用,如果用无效索引为参数,则和给定值并不匹配类型一样。 lua_toboolean转换指定索引lua值为C“布尔型”值(0或1)。当lua值仅为false或nil时返回0(如果你仅想接受一个真正的布尔值,可以先使用lua_isboolean去测试这个值的类型。

lua_tonumber转换指定索引的值为数字(lua_Number默认为double)。这一lua值必须数字或可转换为数字的字符串(参考2.2.1),否则lua_tonumber返回0。

lua_tostring将指定索引的值转换为字符串(const char*)。lua值必须为字符串或数字,否则返回NULL。当值为数字,lua_tostring将会把堆栈的原值转换为字符串(当lua_tostring应用到键值上时会使lua_next出现难以找出原因的错误)。lua_tostring返回一个完全对齐的字符串指针,这一字符串总是’/0’结尾(和C一样),但可能含有其它的0。如果你不知道一个字符串有多少个0,你可以使用lua_strlen取得真实长度。因为lua有垃圾收集机制,因此不保证返回的字符串指针在对应的值从堆栈中删除后仍然有效。如果你以后还要用到当前函数返回的字符串,你应当备份它或者将它放到registry中(参考3.18)。

lua_tofunction将堆栈中的值转换为C函数指针,这个值必须为C函数指针,否则返回NULL。数据类型lua_CFunction将在3.16节讲述。

lua_tothread转换堆栈中的值为lua线程(以lua_State*为表现形式),此值必须是一个线程,否则返回NULL。

lua_topointer转换堆栈中的值为通用C指针(void*)。这个值必须为userdata、表、线程或函数,否则返回NULL。lua保证同一类型的不同对象返回不同指针。没有直接方法将指针转换为原值,这一函数通常用以获取调试信息。

lua_push***

void lua_pushboolean(lua_State *L, int b);
void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);
void lua_pushcfunction(lua_State *L, lua_CFunction f);
const char *lua_pushfstring (lua_State *L, const char *fmt, ...);
void lua_pushinteger (lua_State *L, lua_Integer n);
void lua_pushliteral
void lua_pushlstring(lua_State *L, const char *s, size_t len);
void lua_pushnil(lua_State *L);
void lua_pushnumber(lua_State *L, lua_Number n);
void lua_pushstring(lua_State *L, const char *s);
const char *lua_pushvfstring (lua_State *L,
                          const char *fmt,
                          va_list argp);

这些函数接受一个C值,并将其转换为对应的lua值,然后将其压入堆栈。lua_pushlstring和lua_pushstring对给定的字符串生成一个可以互转的拷贝,这是个例外。lua_pushstring能压C字符串(即以0结尾并且内部没有0),否则建议使用更通用的lua_pushlstring,它能指定长度。

你同样可以压入“格式化”字符串:

const char *lua_pushfstring  (lua_State *L, const char *fmt, ...);
const char *lua_pushvfstring (lua_State *L, const char *fmt, va_list argp);

这两个函数向堆栈压入格式化字符串并返回指向字符串的指针。它们跟sprintf和vsprintf很象但有如下的重要不同:

  • 你不用申请内存去保存格式化结果,这结果是一个lua字符串并且lua自己会小心管理内存(并通过垃圾收集机制释放)。
  • 使用转义字符受限。它们没有标志量、宽度和精确度。转义字符能够是’%%’(插入一个”%”)、’%s’(插入一个以0结尾的字符串)、’%f’(插入一个lua_Number)、’%d’(插入一个int)和’%c’(插入一个用int表示的字符)。

lua_register

void lua_register (lua_State *L, const char *name, lua_CFunction f);

设置 C 函数 f 为新的全局变量 name 的值,它被定义为宏(macro):

#define lua_register(L,n,f)  (lua_pushcfunction(L, f), lua_setglobal(L, n))

完整示例

#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>

void
load(lua_State *L, const char *fname, int *w, int *h) {
    if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0 ,0)) {
        printf("Error Msg is %s.\n", lua_tostring(L, -1));
        return;
    }
    lua_getglobal(L, "width");  // #define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s))
    lua_getglobal(L, "height");
    if (!lua_isnumber(L, -2)) {
        printf("'width' should be a number\n");
        return;
    }
    if (!lua_isnumber(L, -1)) {
        printf("'height' should be a number\n", );
        return;
    }
    *w = lua_tointeger(L, -2);
    *h = lua_tointeger(L, -1);
}

int
main() {
    lua_State *L = luaL_newstate();
    int w, h;
    load(L, "D:/test.lua", &w, &h);
    printf("width = %d, height = %d\n", w, h);
    lua_close(L);
    return 0;
}