lua與c之間交互詳解(二)


本篇主要講解下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從棧中獲取結果


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM