tolua++实现lua层调用c++技术分析

时间:2023-02-17 13:29:33

tolua++技术分析 cocos2dx+lua

前言

一直都使用 cocos2dx + lua 进行游戏开发,用 Lua 开发可以专注于游戏逻辑的实现,另外一方面可以实现热更新;而且 lua 是一个轻量级的脚本语言,库小但是功能齐全,所以在业内非常受欢迎。之前看了网上很多关于 c/c++ 如何与 lua 互调的讲解,也查看了 lua 官网的 lua api 和 c api,感觉大有收获。最近这一段时间研究了 tolua++ 里面 lua 层调用 c/c++ 的技术实现,准备记录一下学习心得,这样可以让自己对 tolua++ 工作机制理解的更加流畅,也希望自己对原理和 api 的分析能够对其他人有帮助!


tolua++需要将 c/c++ 中的类型,变量,函数,对象导出到lua

  1. 通过 tolua_reg_types(lua_State* tolua_S) 将类型导出,作用是为每一个需要导出到 lua 中的 c++ 类型创建元表,比如 CCNode 这种类型,就会在注册表中创建一个元表 CCNode_mt。( 之后会用 _R 代表注册表 , _G 代表全局表 , type_mt 代表类型为type的元表。 )

  2. 通过 tolua_cclass (lua_State* L, const char* lname, const char* name, const char* base, lua_CFunction col) 把基类设为子类的元表;同时在 _R.tolua_super 中以子类的元表为键,创建一张表作为值,而这张表会以基类,基类的基类(递归下去)为键,true/false 为值 ; 还会让基类和子类共同享有同一张tolua_ubox表(以c++指针为键 , fulluserdata为值)。 最后让 _G 持有 name_mt,即:_G.lname = name_mt。所以对于一个 c++ 类型,tolua++ 为其创建的元表最终会让全局表和注册表共同持有。

  3. 通过 tolua_function (lua_State* L, const char* name, lua_CFunction func) 将成员方法导出到 步骤1 创建的元表中;即:_G.type_mt.name = func

  4. 通过 tolua_variable(lua_State* L, const char* name, lua_CFunction get, lua_CFunction set) 在c++类型对象的元表中准备两张表_G.type_mt.get = {} , _G.type_mt.set = {} ; 两张表以变量名为键,get/set方法为值。

上面c++数据的导出步骤就是 tolua_Cocos2d_open(lua_State* tolua_S) 的实现,在CCLuaStack 初始化的时候完成。

tolua_Cocos2d_open (lua_State* tolua_S)内容分析

1: tolua_open(tolua_S) 创建一系列全局的table

  • _R.tolua_opened = true

  • _R.tolua_value_root = { }

  • _R.tolua_ubox = { }

  • setmetatable(_R.tolua_ubox,{__mode = v })

  • _R.tolua_super = { }

  • _R.tolua_gc = { }

  • _R.tolua_gc_event = class_gc_event(_R.tolua_gc , _R.tolua_super)

  • _R.tolua_commonclass = { }

  • _G.tolua = { type = tolua_bnd_type , takeownership = tolua_bnd_takeownership , releaseownership = tolua_bnd_releaseownership , cast = tolua_bnd_cast ,isnull = tolua_bnd_isnulluserdata , inherit = tolua_bnd_inherit }

  • 如果lua版本是5.1: _G.tolua.setpeer = tolua_bnd_setpeer ; _G.tolua.getpeer = tolua_bnd_getpeer ;

2: tolua_reg_types (lua_State* tolua_S) 为需要导出到lua中的c++类型注册元表

  • 对每一个要导出的c++类型调用tolua_usertype完成元表的注册。跟进tolua_usertype可以发现它做了两件事情。

    Step1: 调用 tolua_newmetatable 创建元表,并且给元表设置一系列的元方法。

    Step2: 调用 mapsuper( L , derived_type , base_type ) 在tolua_super表中以 derived_mt(子类型的元表)作为一个字段建立一张映射表 t , 这个t以父类,父类的父类(递归下去)的元表为键,布尔变量为值。 用伪代码可以表示为 tolua_super.derived_mt = { base_type = true , base_type_B = true , base_type_C = true} ,这样可以就可以判断两个类的继承关系。

3 tolua_cclass( L , lname , name , base ,col ) 实现类之间的关联,让子类能够继承父类

  • mapinheritance(L,derived,base) 将base设为derived的元表,这样就可以认为derived派生于base.

    1: 内部会调用 set_ubox(L) 实现基类与派生类共享同一份tolua_ubox表

    2: 然后将基类设置为子类的metatable,如果基类为nil , 就将 _R.tolua_commonclass 设置为子类的元表。

  • mapsuper(L,derived,base)这个方法在上面提到了,就是在 _R.tolua_super 表中创建一个索引表可以用来判断两个类之间是否有继承关系。

    tolua_super.derived_mt = { base_type = true , base_type_B = true , base_type_C = true}

  • push_collector(L,type,col) 这个地方的col是个lua_CFunction类型;type_mt.collector = col 将col函数设为type的元表collector字段所对应的元方法。c++ 对象释放的时候会要触发这个函数。

  • _G.lname = name_mt 最后这个步骤就是把之前c++类型在注册表中创建的元表让全局表也持有一份。

