本篇主要講解下c如何調用Lua的,即c作為宿主語言,Lua為附加語言。c和Lua之間是通過Lua堆棧交互的,基本流程是:把元素入棧——從棧中彈出元素——處理——把結果入棧。關於Lua堆棧介紹以及Lua如何調用c參考其他兩篇。
1. 加載運行Lua腳本
通過luaL_newstate()創建一個狀態機L,c與Lua之間交互的api的第一個參數幾乎都是L,是因為可以創建多個狀態機,調用api需指定在哪個狀態機上操作。lua_close(L)關閉狀態機。
int main(int argc, char *argv[]){ lua_State *L = luaL_newstate(); //創建一個狀態機 luaL_openlibs(L); //打開所有lua標准庫 int ret = luaL_loadfile(L, "c2lua.lua"); //加載但不運行lua腳本 if(ret != LUA_OK){ const char *err = lua_tostring(L, -1); //加載失敗,會把錯誤信息壓入棧頂 printf("-------loadfile error = %s\n", err); lua_close(L); return 0; } ret = lua_pcall(L, 0, 0, 0); //保護模式調用棧頂函數 if(ret!=LUA_OK){ const char *err = lua_tostring(L, -1); //發生錯誤,會把唯一值(錯誤信息)壓入棧頂 printf("-------pcall error = %s\n", err); lua_close(L); return 0; } lua_close(L); return 0; }
luaL_loadfile(L, filename):加載指定的Lua腳本,加載成功,把一個編譯好的代碼塊作為Lua函數壓入棧頂。若加載失敗,會把錯誤信息壓入棧頂
lua_pcall(L,0,0,0),在保護模式下調用棧頂函數,稍后介紹調用Lua函數時說明幾個參數的作用,若運行發生錯誤,會把唯一值(錯誤信息)壓入棧頂。luaL_openlibs(L)打開所有標准庫,若不調用,則會因為找不到print函數而報錯。
-- c2lua.lua print("------c2lua------")
在c中加載運行Lua腳本的流程通常是,luaL_newstate、luaL_openlibs、luaL_loadfile、lua_pcall
2. 操作Lua中全局變量
lua_getglobal(L, name),獲取Lua腳本中命名為name的全局變量並壓棧,然后c通過棧獲取
void test_global(lua_State *L){ //讀取,重置,設置全局變量 lua_getglobal(L, "var"); //獲取全局變量var的值並壓入棧頂 int var = lua_tonumber(L, -1); printf("var = %d\n", var); lua_pushnumber(L, 10); lua_setglobal(L, "var"); //設置全局變量var為棧頂元素的值,即10 lua_pushstring(L, "c str"); lua_setglobal(L, "var2"); //設置全局變量var2為棧頂元素的值,即c str lua_getglobal(L, "f"); lua_pcall(L,0,0,0); }
//c2lua.lua var = 5 function f() print("global var = ", var, var2) end
lua_setglobal(L, name),彈出棧頂的值,並設置為全局變量name的新值(name可以在Lua中未定義)
3. 調用Lua中函數
通過lua_pcall這個api在保護模式下調用一個Lua函數
int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);
nargs是函數參數的個數,nresults是函數返回值的個數。約定:調用前需要依次把函數,nargs個參數(從左向右)壓棧(此時最后一個參數在棧頂位置),然后函數和所有參數都出棧,並調用指定的Lua函數。如果調用過程沒有發生錯誤,會把nresults個結果(從左向右)依次壓入棧中(此時最后一個結果在棧頂位置),並返回成功LUA_OK。如果發生錯誤,lua_pcall會捕獲它,把唯一返回值(錯誤信息)壓棧,然后返回特定的錯誤碼。此時,如果設置msgh不為0,則會指定棧上索引msgh指向的位置為錯誤處理函數,然后以錯誤信息作為參數調用該錯誤處理函數,最后把返回值作為錯誤信息壓棧。
void test_function(lua_State *L){ //調用lua函數 lua_getglobal(L, "f1"); lua_pcall(L, 0, 0, 0); //調用f1 lua_getglobal(L, "f2"); lua_pushnumber(L, 100); lua_pushnumber(L, 10); lua_pcall(L, 2, 2, 0); //調用f2 lua_getglobal(L, "f3"); char *str = "c"; lua_pushstring(L, str); lua_pcall(L,1,1,0); //調用f3 }
// c2lua.lua function f1() print("hello lua, I'm c!") end function f2(a, b) return a+b, a-b end function f3(str) return str .. "_lua" end
打印出棧的數據如下:棧頂是f3的返回值,接着是f2的2個返回值
4. 操作Lua中的table
對表的操作主要有查找t[k]、賦值t[k]=v以及遍歷表。
//c2lua.lua t = {1, 2, ["a"] = 3, ["b"] = {["c"] = 'd'}}
int lua_getfield (lua_State *L, int index, const char *k);
查找,把t[k]的值壓棧,t為棧上索引index指向的位置,跟Lua一樣該api可能觸發"index"事件對應的元方法,等價於lua_pushstring(L,const char*k)和lua_gettable(L, int index)兩步,所以通常用lua_getfield在表中查找某個值。
void lua_setfield (lua_State *L, int index, const char *k);
賦值,等價於t[k]=v,將棧頂的值(v)出棧,其中t為棧上索引index指向的位置,跟Lua一樣該api可能觸發“newindex”事件對應的元方法。需先調用lua_pushxxx(L,v)將v入棧,再調用lua_setfield賦值。
void test_table(lua_State *L){ // 讀取table lua_getglobal(L, "t"); lua_getfield(L, 1, "a"); //從索引為1的位置(table)獲取t.a,並壓棧 lua_getfield(L, 1, "b"); lua_getfield(L, -1, "c"); //從索引為-1的位置(棧頂)獲取t.c,並壓棧 // 修改table lua_settop(L, 1); //設置棧的位置為1,此時棧上只剩一個元素t lua_pushnumber(L, 10); lua_setfield(L, 1, "a"); //t.a=10 lua_pushstring(L, "hello c"); lua_setfield(L, 1, "e"); //t.e="hello c" dump_table(L, 1); //遍歷table number-number 1-1 // number-number 1-2 // string-number a-3 // string-string e-hello c // string-table b-table //新建一個table lua_settop(L, 0); //清空棧 lua_newtable(L); //創建一個table lua_pushboolean(L, 0); lua_setfield(L, 1, "new_a"); lua_pushboolean(L, 1); lua_setfield(L, 1, "new_b"); dump_table(L, 1); //遍歷table string-boolean new_a-false // string-boolean new_b-true }
注:lua_settop(L, int index)設置棧頂為index,大於index位置的元素都被移除,特別當index為0,即清空棧;如果原來的棧小於index,多余的位置用nil填充。
int lua_next (lua_State *L, int index);
通過lua_next遍歷表t,t為索引index指向的位置,從棧頂彈出key,將該key的下一個key-value對壓棧,此時key位於棧上-2位置,value位於-1位置。如果表中沒有更多元素,則返回0。
void dump_table(lua_State *L, int index){ if(lua_type(L, index)!=LUA_TTABLE) return;
// 典型的遍歷方法 lua_pushnil(L); //nil入棧,相當於從表的第一個位置遍歷 while(lua_next(L, index)!=0){ //沒有更多元素,lua_next返回0 //key-value入棧, key位於棧上-2處,value位於-1處 printf("%s-%s\n", lua_typename(L,lua_type(L,-2)), lua_typename(L,lua_type(L,-1))); lua_pop(L,1); //彈出一個元素,即把value出棧,此時棧頂為key,供下一次遍歷 } }
總之,c調用lua的流程通常是:c把需要的數據入棧——Lua從棧中取出數據——執行Lua腳本——Lua把結果入棧——c從棧中獲取結果