玩转cocos2d-x lua-binding, 实现c++与lua混合编程

引言

城市精灵GO(http://csjl.teamtop3.com/)是一款基于cocos2d-x开发的LBS社交游戏, 通过真实地图的探索, 发现和抓捕隐匿于身边的野生精灵, 利用游戏中丰富的玩法提升和进化自己的精灵团队, 一步一步成为精灵训练大师.

玩转cocos2d-x lua-binding, 实现c++与lua混合编程

本游戏的开发混合使用了c++和lua编程, 既发挥了c++高性能, 跨平台系统兼容的优势, 又享受了lua敏捷方便的开发效率. cocos2d-x提供了一套完备的lua-binding工具来帮助开发者实现c++和lua的代码联合, 可以方便实现两者之间的数据通信和代码互调. 本文就从lua-binding入手, 深入介绍c++和lua混合编程的相关细节, 探讨其中可能存在的问题和发展. 本文原创发布于博客园(http://www.cnblogs.com/dabaopku/p/5649294.html), 独家授权XX转载.

本游戏开发基于cocos2d-x 3.2.0版本, 其他版本用户请祥阅官方文档和源代码.

原理介绍

lua(https://www.lua.org/)作为一种轻量级的脚本语言, 以其简单的语法结构, 方便的c++集成能力, 高效的执行效率收到广大游戏开发者的热爱, 也是cocos2d-x官方首次引入的脚本语言.

作为一种脚本语言, lua是在一个运行时环境(State)里执行的, 这个运行时环境保存了脚本运行所需的内存空间, 创建的全局变量, 加载的库文件等. 在这个运行时环境里还有一个栈空间(Stack), 其作用就是在lua和c语言进行数据传递和函数调用. lua原生实现了很多c api对栈空间进行操作, 让开发者能够方便地实现lua脚本代码与c编译代码的双向通信.

本文的主题是lua和c++混合编程, 但背后其实是lua和c api的互相调用, 所有c++的功能都要通过一层c函数的包装, 这点是要牢记在心的, 这也正是lua-binding的核心.

cocos2d-x提供的lua-bingding工具使用libclang分析c++源码, 提取语法树, 将c++的类成员函数封装为c函数, 然后根据参数类型自动调用lua c api, 实现对栈空间的操作, 将c++的数据传递给lua. lua脚本加载编译好的c++库, 就可以自由调用c++里面的类对象和成员函数了; c++的代码则可以直接使用lua c api, 执行一段lua脚本, 并通过栈空间获取返回结果.

操作实战

本节我们来详细介绍c++和lua混合编程的具体实现方法, 首先介绍如果利用cocos2d-x的工具自动把项目里的c++代码导出为lua模块, 然后介绍如果手动导出特殊类型的函数, 最后介绍实践中的技巧和潜在隐患.

自动生成lua模块

cocos2d-x提供的lua-binding工具位于项目 tools/tolua 目录下, 可以看到里面有 genbindings.py, **.ini, userconf.ini等文件, 这些就是自定义代码导出策略的配置文件.

玩转cocos2d-x lua-binding, 实现c++与lua混合编程

玩转cocos2d-x lua-binding, 实现c++与lua混合编程
  • userconf.ini: 这个文件配置了系统运行的环境变量, 比如adk路径, 使用系统默认值即可
  • genbindings.py: 这是生成lua-binding代码的脚本, 代码最后的cmd_args参数配置了需要导出的不同lua模块, 根据需要在这里添加条目即可. 括号里的第一个参数代表了模块名(下文介绍), 第二个参数代表了生成文件的名字; 如果想要自定义生成目录, 可以修改output_dir变量
  • **.ini: 这里是导出配置的关键, 我们以cocos2dx.ini为例, 详细介绍每一个部分的作用
  • [cocos2d-x] 这里对应上文介绍的模块名, 要和第一个参数保持一致
  • prefix 给所有生成的c函数添加前缀, 防止命名重复
  • target_namespace 导出的lua模块的命名空间, 重要
  • 接下来是一些编译参数, 用于辅助libclang寻找头文件, 设置宏参数, 如果发现clang出错, 可以考虑修改这里的参数
  • headers 这个参数可以设置一组头文件, 程序根据这个头文件及其包含的头文件, 抽取出c++声明的类, 作为导出对象,重要
  • classes 这个参数配置基于正则表达式的模式列表, 过滤上一个参数提取出的类, 得到最终导出的类列表,重要
  • skip 不想导出的类级函数, 用于处理一些和lua不兼容的c++参数, 比如 std::function, std::pair 等, 这些参数没法通过栈空间传递给lua, 因此也就没办法导出给lua使用, 需要排除掉.重要
  • rename_functions, rename_classed 重命名导出的函数和类, 用处不大
  • classes_have_no_parents, base_classes_to_skip, abstract_classes 一些小功能, 用处不大

设置完这个文件, 运行 genbindings.py 文件, 就可以看到生成的c++代码了. 把这个代码加入到项目中, 就可以在lua脚本里直接使用c++的相关功能了.

比如, c++里有这个一个类:

cpp;gutter:true; class GuideManager { public: static GuideManager *getInstance();</p> <p>public: bool isAvailable(const std::string &id); void addGuide(const std::string &id); void finishGuide(const std::string &id); bool isGuideFinished(const std::string &id); void clear(); protected: std::map _finishedGuides; std::mutex _lock; };</p> <pre><code> 通过lua-binding得到导出类, 就可以在lua代码里直接使用: ;gutter:true;
local manager = pp.GuideManager:getInstance()
if manager:isAvailable("12345") then
— Show Guide
manager:finishGuide("12345")
end

通过上述简单的配置, 就可以把项目里上百个类, 几千个成员函数直接导出, 提供给lua使用. 在生成c++文件的同时, 程序还会生成一系列没有实际功能的lua文件, 每一个文件对应一个导出类, 列出了这个类导出的所有函数以及参数类型, 方便开发者验证导出的方法是否满足预期, 同时可以交给第三方插件来辅助IDE进行代码高亮与提示.

如果开发者更新了c++代码, 只需要重新运行脚本, 更新导出文件即可.

以上操作就是cocos2d-x推荐给开发者使用的lua-binding方案, 可以在官方网站和网络上找到丰富的教程, 这里不再深入展开.

手动导出lua模块

在实际应用中, 手动导出一个功能模块也是很重要的需求, 比如在c++里面实现了一个网络库, 通过传递一个std::function作为回调函数, 函数原型如下:

cpp;gutter:true; void get(const std::string &path, Json::Value ¶ms, std::function callback);</p> <pre><code> 但是lua里面的函数和c++的std::function并不兼容, 不能直接把lua的函数传递给c++使用, 因此lua-binding工具就不能自动生成代码绑定了. 开发者需要手动实现参数的传递, 把lua函数转换为c++的std::function. 为了克服这个困难, 我们先来看一下lua-binding是怎样自动生成代码的: ;gutter:true;
int lua_pocketpet_PetModel_getSkillById(lua_State* tolua_S)
{
// 1
int argc = 0;
PocketPet::PetModel* cobj = nullptr;
bool ok = true;

cobj = (PocketPet::PetModel*)tolua_tousertype(tolua_S,1,0);// 2
argc = lua_gettop(tolua_S)-1;
if (argc == 1)
{
std::string arg0;

ok &= luaval_to_std_string(tolua_S, 2,&arg0);
if(!ok)
return 0;// 3
PocketPet::SkillModel* ret = cobj->getSkillById(arg0);
object_to_luaval(tolua_S, "pp.SkillModel",(PocketPet::SkillModel*)ret);
return 1;
}
return 0;
}

我们可以看出, lua调用c++代码一共包含3步:

  1. 获取c++对象
  2. 获取参数, 校验参数类型
  3. 调用成员函数

自动生成的代码支持int, double等数值类型, 指针类型, std::string, std::map, std::vector, cocos2d::Map, cocos2d::Vector等模板类型, 超出这些范围的, 就需要我们自己实现了. 参考上述代码, 我们可以先实现以下这个函数:

cpp;gutter:true; int lua_pocketpet_NetworkManager_getInLua(lua_State<em> tolua_S) { int argc = 0; PocketPet::NetworkManager</em> cobj = nullptr; bool ok = true;</p> <pre><code>cobj = (PocketPet::NetworkManager*)tolua_tousertype(tolua_S,1,0); argc = lua_gettop(tolua_S)-1; if (argc == 3) { std::string arg0; std::string arg1; LUA_FUNCTION arg2; ok &= luaval_to_std_string(tolua_S, 2,&arg0); ok &= luaval_to_std_string(tolua_S, 3,&arg1); // 1 arg2 = toluafix_ref_function(tolua_S, 4, 0); if(!ok) return 0; cobj->get(arg0, arg1, arg2); return 0; } return 0; </code></pre> <p>}</p> <pre><code> 在这里, 我们首先通过 toluafix_ref_function 获得一个LUA_FUNCTION(也就是 int)类型的lua函数指针, 将这个值作为参数传递给业务函数. 在业务函数里, 通过栈空间来回调这个函数指针, 如下所示: ;gutter:true;
void NetworkManager::get(const std::string &path, const std::string ¶ms, int callback)
{
auto func = [callback](NetworkResponse *response){
auto engine = LuaManager::getInstance()->engine();
engine->getLuaStack()->pushObject(response, "pp.NetworkResponse");// 1
engine->getLuaStack()->executeFunctionByHandler(callback, 1);
};

Json::Value json;

this->get(path, json, func);
}

我们创建了一个std::function对象func作为lua函数的封装, 在func内部, 通过lua栈空间调用lua回调函数. 通过这两层的封装, 就实现了把lua的函数作为c++的回调函数进行使用.

对于其他的特殊类型, 也都可以用类似的手段来解决.

其他技巧与潜在风险

通过lua-binding方案, 可以方便的把c++开发的功能导入到lua里面进行使用, 可以方便团队从c++向lua转型, 提高产品后期快速迭代更新的速度. 虽然现在鼓励脚本开发, 但c++的应用无可避免, 比如渠道sdk, 比如跨平台适配, 比如账号安全维护等等, 都还是需要c++这把瑞士军刀来应对一切挑战. 在c++和lua之间, 除了通过栈空间传递数据, 我们还可以有多种机制来进行通信, 从而克服lua-binding的局限.

一种方案是开辟一块专门的内存空间, 通过键值对存储临时对象, 双方通过这块共享空间传递必要信息. 这种方式可以灵活的传递复杂的数据, 同时可以应对异步调用c++的问题, 防止cocos2d-x对象因为跨帧被autorelease.

另一种方案可以使用消息分发, 通过数据对象的序列化与反序列化, 实现复杂数据的传递, 比如json对象, 但需要评估实现的性能损耗.

在使用lua-binding时, 还需要考虑线程执行的问题,如果涉及到多层回调以及ui刷新, 要确保内容的更新在主线程完成.

另外, 在c++中调用lua脚本会创建一个新的运行时环境, 不同运行时环境之间的数据是相互独立的, 要格外留意脚本文件相关初始化工作是否正确执行.

总结

本文详细介绍了使用cocos2d-x工具, 实现c++和lua混合编程的基本原理和实现方案, 希望对大家帮助.

Original: https://www.cnblogs.com/dabaopku/p/5649294.html
Author: 大宝pku
Title: 玩转cocos2d-x lua-binding, 实现c++与lua混合编程

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/536541/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • c++对象工厂

    一.简单工厂 #pragma once struct IObjectA { virtual void Test1()=0; }; class ObjectA:public IObj…

    C++ 2023年5月29日
    060
  • C++ 总结

    1、迭代器并不是都可以进行加减 迭代器实质上是一个指针,但是,并不是所有的容器的迭代器可以支持加减操作。 能进行算术运算的迭代器只有随机访问迭代器,要求容器元素存储在连续内存空间内…

    C++ 2023年5月29日
    068
  • c++中捕捉内存泄露、异常

    cpp;gutter:false; //在Watch面板加上可以观察当前断点处最后一条异常信息:@err,hr#include "stdafx.h"</p…

    C++ 2023年5月29日
    055
  • C++ 求时差

    C++ 求时差的三种方法 【1】标准C库方式 示例代码 【2】C++库方式 C++11之后才引入 示例代码: 【3】windows API方式 示例代码: 为什么第14行,我们把G…

    C++ 2023年5月29日
    082
  • 深入理解c++构造函数, 复制构造函数和赋值函数重载(operator=)

    以下代码编译及运行环境均为 Xcode 6.4, LLVM 6.1 with GNU++11 support, Mac OS X 10.10.2 调用时机 看例子 1,2,3,4 …

    C++ 2023年5月29日
    073
  • WIN部分程序调用VS C++库导致提示报错R6034解决方法

    最近电脑上的部分软件运行的时候有时候会报错R6034,猜测可能是软件安装冲突导致 可能是因为软件在调用VS C++runtime error库的dll文件的时候找不到或者找到了错误…

    C++ 2023年5月29日
    083
  • c++ win32 遍历进程列表

    使用 CreateToolhelp32Snapshot #include #include #include #include int main() { HANDLE hSnap …

    C++ 2023年5月29日
    072
  • C++ #ifndef/#define/#endif解释

    作用:防止头文件的重复包含和编译 ifndef x define x endif 比如说有一个头文件叫head.h,这是一个通用的头文件,然后我又定义了两个自己用的头文件,分别叫l…

    C++ 2023年5月29日
    044
  • 无废话–Mac OS, VS Code 搭建c/c++基本开发环境

    无废话,直接上步骤。 1) 安装 xcode。 打开App Store,搜索xcode,进行下载安装。 2)执行命令: xcode-select –install 安装…

    C++ 2023年5月29日
    061
  • C++Builder及VC的库相互调用

    coff2omf vc.lib bc.lib implib -f xxx.lib xxx.dll dll文件为VC编译的动态库lib文件为你需要转换的c++ builder 使用的…

    C++ 2023年5月29日
    063
  • C++ 相关库

    C++ 相关库 说明 RapidJSONhttps://github.com/Tencent/rapidjson Original: https://www.cnblogs.com…

    C++ 2023年5月29日
    068
  • Android jni c/c++线程通过CallVoidMethod调用java函数出现奔溃问题

    最近在移植网络摄像机里的p2p库到android平台,需要用到jni,最近在c线程了调用java函数的时候出现一个问题,假如在同一个线程调用java函数是没问题的,但在一个c线程了…

    C++ 2023年5月29日
    032
  • 代朋友发招聘信息,C++程序员

    急招:高级C/C++程序员工作职能:1、开发工具(地图编辑,动作编辑器等)的维护及新版本的开发。2、Symbian、WM新游戏开发要求:1、精通C/C++语言;2、强烈的责任心和认…

    C++ 2023年5月29日
    054
  • EclipseC++学习笔记-5 隐藏cmd窗口

    每次启动eclipse后,会有一个cmd窗口,很影响整洁解决办法示例:powershell.exe -WindowStyle Hidden -c wsl — /root…

    C++ 2023年5月29日
    070
  • C++案例——协助破案

    协助破案。假设已经查清,有 A、B、C、D、E 五个嫌疑人可能参与制造了一起抢劫银行案,但不知道其中哪几个是真正的案犯。不过,有确凿证据表明: 上述论断可以用数理逻辑中的正规表达式…

    C++ 2023年5月29日
    047
  • windows下使用vscode编写运行以及调试C/C++

    c++本地运行和debughttps://www.cnblogs.com/TAMING/p/8560253.html 运行C++/C代码 在终端运行 文件读入https://blo…

    C++ 2023年5月29日
    052
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球