4 接下来就是c++类中的方法和成员变量的导出,就以导出 ccColor3B类中的成员方法和变量 为例子

  • tolua_cclass(tolua_S,"ccColor3B","ccColor3B","",tolua_collect_ccColor3B) 先将类导出到_G中,并且设置好继承关系.

  • tolua_beginmodule(tolua_S,"ccColor3B"); 将 _G.ccColor3B 压入栈中(此时lua栈负1位置是ccColor3B_mt,负2位置是_G).

  • tolua_function(tolua_S,"new",tolua_Cocos2d_ccColor3B_new00); 将方法导出到c++类型所对应的元表中即:

    _G.ccColor3B.new = tolua_Cocos2d_ccColor3B_new00

  • tolua_function(tolua_S,"new_local",tolua_Cocos2d_ccColor3B_new00_local);

    _G.ccColor3B.new_local = tolua_Cocos2d_ccColor3B_new00_local 同理下面的一系列tolua_function都会将c++方法注册到对应类型的元表中。

  • tolua_variable(tolua_S,"r",tolua_get_ccColor3B_unsigned_r,tolua_set_ccColor3B_unsigned_r)

    tolua_variable(tolua_S,"g",tolua_get_ccColor3B_unsigned_g,tolua_set_ccColor3B_unsigned_g);

    tolua_variable(tolua_S,"b",tolua_get_ccColor3B_unsigned_b,tolua_set_ccColor3B_unsigned_b);

tolua_variable 的逻辑是为每一个类型创建一张get表,一张set表,然后将变量对应的 get / set 方法放到 get表 / set表中;这样在lua层访问成员变量最终就会索引到对应的存取方法。 即:_G.ccColor3B.get = { "r" = tolua_get_ccColor3B_unsigned_r , "g" = tolua_get_ccColor3B_unsigned_g , "b" = tolua_get_ccColor3B_unsigned_b }

_G.ccColor3B.set = { "r" = tolua_set_ccColor3B_unsigned_r , "g" = tolua_set_ccColor3B_unsigned_g , "b" = tolua_set_ccColor3B_unsigned_b }

tolua++ 胶水函数分析 , 还是以ccColor3B为例

1. tolua_Cocos2d_ccColor3B_new00(lua_State* tolua_S) 将C++对象的指针以full userdata 的形式传入到 lua 层

  • ccColor3B* tolua_ret = (ccColor3B*) Mtolua_new((ccColor3B)())先创建一个c++对象,获取到指针。
  • tolua_pushusertype(tolua_S,(void*)tolua_ret,"ccColor3B") 函数在 c++ 层创建对象,然后创建full userdata压入栈中,函数最后会把 userdata 地址返回到lua层,lua 要想操作c++对象就得操作 fulluserdata, 又因为fulluserdata 的元表是 c++对象在lua中的元表,所以最终 lua 就是通过操作c++类型对应的元表来控制c++对象。这也是前面一系列步骤的意义。

    压入c++对象时候,以 light userdata 为键,full userdata 为值把这一对key-value存入tolua_ubox中。 即:

    ccColor3B_mt.tolua_ubox.tolua_ret = userdata

    setmetatable(userdata,ccColor3B_mt)

2. tolua_Cocos2d_ccColor3B_new00_local 与前者相比多了一个tolua_register_gc方法,其他的都一样

  • tolua_register_gc : 以c++指针为键,c++类型对应的元表为值,将这对key-value放于_R.tolua_gc中。

3. tolua_get_ccColor3B_unsigned_r(lua_State* tolua_S) 获取ccColor3B类中的r值

  • 首先通过 tolua_tousertype 从栈顶拿到userdata中的指针ptr, 把 ptr 转型为(ccColor3B*)
  • 然后将 (lua_Number)ptr->r 压入栈中,最后返回给lua层。

总结

  1. tolua++ 为需要导出到lua中的 c++类型 创建元表,这个元表由 注册表 和 全局表共同持有,同时在元表中注册了一系列元方法。

  2. tolua++ 将父类型设为子类型的元表;父子类共同持有同一份tolua_ubox;同时在tolua_super中为c++类型准备了一张类型映射表,可以通过该表来查询自身有哪些父类。 这样就可以在lua层实现类的继承。

  3. 通过调用 tolua_register_gc 方法,以c++类型的指针为键,c++类型对应的元表为值 作为key-value插入到_R.tolua_gc中 来管理创建c++对象的内存。
  4. tolua++ 对 c++ 对象内存的管理,以及 c++对象在lua层的扩展准备放下一篇文章再写!

