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

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

时间: 2024-07-28 19:31:08

如何在Lua与C/C++之间实现table数据的交换的相关文章

如何在String和Byte[]对象之间进行转换

分析问题 字符串和字节数组之间的转换,事实上代表了现实世界信息和数字世界信息之间的转换,要理解其中的机制,需要先了解一下几个概念. 1.比特. 比特(bit)是指一个位,它可以说是计算机内物理保存的最近本单元.现在的计算机体系采用二进制逻辑,即一个基本单元可以保存两种数值:0和1.这是因为0.1机制可以用多种物理系统来表示,例如高电平和低电平.二极管的导通和关闭.磁场的正极和负极等.总之,一个比特就是一个二进制位. 2.字节. 一个字节(byte),在C#中是由8个比特来构成的.它的值可以有一个

lua 实现在字符之间插入指定字符

-- 下面的代码可以实现在字符之间插入指定字符(lua),给大家分享下,可能笨拙一些,不过解决了问题  function ui.string_insert(str,insertStr)      local len = #str;      local left = len;      local cnt = 0;      local arr={0,0xc0,0xe0,0xf0,0xf8,0xfc};      local indx = -left;      local newstr = "

如何在docker和宿主机之间复制文件

如何在docker和宿主机之间复制文件 最近在用Docker布署hadoop,要将文件上传到HDFS首先文件得在Docker容器中吧,网上提供的方法差不多有三种 1.用-v挂载主机数据卷到容器内 2.直接在主机上拷贝到容器物理存储系统 3.用输入输出符 具体方法这篇文章写的很详细:http://blog.csdn.net/yangzhenping/article/details/43667785 但是对这三种方法我都不太喜欢,无意间看到另位一种方法供大家参考: 从主机复制到容器sudo dock

Express框架与html之间如何进行数据传递

关于Node.js 的Express框架介绍,推荐看菜鸟教程的Express框架,很适合入门,这里不再赘述,这里主要讲一下Express框架与html之间如何进行数据传递 我采用的是JQuery的Ajax()向后台传参方式 (url传参) 一.首先先讲一下jQuery的Ajax()向后台传参(参考http://blog.csdn.net/ailo555/article/details/48859425) 1.Type属性为Get时: (1)第一种方法:(通过url传参) function Get

在nginx中使用lua直接访问mysql和memcaced达到数据接口的统一

安装nginx参见<nginx+lua+redis构建高并发应用> 让nginx 中的nginx_lua_module支持mysql 和memcache 下载 https://github.com/agentzh/lua-resty-memcached https://github.com/agentzh/lua-resty-mysql 对于访问接口的统一有很多的处理方式,这里介绍使用nginx lua 访问mysql并用memcache缓存起来. 配置如下: ... location /ge

activity与fragment之间的传递数据

首先activity之间的数据传递就是 用intent和intent+bundle intent 传递 Intent i= new Intent(MainActivity.this,TheAty.class); i.putExtra("date","Hello SWWWWWW"); startActivity(i); 接受数据 Intent i =getIntent(); tv=(TextView) findViewById(R.id.tv); //通过"

虚拟化Xen平台中,Dom0和DomU之间发送网络数据时各个部分所运行时间

12年研究Xen的时候,曾经写过很多报告,当时考虑到保密,不能随便发布.现在Xen已经被KVM干的快不行了,发出来供大家参考. 关于xennet_start_xmit <-dev_hard_start_xmit调用函数(notify_remote_via_irq <-xennet_start_xmit)之间时间(0.085313)很长的解释:前端放入请求后,需要根据notify来表明,是否需要向后端发送事件请求.如果后端正在处理请求队列,就不需要向后端发送事件通知(notify=false).

序列化和反序列化在浏览器和 Web 服务器之间传递的数据、加密解密

js中数组不能传递到后台,需进行json序列化: var data = new Array(); data.push({para1:name,para2:answer}); string data = JSON.stringify(data) //解析参数 JavaScriptSerializer js = new JavaScriptSerializer(); List<Dictionary<string, string>> answerList = js.Deserialize

Lua中的weak表——weak table

弱表(weak table)是一个很有意思的东西,像C++/Java等语言是没有的.弱表的定义是:A weak table is a table whose elements are weak references,元素为弱引用的表就叫弱表.有弱引用那么也就有强引用,有引用那么也就有非引用.我们先要厘这些基本概念:变量.值.类型.对象. (1)变量与值:Lua是一个dynamically typed language,也就是说在Lua中,变量没有类型,它可以是任何东西,而值有类型,所以Lua中没