之前在《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. typescript里面调用javasript

    index.html 里面加入函数: function tellYou() { egret.log("tell you."); javascript:android.funA(); ...

  2. 对hadoop namenode -format执行过程的探究

      引言 本文出于一个疑问:hadoop namenode -format到底在我的linux系统里面做了些什么? 步骤 第1个文件bin/hadoop Hadoop脚本位于hadoop根目录下的bi ...

  3. ubuntu,day1基础命令,shutdown,man,touch,rm,mv,cp,stat,locale,apt,date,tzselect,cal,快捷方式,echo,查看文件

    基本设置命令 1,shutdown 命令, shutdown -r now # 现在立即重启 shutdown -r + # 三分钟后重启 shutdown -r : #在12:12时将重启计算机 s ...

  4. Maven学习 二 Maven环境搭建

    第一步:下载Maven并解压 注意选择镜像地址,选择国内的会快点 解压目录 Maven目录分析 bin:含有mvn运行的脚本 boot:含有plexus-classworlds类加载器框架 conf: ...

  5. 2019.02.12 bzoj5294: [Bjoi2018]二进制(线段树)

    传送门 题意简述: 给出一个长度为nnn的二进制串. 你需要支持如下操作: 修改每个位置:1变0,0变1 询问对于一个区间的子二进制串有多少满足重排之后转回十进制值为333的倍数(允许前导000). ...

  6. usb协议栈学习笔记

    1.usb 集线器为什么一般都是只有4个扩展口? PC的根集线器可为每个A型连接器提供5V.500mA电源.一个总线供电的外部集线器可为每个端口提供100mA电流.由于USB为为外部集线器电路分配10 ...

  7. STM32F1xx寄存器版库

    文件下载链接 https://files.cnblogs.com/files/listenscience/STM32F1xx%28MDK5%292018.10.20.rar

  8. 【python-strip】Python strip()方法

    strip()方法用于移除字符串首尾的指定字符(默认是空格) 比如: str = "0000000this is string example....wow!!!0000000"; ...

  9. windows下安装QT并与visual studio 2017搭建开发环境

    1.环境搭建 这里并不是说qt必须要和visual studio结合使用,不过用习惯了visual studio开发,继续使用可节省开发时间,并大大提供便利性. 关于安装过程这里不再详细赘述,软件下载 ...

  10. scala字符串前加s使用$

    https://my.oschina.net/u/2000675/blog/1592140 字符串中的变量替换,Scala中基础的字符串插值就是在字符串前加字幕‘s’,然后在字符串中放入变量,每个变量 ...