多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
## 基础概念 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#states)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所占的资源,以避免其过于庞大。 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#堆栈与索引)堆栈与索引 Lua使用虚拟堆栈机制和C程序互相传值,所有的堆栈中的元素都可以看作一个Lua值(如nil, number, string等)。 当Lua调用C函数时,被调用的C函数将得到一个新的堆栈。这一堆栈与之前调用此函数的堆栈无关,也有其它C函数的堆栈无关。这一新的堆栈用调用C函数要用到的参数初始化,同时,这一堆栈也被用以返回函数调用结果。 为了便于操作,在API的中大量操作都并不依从堆栈只能操作栈顶元素的严格规则。而通过索引引用堆栈的任一元素。一个正整数索引可以看作某一元素在堆栈中的绝对位置(从1开始计数),一个负整数索引可以看作某一元素相对于栈顶的偏移量。 特别地,如果堆栈中有n个元素,那么索引1指向第一个元素(即第一个压入栈的元素)索引n指向最后一个元素;反过来,索引-1指向最后一个元素(即栈顶元素)索引-n指向第一个元素。当一个索引大于1并小于n时我们称其为一个有效索引(即1 <= abs(index) <= top)。 ## [](https://github.com/andycai/luaprimer/blob/master/07.md#接口解析)接口解析 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_newstate)lua_newstate ~~~ lua_State *lua_newstate (lua_Alloc f, void *ud); ~~~ 创建一个新的独立 state,不能创建返回 NULL。形参 f 是 allocator 函数,Lua 通过这个函数来为这个 state 分配内存。第二个形参 ud,是一个透明指针,每次调用时,Lua简单地传给 allocator 函数。 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_openlua_close)lua_open/lua_close lua_open 被 lua_newstate 替换,可以使用luaL_newstate从标准库中创建一个标准配置的 state,如: lua_State *L = luaL_newstate(); 。 ~~~ void lua_close (lua_State *L); ~~~ 销毁指定的 state 中所有的对象,并释放指定的 state 中使用的所有动态内存。 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_loadlua_calllua_pcalllua_cpcall)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本质是一样的,只是他们类型不同而已。 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_getfieldlua_setfield)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 元方法事件。 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_getgloballua_setglobal)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) ~~~ ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_gettoplua_settoplua_pop)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个元素删除。 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_pushvaluelua_insertlua_removelua_replace)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* ~~~ ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_gettablelua_settable)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 事件。 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_concat)lua_concat ~~~ void lua_concat (lua_State *L, int n); ~~~ 用来连接字符串,等价于Lua中的..操作符:自动将数字转换成字符串,如果有必要的时候还会自动调用metamethods。另外,她可以同时连接多个字符串。调用lua_concat(L,n)将连接(同时会出栈)栈顶的n个值,并将最终结果放到栈顶。 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_typelua_typename)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); ~~~ ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_checkstack)lua_checkstack 当你使用Lua API的时候,你有责任控制堆栈溢出。函数 ~~~ int lua_checkstack (lua_State *L, ine extra); ~~~ 将把堆栈的尺寸扩大到可以容纳top+extra个元素;当不能扩大堆栈尺寸到这一尺寸时返回假。这一函数从不减小堆栈的尺寸;当前堆栈的尺寸大于新的尺寸时,它将保留原来的尺寸,并不变化。 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_is)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。 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_to)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保证同一类型的不同对象返回不同指针。没有直接方法将指针转换为原值,这一函数通常用以获取调试信息。 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_push)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表示的字符)。 ### [](https://github.com/andycai/luaprimer/blob/master/07.md#lua_register)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)) ~~~ ### [](https://github.com/andycai/luaprimer/blob/master/07.md#完整示例)完整示例 ~~~ #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; } ~~~