如何在Lua與C/C++之間實現table數據的交換


  之前在《C/C++和Lua是如何進行通信的?》一文中簡單的介紹了lua與宿主之間的通信。簡單的說兩種不同的語言之間數據類型不一樣又如何進行數據交換呢?那就是lua_State虛擬棧,通過棧操作和lua庫函數,我們很輕松就能完成兩者之間的數據交換。

  開始之前,明確幾個問題,lua中的虛擬棧的索引編號問題(我們假設棧大小為n),編號1是棧底,n視棧頂,編號-1是棧頂,-n是棧底。lua中的庫函數需要訪問和操作棧上的數據都是通過索引編號定位的。但是我們需要明確一點,有些API並沒有使用索引編號作為參數,意味着默認對棧頂進行操作。如lua_pushnumber(L, 66)將數值66壓入棧頂,lua_tonumber(L, -1)取編號-1(棧頂)元素等等,如果這些基本知識和API的都已經熟悉了,那么lua與宿主之間的數據交換就很容易理解了。

  姿勢准備好了,那么問題來了。需求:我們現在要設計一個UI界面,我們希望這個UI是可以重用的。為了滿足這個需求,顯然我們必須將UI界面與顯示數據分離。使用lua初始化數據后,將數據傳遞給UI界面然后顯示。這樣如果需求變更(游戲開發中經常產生這樣的需求),我們也只需改變lua腳本就能重用UI界面,聽上去真是程序猿的福音啊~~

  用於顯示UI的數據必定很多,需要使用lua中的table來封裝這些數據,現在給定如下lua table數據:

1 local tTest = 
2 { 3     gdp = 1234, 4     info = "this is test about exchange table data!", 5     task = {12, 23, 34, 45}, 6 };

我們的腳本將調用一個程序封裝好的c API(TestTable函數),然后將tTest作為參數,壓入虛擬棧中,如下:

1 local tRet = TestTable(tTest);

雖然tTest table已經傳給了程序,我們還需要對TestTable這個c API進行定制,使它能夠正確的理解這個table中的數據,實現代碼如下(LuaTestTable函數類型是lua_CFuntion類型,注冊到lua虛擬機中的函數名為TestTable):

 1 int LuaTestTable(lua_State* L)  2 {  3     printf("stack size = %d\n", lua_gettop(L)); //打印棧中元素的個數
 4 
 5     lua_pushstring(L, "gdp");            //將gdp字符串壓入棧頂  6     //根據棧頂的key獲取table中的value,將key(這里的“gdp”)移除,再將value壓入棧頂
 7     lua_gettable(L, 1);  8     printf("%s\n", lua_tostring(L, -1)); //取棧頂元素(注意這里的整型值都是string類型)
 9     lua_pop(L, 1); //取完之后清理棧頂
10     printf("stack size = %d\n", lua_gettop(L)); //打印棧中元素的個數
11 
12     lua_pushstring(L, "info");   //同上
13     lua_gettable(L, 1); 14     printf("%s\n", lua_tostring(L, -1)); 15     lua_pop(L, 1); 16     printf("stack size = %d\n", lua_gettop(L)); 17 
18     lua_pushstring(L, "task"); //這里的value值是一個table哦,沒關系棧操作都是一樣的
19     lua_gettable(L, 1); 20     for (int i = 0; i < 4; ++i) 21  { 22         lua_pushnumber(L, i+1); 23         lua_gettable(L, -2); 24         printf("%s\n", lua_tostring(L, -1)); 25         lua_pop(L, 1); 26  } 27     lua_pop(L, 1); 28     printf("stack size = %d\n", lua_gettop(L)); //到這里tTest表依然在棧底,但不影響后面的操作。 29     
30     //------華麗的分割線------------//
31     //到這里table數據的解析就結束了,以下內容是c API給lua返回table數據
32 
33     lua_newtable(L);//要給lua腳本返回一個table類型,先要new一個,壓入棧頂
34     lua_pushnumber(L, 1); //將key先壓入棧
35     lua_pushstring(L, "table2lua"); //再將value壓入棧
36     lua_settable(L, -3);//settable將操作-2,-1編號的鍵值對,設置到table中,並把key-value從棧中移除
37 
38     lua_pushstring(L, "key"); //同上
39     lua_newtable(L); //這里有個子table
40     lua_pushstring(L, "capi"); 41     //這里的value類型使用lua_CFunction類型,可用做c API調用,函數實現請參看附錄1
42  lua_pushcfunction(L, LuaSayHello); 43     lua_settable(L, -3); 44     lua_pushnumber(L, 2); 45     lua_pushnumber(L, 10086); 46     lua_settable(L, -3); 47     lua_settable(L, -3); //這個從這里“lua_pushstring(L, "key"); //同上”開始匹配的
48     printf("stack size = %d\n", lua_gettop(L)); 49     return 1; //返回棧頂1個元素
50 }

