Lua是一個嵌入式的語言,它不只能夠是一個獨立運行的程序,也能夠是一個用來嵌入其它應用的程序庫。html
C API是一個C代碼與Lua進行交互的函數集,它由如下幾部分構成:程序員
一、 讀寫Lua全局變量的函數;數組
二、 調用Lua函數的函數;閉包
三、 運行Lua代碼片斷的函數;函數
四、 註冊C函數後能夠在Lua中被調用的函數;ui
在C和LUA之間交互的關鍵在於一個虛擬棧(virtual stack),數據交互經過棧進行。操做數據時,首先將數據拷貝到棧上,而後獲取數據,棧中的每一個數據經過索引值進行定位,索引值爲正時表示相對於棧底的偏移索引,索引值爲負時表示相對於棧頂的偏移索引。索引值以1或 -1起始值,所以棧頂索引值永遠爲-1, 棧底索引值永遠爲1 。 "棧"至關於數據在Lua和C之間的中轉地,每種數據都有相應的存取接口 。lua
另外,還可使用棧來保存臨時變量。棧的使用解決了C和LUA之間兩個不協調的問題:spa
一、 Lua使用自動垃圾收集機制,而C要求顯式的分配和釋放內存;操作系統
二、 Lua使用動態數據類型,而C使用靜態類型;線程
特別注意的是:
一、每當Lua調用C函數時,C函數會使用一個局部棧,這個局部棧與以前的棧,以及其它正在調用的C函數使用的棧都是相互獨立的。Lua和C就使用這個局部的棧進行數據交互。
二、當Lua調用C時,棧至少包含LUA_MINSTACK(20)個位置,程序員也可使用lua_checkstack函數來增長棧的大小。
三、使用僞索引(Pseudo-Indices)來表示一些不在棧中的數據,好比thread環境、C函數環境、registry、C閉包的upvalues。
關於Registry:
在C裏面若是函數要保存持久狀態,只能依靠全局或static變量,但這樣C API庫就沒法爲多個LuaState狀態同時提供服務(就相似於帶有static變量的C函數是不可重入的)。爲此Lua提供了一個名爲Registry的預約義table,容許C API往Registry裏面存儲數據。
關於References:
Reference其實就是在一個指定的表t中保存一次lua的數據對象,Refenence自己其實就是表t的索引子,簡稱RefIndex,當RefIndex做爲Refenence時,t[RefIndex]其實就是用戶要求引用的lua數據對象。當RefIndex被luaL_unref()回收時,t每個被回收的RefIndex構成一個單向鏈表: t[Refindex] = Refindex0, t[Refindex0] = Refindex1, t[Refindex1] = Refindex2 ... t[0] = Refindex。
注意,t[0]始終是指向了空閒鏈表的頭部。每次調用luaL_ref()且回收鏈表爲空時,都會產生一個新的Reference,每次調用luaL_unref()都會銷燬一個指定的Reference存入空閒鏈表中。
類型聲明
typedef double lua_Number;
typedef ptrdiff_t lua_Integer;
初始化lua狀態機
lua_State* lua_open();
lua_State *lua_newstate (lua_Alloc f, void *ud);
lua_newstate 建立一個新的、獨立的Lua狀態機,若是由於內存不足致使建立失敗,返回NULL。參數f 指定內存分配函數,參數ud是傳給f 函數的指針。
lua_open 沒有指定內存分配函數的功能,不建議再使用。
注意:lua_State表示的一個Lua程序的執行狀態,它表明一個新的線程(注意是指Lua中的thread類型,不是指操做系統中的線程),每一個thread擁有獨立的數據棧以及函數調用鏈,還有獨立的調試鉤子和錯誤處理方法。
銷燬lua狀態機
void lua_close(lua_State *L);
銷燬Lua狀態機的全部對象,回收分配的內存。
加載lua庫
void luaL_openlibs(lua_State *L);
void luaopen_base(lua_State *L);
void luaopen_package(lua_State *L);
void luaopen_string(lua_State *L);
void luaopen_io(lua_State *L);
void luaopen_table(lua_State *L);
void luaopen_math(lua_State *L);
luaL_openlibs 在給定的Lua狀態機中打開全部的標準Lua庫;
編譯/加載 lua代碼
int luaL_dofile(lua_State *L, char *lua_script);
int luaL_dostring (lua_State *L, const char *str);
int lua_load (lua_State *L, lua_Reader reader, void *data,const char *chunkname);
int luaL_loadbuffer (lua_State *L, const char *buff, size_t sz, const char *name);
int luaL_loadfile (lua_State *L, const char *filename);
int luaL_loadstring (lua_State *L, const char *s);
luaL_dofile 加載並執行給定lua文件,成功返回0,錯誤返回1;
luaL_dostring 加載並執行給定string,成功返回0,錯誤返回1;
lua_load 加載一段chunk(但並不執行它),並將編譯後的代碼做爲一個函數壓入堆棧,若是發生錯誤,則將錯誤消息壓入堆棧;
luaL_loadbuffer 從一個buffer中加載chunk;
luaL_loadfile從文件加載chunk;
luaL_loadstring從字符串加載chunk;
函數參數檢查
void luaL_checkany (lua_State *L, int narg);
int luaL_checkint (lua_State *L, int narg);
lua_Integer luaL_checkinteger (lua_State *L, int narg);
long luaL_checklong (lua_State *L, int narg);
const char *luaL_checklstring (lua_State *L, int narg, size_t *l);
lua_Number luaL_checknumber (lua_State *L, int narg);
int luaL_checkoption (lua_State *L, int narg, const char *def, const char *const lst[]);
const char *luaL_checkstring (lua_State *L, int narg);
void luaL_checktype (lua_State *L, int narg, int t);
void *luaL_checkudata (lua_State *L, int narg, const char *tname);
luaL_checkany 檢查函數有多少個(由narg指定)參數;
luaL_checktype 檢查函數第narg個參數是否爲指定類型;
luaL_checkudata 檢查函數第narg個參數是否爲name類型的userdata;
luaL_checkint/luaL_checkinterger 等接口很是相似:
以luaL_checkint爲例,它是檢查函數的第narg個參數類型是否number型,而且返回這個number型參數;
luaL_checkoption 檢查函數第narg個參數是否位字符串類型,而且在lst[](字符串數組)中搜索這個字符串,最後返回匹配的數組下標,如未能匹配,引起一個錯誤。若是參數def非空,當narg參數不存在或爲nil時,就是要def做爲搜索串。這個函數的做用是將字符串映射爲C的enum類型。
table操做
void lua_createtable (lua_State *L, int narr, int nrec);
void lua_newtable (lua_State *L);
void lua_settable (lua_State *L, int index);
void lua_gettable (lua_State *L, int index);
lua_createtable 建立一個空table並壓入堆棧,它會爲narr個數組風格元素,和nrec個記錄風格元素預分配內存空間。
lua_newtable 建立一個空table,並壓入stack,等價於 lua_createtable(L, 0, 0);
lua_settable 至關於t[k]=v 操做,其中值t由參數index指定,v是棧頂,k是棧頂下一個元素;這個函數會將key和value都彈出棧;
lua_gettable 將t[k]壓入堆棧,由參數index指定操做的table,k是棧頂元素。這個函數會彈出棧頂的key,並由t[k]代替;
metatable操做
int luaL_newmetatable (lua_State *L, const char *tname);
void luaL_getmetatable (lua_State *L, const char *tname);
int lua_getmetatable (lua_State *L, int index);
int lua_setmetatable (lua_State *L, int index);
int luaL_getmetafield (lua_State *L, int obj, const char *e);
luaL_newmetatable 在Registry中建立一個key爲tname的metatable,並返回1;若是Registry 已經有tname這個key,則返回0;這兩種狀況都會將metatable壓入堆棧;
luaL_getmetatable 將Registry中key爲tname的metatable壓入堆棧;
luaL_getmetatable 將index處的元表壓入堆棧;
lua_setmetatable 彈出棧頂,並將它做爲由index指定元素的元表;
luaL_getmetafield 將索引obj處的元素的元表的e字段壓入堆棧;
stack操做
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_pushlightuserdata (lua_State *L, void *p);
void lua_pushliteral (lua_State *L, const char *s);
void lua_pushlstring (lua_State *L, const char *s, size_t len);
void lua_pushnil (lua_State *L);
void lua_pushstring (lua_State *L, const char *s);
lua_pushfstring 將一個格式化字符串壓入堆棧,並返回指向這個字符串的指針;
lua_pushlstring 將一個指定大小的字符串壓入堆棧;
lua_pushvalue 將棧中指定索引的元素複製一份到棧頂;
值得注意的是,向棧中壓入一個元素時,應該確保棧中具備足夠的空間,能夠調用lua_checkstack來檢測是否有足夠的空間。
實質上這些API是把C語言裏面的值封裝成Lua類型的值壓入棧中的,對於那些須要垃圾回收的元素(如string、full userdata),在壓入棧時,都會在Lua(也就是Lua虛擬機中)生成一個副本,今後不會再依賴於原來的C值。例如lua_pushstring 向棧中壓入一個以'\0'結尾的字符串,在C中調用這個函數後,能夠任意修改或釋放這個字符串,也不會出現問題。
void lua_pop(lua_State *L, int n);
int lua_gettop (lua_State *L);
void lua_concat (lua_State *L, int n);
void lua_getfield (lua_State *L, int index, const char *k);
void lua_setfield (lua_State *L, int index, const char *k);
void lua_getglobal(lua_State *L, char *name);
void lua_setglobal (lua_State *L, const char *name);
void lua_insert (lua_State *L, int index);
void lua_remove (lua_State *L, int index);
void lua_replace (lua_State *L, int index);
int lua_next (lua_State *L, int index);
size_t lua_objlen (lua_State *L, int index);
void luaL_checkstack (lua_State *L, int sz, const char *msg);
lua_pop 從棧中彈出n個元素;
lua_gettop 返回棧頂元素的索引(也即元素個數);
lua_concat 將棧頂開始的n個元素鏈接起來,並將它們出棧,而後將結果入棧;
lua_getfield 將t[k]壓入堆棧,t由參數index指定在棧中的位置;
lua_setfield 至關於t[k]=v,t由參數index指定在棧中的位置,v是棧頂元素,改函數會將棧頂的value出棧;
lua_getglobal(L,s) 等價於 lua_getfield(L, LUA_GLOBALSINDEX, s),注意:棧中LUA_GLOBALSINDEX索引位置處是當前Lua狀態機的全局變量環境。
lua_setglobal(L,s) 等價於 lua_setfield(L, LUA_GLOBALSINDEX, s);
lua_insert 移動棧頂元素到index指定的位置;
lua_remove 移除index處的元素,index之上的元素均下移一個位置;
lua_replace 將棧頂元素移到指定位置,並取代原來的元素,原先的棧頂元素彈出;
lua_next 彈出一個key,而後將t[key]入棧,t是參數index處的table;在利用lua_next遍歷棧中的table時,對key使用lua_tolstring尤爲須要注意,除非知道key都是string類型。
lua_objlen 返回index處元素的長度,對string,返回字符串長度;對table,返回"#"運算符的結果;對userdata,返回內存大小;其它類型返回0;
luaL_checkstack 增長棧大小(新增sz個元素的空間),若是grow失敗,引起一個錯誤,msg參數傳遞錯誤消息。
int lua_isboolean (lua_State *L, int index);
int lua_iscfunction (lua_State *L, int index);
int lua_isfunction (lua_State *L, int index);
int lua_islightuserdata (lua_State *L, int index);
int lua_isnil (lua_State *L, int index);
int lua_isnone (lua_State *L, int index);
int lua_isnoneornil (lua_State *L, int index);
int lua_isnumber (lua_State *L, int index);
int lua_isstring (lua_State *L, int index);
int lua_istable (lua_State *L, int index);
int lua_isthread (lua_State *L, int index);
int lua_isuserdata (lua_State *L, int index);
這些都是棧中指定元素類型檢查的接口;
函數調用
void lua_call (lua_State *L, int nargs, int nresults);
int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);
int lua_cpcall (lua_State *L, lua_CFunction func, void *ud);
lua_call 調用函數,參數nargs指定函數參數個數,參數nresults指定返回值個數。首先,被調函數必須在棧中;其次,函數參數必須是按從左往右的順序入棧的;函數調用時,全部函數參數都會彈出堆棧。函數返回時,其返回值入棧(第一個返回最最早入棧)。
lua_pcall 以保護模式調用函數,若是發生錯誤,捕捉它,並將錯誤消息壓入棧,而後返回錯誤碼。
lua_cpcall 以保護模式調用C函數func,參數ud指針指向一個用戶自定義數據。
錯誤處理
int luaL_error (lua_State *L, const char *fmt, ...);
引起一個錯誤。
類型轉換
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, size_t *len);
lua_Number lua_tonumber (lua_State *L, int index);
const void *lua_topointer (lua_State *L, int index);
const char *lua_tostring (lua_State *L, int index);
lua_State *lua_tothread (lua_State *L, int index);
void *lua_touserdata (lua_State *L, int index);
lua_toboolean 將給定index索引處的元素轉換爲bool類型(0或1);
lua_tocfunction 將給定index索引處的元素轉換爲C函數;
lua_tointeger 將給定index索引處的元素轉換爲int類型;
lua_tolstring 將給定index索引處的元素轉換爲char*類型,若是len不爲空,同時還設置len爲字符串長度;該函數返回的指針,指向的是Lua虛擬機內部的字符串,這個字符串是以'\0'結尾的,但字符串中間也可能包含值爲0的字符。
lua_tostring 等價於參數len=NULL時的lua_tolstring;
lua_tonumber 將給定index索引處的元素轉換爲double類型;
lua_topointer 將給定index索引處的元素轉換爲void*類型;
lua_tothread 將給定index索引處的元素轉換爲lua_State*類型(即一個thread);
lua_touserdata 返回給定index索引處的userdata對應的內存地址;
thread 操做
lua_State *lua_newthread (lua_State *L);
int lua_resume (lua_State *L, int narg);
int lua_yield (lua_State *L, int nresults);
lua_newthread 建立一個新的thread,而後壓入堆棧,並返回一個lua_State*指針表示建立的新thread。
新建立的thread與當前thread共享一個全局環境。沒有銷燬thread的顯式調用,它由垃圾收集器負責回收。
一個簡單的例子:
// test.c #include <stdio.h> #include "lua.h" #include "lualib.h" #include "lauxlib.h" /*the lua interpreter*/ lua_State* L; int luaadd(int x, int y) { int sum; lua_getglobal(L,"add"); lua_pushnumber(L, x); lua_pushnumber(L, y); lua_call(L, 2, 1); sum = (int)lua_tonumber(L, -1); lua_pop(L,1); return sum; } int main(int argc, char *argv[]) { int sum; L = lua_open(); luaL_openlibs(L); luaL_dofile(L, "add.lua"); sum = luaadd(10, 15); printf("The sum is %d \n",sum); lua_close(L); return 0; }
注意:在C代碼裏面咱們要引入三個頭文件lua.h,lauxlib.h和lualib.h:
lua.h中定義的是最基礎的API;
lauxlib.h中的函數都以luaL_開頭,他們是比基礎API更抽象的函數;
lualib.h中定義了打開標準類庫的API,好比luaL_openlibs(L)。
程序開始用luaL_open()函數建立一個lua_State。lua_State中保存了Lua運行時的全部的狀態信息(好比變量的值等),而且全部的Lua的C的API都有一個lua_newstate指針的參數。luaL_open函數會建立一個全新的Lua運行時狀態,其中沒有任何預先定義好的函數(包括最基本的print函數)。若是須要試用標準類庫的話,只要調用luaL_openlibs(L)函數就打開標準類庫就能夠了。標準類庫被分別封裝在不一樣的包中,當你須要使用的時候再引入到代碼中,這樣作的好處是可使Lua儘量的小(嵌入式語言必需要小),從而能夠方便嵌入到其餘語言中去。當Lua運行時狀態和標準類庫都準備完成後,就能夠調用luaL_dofile(L,"test.lua")函數來執行Lua腳本。運行結束後,須要調用lua_close(L)來關閉Lua運行時狀態。
被調用的test.lua文件:
-- test.lua function add(x,y) return x + y end
編譯命令,實際命令須要根據本身的lua環境調整
gcc test.c -o test -llua-5.1 -I /usr/local/include/
執行./test命令的輸出:
The sum is 25
另一個操做table的例子:
int main()
{ lua_State *L = luaL_newstate(); luaL_openlibs(L); lua_newtable(L); lua_pushstring(L, "i am key"); lua_pushstring(L, "i am value"); lua_settable(L, -3); lua_pushstring(L, "i am key"); lua_gettable(L, -2); const char *str = lua_tostring(L, -1); printf("%s", str); lua_close(L); return 0; }
當Lua調用C函數時,也使用了一個與C語言調用Lua時相同的棧。C函數從棧中獲取函數參數,並將結果壓入棧中。爲了在棧中將函數結果與其餘值區分開,C函數還應返回其壓入棧中的結果個數。
棧不是一個全局性的結構,每一個函數都有本身的局部私有棧。當Lua調用一個C函數時,第一個參數老是這個局部棧的索引1。
對於可被Lua調用的C函數而言,其接口必須遵循Lua要求的形式,即
typedef int (*lua_CFunction)(lua_State* L);
接收一個參數Lua_State*,即Lua的狀態,返回值表示壓入棧中的結果個數。
把要調用的C 函數註冊到lua狀態機中:
void lua_register (lua_State *L, const char *name, lua_CFunction f);
lua_register 是一個宏:#define lua_register(L,n,f) (lua_pushcfunction(L, f), lua_setglobal(L, n))
其中,參數name是lua中的函數名,f 是C中的函數。
從宏定義能夠看出,這個函數的做用是把C函數壓入堆棧,並在全局環境中設置Lua函數名;
Lua在require模塊的時候,除了搜索 "*.lua" 文件,也會搜索 "*.so" 文件,也就是說,Lua支持加載C/C++語言編譯的動態庫文件。
// foo.c #include "lua.h" #include "lualib.h" #include "lauxlib.h" static int add(lua_State *L) { int n = lua_gettop(L); /* number of arguments */ lua_Number sum = 0; int i; for (i = 1; i <= n; i++) { if (!lua_isnumber(L, i)) { lua_pushstring(L, "incorrect argument"); lua_error(L); } sum += lua_tonumber(L, i); } lua_pushnumber(L, sum/n); /* first result */ lua_pushnumber(L, sum); /* second result */ return 2; /* number of results */ } int luaopen_foo(lua_State *L) { lua_register(L, "add", add); return 1; }
注意:luaopen_MODULE 函數的後綴是有規則的,必須是模塊名稱,而lua_register的第二個參數是供Lua代碼調用的函數名稱,第三個參數是當前C函數;
OK,如今把C代碼編譯成動態庫:
gcc foo.c -shared -fPIC -o foo.so -llua-5.1 -I /usr/local/include/
而後在lua代碼裏面能夠加載該模塊:
require("foo")
這條命令會自動加載foo.so庫,並調用其中的 luaopen_foo 函數,而後執行裏面的函數註冊代碼,這樣接下來就能直接使用那些註冊到Lua狀態機裏面的C函數了。
print(add(14,25,15))
輸出結果:
18 54
參考文檔: