如何在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编译)

转载请申明出处,如有任何疑问或建议指出,谢谢~

转载于:https://www.cnblogs.com/borey/p/5622896.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
详细描述Lua和C之间相互传递Table类型数据 /* ====================================================== */ // 遍历Lua传入的Table类型参数, 获取它的Key/Value, 其关键操作是 lua_next() // lua_next() 返回1表示读取成功,返回0表示已经没有数据可读了 // lua_next() 会使用栈顶元素作为Key去定位本次需要取出Table里面的那个值对 // 如果Key=nil, 那就表示本次取出的是第一个元素 // 它会先将当前的这个Key弹出,然后将本次取出的Key/Value压入栈, Value在栈顶 // 一个比较隐晦的处理就是, 我们不应直接使用lua_tostring(L, -2)来读取Key // 因为lua_tostring()在Key类型不是字符串时, 它会修改栈上的Key数据 // 这样, 下次调用lua_next()时, 就会因为Key被修改了而导致错误 // 为此,先调用lua_pushvalue(L, -2),将它Copy一份到栈顶,对这个Copy进行lua_tostring() // 读取Key,Value到C变量里面后,将Value和Copy弹出,留着Key在栈顶,给下次lua_next()用 // // 指令及栈图变化如下: (假如Table的栈下标是Index) // 0. 刚进入函数时 ...Table, ... <--- 这里栈顶 // 1. lua_pushnil(L) ...Table, ..., nil <--- 这里栈顶 // 2. lua_next(L, Index) ...Table, ..., Key, Value <--- 这里栈顶 // 3. lua_pushvalue(L, -2) ...Table, ..., Key, Value, KeyCopy <--- 这里栈顶 // 4. lua_pop(L, 2), ...Table, ..., Key <--- 这里栈顶 // ... 如此重复2,3,4 // N. lua_next(L, Index)返回0 ...Table, ... <--- 这里栈顶 /* ====================================================== */

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值