之前在《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数据:

 local tTest =
{
gdp = ,
info = "this is test about exchange table data!",
task = {, , , },
};

我们的脚本将调用一个程序封装好的c API(TestTable函数),然后将tTest作为参数,压入虚拟栈中,如下:

 local tRet = TestTable(tTest);

虽然tTest table已经传给了程序,我们还需要对TestTable这个c API进行定制,使它能够正确的理解这个table中的数据,实现代码如下(LuaTestTable函数类型是lua_CFuntion类型,注册到lua虚拟机中的函数名为TestTable):

 int LuaTestTable(lua_State* L)
{
printf("stack size = %d\n", lua_gettop(L)); //打印栈中元素的个数 lua_pushstring(L, "gdp"); //将gdp字符串压入栈顶
//根据栈顶的key获取table中的value,将key(这里的“gdp”)移除,再将value压入栈顶
lua_gettable(L, );
printf("%s\n", lua_tostring(L, -)); //取栈顶元素(注意这里的整型值都是string类型)
lua_pop(L, ); //取完之后清理栈顶
printf("stack size = %d\n", lua_gettop(L)); //打印栈中元素的个数 lua_pushstring(L, "info"); //同上
lua_gettable(L, );
printf("%s\n", lua_tostring(L, -));
lua_pop(L, );
printf("stack size = %d\n", lua_gettop(L)); lua_pushstring(L, "task"); //这里的value值是一个table哦,没关系栈操作都是一样的
lua_gettable(L, );
for (int i = ; i < ; ++i)
{
lua_pushnumber(L, i+);
lua_gettable(L, -);
printf("%s\n", lua_tostring(L, -));
lua_pop(L, );
}
lua_pop(L, );
printf("stack size = %d\n", lua_gettop(L)); //到这里tTest表依然在栈底,但不影响后面的操作。 //------华丽的分割线------------//
//到这里table数据的解析就结束了,以下内容是c API给lua返回table数据 lua_newtable(L);//要给lua脚本返回一个table类型,先要new一个,压入栈顶
lua_pushnumber(L, ); //将key先压入栈
lua_pushstring(L, "table2lua"); //再将value压入栈
lua_settable(L, -);//settable将操作-2,-1编号的键值对,设置到table中,并把key-value从栈中移除 lua_pushstring(L, "key"); //同上
lua_newtable(L); //这里有个子table
lua_pushstring(L, "capi");
//这里的value类型使用lua_CFunction类型,可用做c API调用,函数实现请参看附录1
lua_pushcfunction(L, LuaSayHello);
lua_settable(L, -);
lua_pushnumber(L, );
lua_pushnumber(L, );
lua_settable(L, -);
lua_settable(L, -); //这个从这里“lua_pushstring(L, "key"); //同上”开始匹配的
printf("stack size = %d\n", lua_gettop(L));
return ; //返回栈顶1个元素
}

需要说明的是在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在栈中的索引编号)。

  好了,基本介绍完了,最后来编写脚本,看看效果(以下是程序调用的脚本):

 --file: test.lua
local tTest = {
gdp = ,
info = "this is test about exchange table data!",
task = {, , , },
};
local tRet = TestTable(tTest);
printTable(tRet); //实现请参看附录2
tRet.key.capi(); //实现请参看附录1

TestTable成功解析tTest的数据,并且返回一个table类型的tRet。

 printTable(tRet);

printTable简单的实现了一个table打印的脚本将tRet打印输出。

 tRet.key.capi();

脚本调用tRet中返回的c API类型的函数。具体实现请参见附录。

运行结果:

 stack size =   //以下是LuaTestTable的输出

 stack size =
this is test about exchange table data!
stack size = stack size =
stack size = //以下是printTable的输出
{
[] = table2lua
[key] =
{
[] =
[capi] = function: 0x409795
}
}
Lua call c/c++:SayHello() //这里是 [capi] = function: 0x409795被调用的输出
Hello Everyone!

综上述,我们只需要修改tTest中的数据(结构不能改,改了需要修改LuaTestTable函数)就能改变UI界面的显示,成功的解决了UI界面的复用问题。(文中没有具体讲到如何UI截面相关的细节,请参照例子自行脑补。)

附录1:

 //注册到lua虚拟机中的c API函数
int LuaSayHello(lua_State* L)
{
printf("Lua call c/c++:SayHello()\n");
printf("Hello Everyone!\n");
return ;
}

附录2:

//脚本函数实现打印一个table
function printTable(t, n)
if "table" ~= type(t) then
return ;
end
n = n or ;
local str_space = "";
for i = , 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 + );
else
str_k_v = str_k_v..tostring(v);
print(str_k_v);
end
end
print(str_space.."}");
end

