## 基礎概念
### [](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;
}
~~~