tolua++实现lua层调用c++技术分析的更多相关文章

  1. c++对象在lua层的生命周期与内容扩展

    前言 上一篇博客记录了 tolua++ 将 c++类型,变量,函数,以及对象导出到 lua 的过程,这篇博客就接着记录一下 c++对象的内存回收以及c++对象数据和方法在lua中的扩展. 首先 tol ...

  2. cocos2dx中使用tolua++使lua调用c++函数

    一直想学学cocos2dx中如何使用tolua++工具使得lua脚本调用C++函数,今天就来搞一下,顺便记录下来: 首先,我们打开cocos2dx-2.2.4中projects下的test的VS工程, ...

  3. Ripple 20:Treck TCP/IP协议漏洞技术分析

    本文由“合天智汇”公众号首发,作者:b1ngo Ripple 20:Treck TCP/IP协议漏洞技术分析 Ripple20是一系列影响数亿台设备的0day(19个),是JSOF研究实验室在Trec ...

  4. YOLOV4各个创新功能模块技术分析(一)

    YOLOV4各个创新功能模块技术分析(一) 简 介 yolov4论文:YOLOv4: Optimal Speed and Accuracy of Object Detection arxiv:http ...

  5. 什么是OpenMAX技术分析OpenMAX

    什么是OpenMAX技术分析OpenMAX OpenMAX是统一的抽象层,它允许访问否则需要供应商特定API的硬件. Broadcom的MMAL(多媒体抽象层API). 因此,OpenMAX允许使用此 ...

  6. Linux内存技术分析(上)

    Linux内存技术分析(上) 一.Linux存储器 限于存储介质的存取速率和成本,现代计算机的存储结构呈现为金字塔型.越往塔顶,存取效率越高.但成本也越高,所以容量也就越小.得益于程序访问的局部性原理 ...

  7. 微前端框架 qiankun 技术分析

    我们在single-spa 技术分析 基本实现了一个微前端框架需要具备的各种功能,但是又实现的不够彻底,遗留了很多问题需要解决.虽然官方提供了很多样例和最佳实践,但是总显得过于单薄,总给人一种&quo ...

  8. iOS直播的技术分析与实现

    HTTP Live Streaming直播(iOS直播)技术分析与实现 发布于:2014-05-28 13:30阅读数:12004 HTTP Live Streaming直播(iOS直播)技术分析与实 ...

  9. Android应用程序框架层和系统运行库层日志系统源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6598703 在开发Android应用程序时,少 ...

随机推荐

  1. online ddl 跟踪

    | >mysql_prepare_alter_table T@4 : | | | | | | <mysql_prepare_alter_table 7368 T@4 : | | | | | ...

  2. JavaScript实现复制功能

    这两天在做Web前端时,遇到需求通过 js 实现文本复制的功能. 先不考虑浏览器的兼容性,看看各浏览器对复制功能的支持情况: 1.IE浏览器 ,解决方法有三种,代码如下: function copy( ...

  3. WCF中的数据契约(DataContract)

    服务契约定义了远程访问对象和可供调用的方法,数据契约则是服务端和客户端之间要传送的自定义数据类型. 一旦声明一个类型为DataContract,那么该类型就可以被序列化在服务端和客户端之间传送,如下所 ...

  4. &lbrack;译&rsqb;ASP&period;NET Core 2&period;0 路由引擎之网址生成

    问题 如何在ASP.NET Core 2.0中由路由引擎来生成网址? 答案 新建一个空项目,修改Startup.cs文件,添加MVC服务和中间件: public void ConfigureServi ...

  5. Codeforces 626D Jerry&&num;39&semi;s Protest&lpar;暴力枚举&plus;概率&rpar;

    D. Jerry's Protest time limit per test:2 seconds memory limit per test:256 megabytes input:standard ...

  6. &lbrack;Basics&rsqb; 递归

    Recursion就是方法调用自己,递归其实和循环是非常像的,循环都可以改写成递归,递归未必能改写成循环,这是一个充分不必要的条件.

  7. P2251 质量检测--洛谷luogu

    传送门 题目描述 为了检测生产流水线上总共N件产品的质量,我们首先给每一件产品打一个分数A表示其品质,然后统计前M件产品中质量最差的产品的分值Q[m] = min{A1, A2, ... Am},以及 ...

  8. case语法

    一.文件系统访问列表 FACL :Filesystem Access Control List    文件系统访问列表 利用文件扩展保存额外的访问控制权限. setfacl: -m:设定访问控制权限  ...

  9. excel 2007 无法输入中文

    解决方法: 1.32位系统:找到C:\Program Files\Common Files\Microsoft Shared\OFFICE12\Office Setup Controller,将这个文 ...

  10. delimiters 插值 选项

    delimiters差值选项vue默认是{{}},这个选项可以把这个差值形式进行改变,这里讲,默认插值改成${} html <div id="app"> <div ...