- 常用的 C API
- 基础概念
- states
- 堆栈与索引
- 接口解析
- lua_newstate
- lua_open/lua_close
- lua_load/lua_call/lua_pcall/lua_cpcall
- lua_getfield/lua_setfield
- lua_getglobal/lua_setglobal
- lua_gettop/lua_settop/lua_pop
- lua_pushvalue/lua_insert/lua_remove/lua_replace
- lua_gettable/lua_settable
- lua_concat
- lua_type/lua_typename
- lua_checkstack
- lua_is*
- lua_to*
- lua_push*
- lua_register
- 完整示例
- 导航
- 基础概念
常用的 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;
}
导航
- 目录
- 上一章:Table 数据结构
- 下一章:Lua 与 C/C++ 交互