完整项目托管在github上:(支持vs2013和cmake编译)

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

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

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

    如何在docker和宿主机之间复制文件   最近在用Docker布署hadoop,要将文件上传到HDFS首先文件得在Docker容器中吧,网上提供的方法差不多有三种 1.用-v挂载主机数据卷到容器内  ...

  2. Android File Transfer Mac: 如何在 macOS 和 Android 系统之间移动数据

    三大 Mac OS X 系统 Android 文件传输软件 谷歌出品的 Android File Transfer 如何在 Mac 系统上使用 Android File Transfer Androi ...

  3. 如何在Mac和Windows PC之间无线共享文件

    有时候,我需要在Mac和PC之间无线共享文件.由于并非所有人都在使用macOS,因此无论是在办公室还是在家里,这种情况都会发生.尽管并非一帆风顺,但有一种无需任何第三方应用程序即可弥合差距的方法. 根 ...

  4. 不制作证书是否能加密SQLSERVER与客户端之间传输的数据?

    不制作证书是否能加密SQLSERVER与客户端之间传输的数据? 在做实验之前请先下载network monitor抓包工具 微软官网下载:http://www.microsoft.com/en-us/ ...

  5. Android消息机制之实现两个不同线程之间相互传递数据相互调用

    目的:实现两个不同线程之间相互传递数据相互调用方法. 线程一中定义mainHandler 并定义一个方法mainDecode 线程二中定义twoHandler 并定义一个方法twoEncode 实现当 ...

  6. cocos2d-x lua table数据存储

    cocos2d-x lua table数据存储 version: cocos2d-x 3.6 1. 将table转为json http://blog.csdn.net/songcf_faith/art ...

  7. 在Activity之间如何传递数据,请尽可能说出你所知道的传递数据的方法,并详细描述其实现过程。

    在Activity之间如何传递数据,请尽可能说出你所知道的传递数据的方法,并详细描述其实现过程. 答案:可以通过Intent对象.静态变量.剪切板和全局对象进行数据传递,具体的数据传递方法如下. 1. ...

  8. Unity 3D Framework Designing(5)——ViewModel之间如何共享数据

    对于客户端应用程序而言,单页应用程序(Single Page Application)是最常见的表现形式.有经验的开发人员往往会把一个View分解多个SubView.那么,如何在多个SubView之间 ...

  9. Unity应用架构设计(5)——ViewModel之间如何共享数据

    对于客户端应用程序而言,单页应用程序(Single Page Application)是最常见的表现形式.有经验的开发人员往往会把一个View分解多个SubView.那么,如何在多个SubView之间 ...

随机推荐

  1. centos 安装解压工作

    解压工具: yum install ark 编辑器: yum install gedit

  2. Linux配置ntp时间服务器(全)

    时间服务器作用: 大数据产生与处理系统是各种计算设备集群的,计算设备将统一.同步的标准时间用于记录各种事件发生时序, 如E-MAIL信息.文件创建和访问时间.数据库处理时间等. 大数据系统内不同计算设 ...

  3. TensorFlow数据读取

    TensorFlow高效读取数据的方法 TF Boys (TensorFlow Boys ) 养成记(二): TensorFlow 数据读取 Tensorflow从文件读取数据 极客学院-数据读取 十 ...

  4. Rsync的一般使用需求

    rsync 只同步指定类型的文件 需求: 同步某个目录下所有的图片(*.jpg),该目录下有很多其他的文件,但只想同步*.jpg的文件. rsync 有一个--exclude 可以排除指定文件,还有个 ...

  5. python抢火车票 短信通知

    # -*- coding: utf-8 -*- from splinter.browser import Browser from time import sleep import traceback ...

  6. PB的一些记录

    断点设置在函数内,发现返回值没有,需要取消函数内断点才正常 加密--采用矩阵乘法 行列式取+_1的矩阵与逆矩阵其元素都是整数,, 可以使用matlab来找到这些矩阵 A* I   =E       A ...

  7. zabbix教程

    zabbix官方文档:https://www.zabbix.com/documentation/current/zh/manual zabbix视频教程:https://www.bilibili.co ...

  8. delegate的Invoke和BeginInvoke方法

    C#中的控件和delegate委托方法都有Invoke和BeginInvoke方法,控件的这两个方法网上讲得很多, 这里就不多说了,下面讲一下delegate的Invoke和BeginInvoke方法 ...

  9. 使用CefSharp在.Net程序中嵌入Chrome浏览器(一)——简介

    有的时候,我们需要在程序中嵌入Web浏览器,其实.Net Framework中本身就提供了WebBrowser控件,本身这个是最简单易用的方案,但不知道是什么原因,这个控件在浏览网页的时候有些莫名的卡 ...

  10. hiho 第六周 01背包

    简单的01背包,没有报名,这周的没有权限提交 #include<iostream> #include<memory.h> using namespace std; #defin ...