需要說明的是在lua中tTest["gdp"]和tTest.gdp的調用形式是一樣的,這是lua的語法糖。當然操作棧中的table方法除了lua_gettable和lua_settable還有其它方法,請參看lua_rawget和lua_rawset。理解棧中的元素變化是非常重要的。

  LuaTestTable函數API的后面部分介紹了構造一個任意table作為返回值,返回給lua腳本。首先使用lua_newtable庫函數新建一個table類型的數據,並壓入棧。然后將鍵值對key-value依次壓入棧,調用lua_settable(L, index)將key-value設置到table中,子table操作也是一樣的(這里的index指的是要設置的table在棧中的索引編號)。

  好了,基本介紹完了,最后來編寫腳本,看看效果(以下是程序調用的腳本):

1 --file: test.lua 2 local tTest = { 3     gdp = 1234, 4     info = "this is test about exchange table data!", 5     task = {12, 23, 34, 45}, 6 }; 7 local tRet = TestTable(tTest); 8 printTable(tRet);    //實現請參看附錄2
9 tRet.key.capi(); //實現請參看附錄1

TestTable成功解析tTest的數據,並且返回一個table類型的tRet。

1 printTable(tRet);

printTable簡單的實現了一個table打印的腳本將tRet打印輸出。

1 tRet.key.capi();

腳本調用tRet中返回的c API類型的函數。具體實現請參見附錄。

運行結果:

 1 stack size = 1  //以下是LuaTestTable的輸出
 2 1234
 3 stack size = 1
 4 this is test about exchange table data!
 5 stack size = 1
 6 12
 7 23
 8 34
 9 45
10 stack size = 1   
11 stack size = 2   //以下是printTable的輸出
12 { 13   [1] = table2lua 14   [key] = 
15  { 16     [2] = 10086
17     [capi] = function: 0x409795
18  } 19 } 20 Lua call c/c++:SayHello() //這里是 [capi] = function: 0x409795被調用的輸出
21 Hello Everyone!

綜上述,我們只需要修改tTest中的數據(結構不能改,改了需要修改LuaTestTable函數)就能改變UI界面的顯示,成功的解決了UI界面的復用問題。(文中沒有具體講到如何UI截面相關的細節,請參照例子自行腦補。)

附錄1:

1 //注冊到lua虛擬機中的c API函數
2 int LuaSayHello(lua_State* L) 3 { 4     printf("Lua call c/c++:SayHello()\n"); 5     printf("Hello Everyone!\n"); 6     return 0; 7 }

附錄2:

//腳本函數實現打印一個table
function printTable(t, n) if "table" ~= type(t) then return 0; end n = n or 0; local str_space = ""; for i = 1, n do str_space = str_space.."  "; end print(str_space.."{"); for k, v in pairs(t) do local str_k_v = str_space.." ["..tostring(k).."] = "; if "table" == type(v) then print(str_k_v); printTable(v, n + 1); else str_k_v = str_k_v..tostring(v); print(str_k_v); end end print(str_space.."}"); end

完整項目托管在github上:(支持vs2013和cmake編譯)

轉載請申明出處,如有任何疑問或建議指出,謝謝~


免責聲明!

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



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