导出 C/C++ API 给 Lua 使用

 

cocos2d-x 和 quick-cocos2d-x 的底层代码都是使用 C++ 语言开发的。为了使用 Lua 脚本语言进行开发,我们利用 tolua++ 工具,将大量的 C/C++ API 导出到了 Lua 中。

使用 tolua++ 的基本步骤:

  1. 从 C/C++ 源代码复制头文件的内容到 .tolua(tolua++ 文档中称为 .pkg)文件中。
  2. 修改 .tolua 文件内容,去掉 tolua++ 无法识别的内容,以及不需要导出到 Lua 的定义。
  3. 运行 tolua++ 工具,根据 .tolua 文件生成 luabinding 接口文件(由 .cpp 文件和 .h 文件自称)。
  4. 在 AppDelegate.cpp 中加载 luabinding 文件。
  5. 在 AppDelegate 初始化 Lua 虚拟机后,调用 luabinding 接口文件中的 luaopen 函数,注册 C/C++ API。

根据实践,我们建议采用如下的方案来完成整个导出工作。

从 C/C++ 源文件创建 .tolua 文件

假设我们的 MyClass.h 头文件内容如下:

#ifndef __MY_CLASS_H_
#define __MY_CLASS_H_ class MyClass
{
public:
static void addTwoNumber(float number1, float number2); private:
MyClass(void) {}
}; #endif // __MY_CLASS_H_

为了便于维护,应该将 .h 文件对应的 tolua 命名为 XXX_luabinding.tolua。这样生成的 luabinding 接口文件名就是 XXX_luabinding.cpp 和 XXX_luabinding.h,不会和已有的 C/C++ 源文件冲突。

创建 MyClass_luabinding.tolua 文件,并修改内容为:

class MyClass : public CCObject
{
public:
static void addTwoNumber(float number1, float number2);
};

这里可以看到 .tolua 的内容有明显简化。详细的内容修改规则,会在本文后续部分说明。

生成 luabinding 接口文件

quick 为了简化这一步工作,提供了相应工具,我们只需要创建一个脚本文件来调用工具即可。

  1. 创建 build_luabinding.sh 文件,内容如下:

    #!/usr/bin/env bash
    DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
    cd "$DIR"
    OUTPUT_DIR="$DIR"
    MAKE_LUABINDING="$QUICK_COCOS2DX_ROOT"/bin/compile_luabinding.sh
    $MAKE_LUABINDING -E MyClass -d "$OUTPUT_DIR" MyClass_luabinding.tolua

    记得在命令行中 chmod 755 build_luabinding.sh,否则无法执行该脚本。

    Windows 版的批处理内容如下:

    @echo off
    set DIR=%~dp0
    set OUTPUT_DIR=%DIR%
    set MAKE_LUABINDING="%QUICK_COCOS2DX_ROOT%\bin\compile_luabinding.bat"
    pushd
    cd /d "%DIR%"
    call %MAKE_LUABINDING% -E MyClass -d %OUTPUT_DIR% MyClass_luabinding.tolua

    注意:运行脚本前请确保已经下载了 quick-cocos2d-x,并且正确设置了 QUICK_COCOS2DX_ROOT 环境变量。环境配置请参考《入门指引》。

  2. 在命令行下运行我们创建的脚本,如果一切顺利,我们会看到如下输出信息:

    creating file: MyClass_luabinding.cpp
    creating file: MyClass_luabinding.h // add to AppDelegate.cpp
    #include "MyClass_luabinding.h" // add to AppDelegate::applicationDidFinishLaunching()
    CCLuaStack* stack = CCScriptEngineManager::sharedManager()
    ->getScriptEngine()
    ->getLuaStack();
    lua_State* L = stack->getLuaState();
    luaopen_MyClass_luabinding(L);

载入 luabinding 接口文件

打开我们的项目,将 MyClass_luabinding.cpp 和 MyClass_luabinding.h 文件加入工程。然后修改 AppDelegate.cpp 文件:

  1. 在 AppDelegate.cpp 头部区域添加:

    #include "MyClass_luabinding.h"
  2. 在 AppDelegate::applicationDidFinishLaunching() 函数内添加:

    luaopen_MyClass_luabinding(L);

    注意这一行代码应该添加在其他 luaopen 函数后面,例如:

    // register lua engine
    CCLuaEngine *pEngine = CCLuaEngine::defaultEngine();
    CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine); CCLuaStack *pStack = pEngine->getLuaStack();
    lua_State* L = pStack->getLuaState(); // load lua extensions
    luaopen_lua_extensions(L);
    // load cocos2dx_extra luabinding
    luaopen_cocos2dx_extra_luabinding(L); // thrid_party
    luaopen_third_party_luabinding(L); // CCBReader
    tolua_extensions_ccb_open(L); // MyClass
    luaopen_MyClass_luabinding(L);

    应该将我们的代码追加到 tolua_extensions_ccb_open() 后面。

经过上述修改后,重新编译运行项目应该就可以在 Lua 脚本中使用我们导出的 MyClass 对象极其方法了。

.tolua 文件内容的修改规则

前面的 MyClass 是一个非常简单的例子,但我们实际游戏中的 C/C++ API 可能比较复杂。在修改 .tolua 文件内容时,应该仔细阅读以下内容。

删除所有无需在 Lua 中使用的内容

导出的 API 越多,在 Lua 虚拟机中占用的符号表空间就越多。因此我们第一步要做的就是删除所有无需在 Lua 中使用的内容。

  1. 对于 enum、宏定义,如果需要导出,原文保留即可。但宏定义只能导出数值定义,例如:

    #define kCCHTTPRequestMethodGET  0
    #define kCCHTTPRequestMethodPOST 1

    而非数值的宏定义无法导出,以下内容会导出失败:

    #define kMyConstantString "HELLO"
  2. 删除所有无法识别的宏,例如 CC_DLL。

  3. 删除 C++ class 中所有非 public 的定义。

  4. 删除 C++ class 中的类成员变量。

  5. 删除 inline 关键词,以及 inline function 的实现,只保留声明。

处理 CCObject 继承类

CCObject 及其继承类都具备“引用计数”和“自动释放”机制。如果你的 C++ 对象是从 CCObject 继承的,那么必须告诉 tolua++ 做相应处理,否则可能出现内存泄漏等问题。

对于 quick,只需要在 build 脚本中通过 -E CCOBJECTS 参数指定这些 class 的名字即可。

例如前面 MyClass 的示例中,用 -E CCOBJECTS=MyClass 告诉 tolua++ 应该将 MyClass 当作 CCObject 的继承类进行处理。

如果有多个类,那么每个类名之间用“,”分隔即可,例如:

$MAKE_LUABINDING -E CCOBJECTS=MyClass,MyClass2,MyClass3 -d "$OUTPUT_DIR" MyClass_luabinding.tolua

展开宏

有些宏是不能直接删除的,例如 CC_PROPERTY。对于这类宏,需要根据宏定义,将宏展开为声明。

CC_PROPERTY(float, m_fDuration, Duration)

展开为:

float getDuration();
void setDuration(float v);

需要如此处理的宏包括:CC_PROPERTY_READONLY, CC_PROPERTY, CC_PROPERTY_PASS_BY_REF, CC_SYNTHESIZE_READONLY, CC_SYNTHESIZE_READONLY_PASS_BY_REF, CC_SYNTHESIZE, CC_SYNTHESIZE_PASS_BY_REF, CC_SYNTHESIZE_RETAIN。

幸运的是这些宏大多只用在 cocos2d-x 基础代码里,我们自己的 C++ class 还是不要用这些宏了。

处理名字空间

如果使用了名字空间,那么在 .tolua 的头部应该加入:

$using namespace myname;

这里用到的“$”符号,后续内容会原样放入 luabinding 文件。

添加必要的 #include 指令

如果生成的 luabinding 接口文件无法编译,需要检查是否是需要 include 相应的头文件,并添加如下代码:

$#include "MyClass.h"

修改函数参数和返回值类型,去除 const 修饰符

一些函数的参数或返回值,使用了 const 修饰符。由于 tolua++ 的限制,并不能很好的处理这类定义,所以我们要从 .tolua 文件中移除 const 修饰符。唯一例外的就是 const char* 不需要修改为 char*。

例如:

CCPoint convertToNodeSpace(const CCPoint& worldPoint);

应该修改为:

CCPoint convertToNodeSpace(CCPoint& worldPoint);

这样修改的原因是 tolua++ 把 const CCPoint 和 CCPoint 当做两个不同的类型来处理。如果不做修改,那么调用函数时会报告参数类型不符。

从 C/C++ 函数返回多个值

如果一个函数的所有参数都是引用或指针类型,并且不是 const char*,那么在 luabinding 接口文件中,该函数会返回多个值。

例如:

void getPosition(float* x = 0, float* y = 0);

在 Lua 中调用这个函数,会得到两个返回值:

local x, y = node:getPosition()

将 Lua 函数传入 C/C++

quick 里,允许将 Lua 函数传入 C/C++,只要求 C/C++ 函数中使用 int 做参数类型。但在 .tolua 文件里,则必须使用 LUA_FUNCTION 做参数类型。

例如:

static CCHTTPRequest* createWithUrlLua(int listener,
const char* url,
int method = kCCHTTPRequestMethodGET);

listener 参数用于保存传入的 Lua 函数,所以 .tolua 文件里要改写为:

static CCHTTPRequest* createWithUrlLua(LUA_FUNCTION listener,
const char* url,
int method = kCCHTTPRequestMethodGET);

具体用法请参考 lib/cocos2dx_extra/extra/network/CCHTTPRequest 中的 createWithUrlLua() 方法。

在 Lua 和 C/C++ 间交换二进制数据

要从 C/C++ 返回二进制数据给 Lua,函数返回值类型必须是 int,而 .tolua 文件中修改返回值为 LUA_STRING。函数中,需要用 CCLuaStack::pushString() 将二进制数据放入 Lua stack。然后返回“需要传递给 Lua 的值”的数量。

具体用法请参考 lib/cocos2dx_extra/extra/network/CCHTTPRequest 中的 getResponseDataLua() 方法。

从 Lua 传递二进制数据给 C/C++ 很简单,使用 const char* 参数类型和 int 类型参数分别指定二进制数据的指针和数据长度。

具体用法请参考 lib/cocos2dx_extra/extra/crypto/CCCrypto 中的 decryptXXTEALua() 方法。

更多用法

关于利用 tolua++ 的更多用法,建议参考 lib/cocos2dx_extra 中的 CCCrypto、CCNative、CCHTTPRquest 等 class。这些 class 对 Lua 提供了良好的支持,具体用法上也覆盖了绝大多数 C/C++ 和 Lua 交互的需求。

- END -

导出 C/C++ API 给 Lua 使用[转]的更多相关文章

  1. HttpLuaModule——翻译(Nginx API for Lua) (转)

    现在我已经将翻译的内容放到:http://wiki.nginx.org/HttpLuaModuleZh Nginx API for Lua Introduction 各种各样的*_by_lua和*_b ...

  2. HttpLuaModule——翻译(Nginx API for Lua)

    现在我已经将翻译的内容放到:http://wiki.nginx.org/HttpLuaModuleZh Nginx API for Lua Introduction 各种各样的*_by_lua和*_b ...

  3. openresty HTTP status constants nginx api for lua

    https://github.com/openresty/lua-nginx-module context: init_by_lua, set_by_lua, rewrite_by_lua, acce ...

  4. luars232库中用到的一些C API for lua

    代码就不贴了,这里只是梳理一下前两篇里面忽略的一些东西,作为读代码的记录吧. 1.头文件 #include <lauxlib.h> #include <lua.h> All A ...

  5. Lua调用C++时打印堆栈信息

    公司的手游项目,使用的是基于cocos2d-x绑lua的解决方案(参数quick-x的绑定),虽然使用了lua进行开发,更新很爽了,但是崩溃依然较为严重,从后台查看崩溃日志时,基本上只能靠" ...

  6. 如何学习 cocos2d-x ?

    发表于 04/23/2014 作者 zrong — 24 条评论 ↓ 11,687 次查看 本站文章除注明转载外,均为本站原创或者翻译. 本站文章欢迎各种形式的转载,但请18岁以上的转载者注明文章出处 ...

  7. 【精选】Nginx模块Lua-Nginx-Module学习笔记(一)Nginx Lua API 接口详解

    源码地址:https://github.com/Tinywan/Lua-Nginx-Redis 一.介绍 各种* _by_lua,* _by_lua_block和* _by_lua_file配置指令用 ...

  8. Nginx模块Lua-Nginx-Module学习笔记(一)Nginx Lua API 接口详解

    源码地址:https://github.com/Tinywan/Lua-Nginx-Redis 一.介绍 各种* _by_lua,* _by_lua_block和* _by_lua_file配置指令用 ...

  9. 在MyEclipse中使用javadoc导出API文档详解

    本篇文档介绍如何在MyEclipse中导出javadoc(API)帮助文档,并且使用htmlhelp.exe和jd2chm.exe生成chm文档. 具体步骤如下: 打开MyEclipse,选中想要制作 ...

随机推荐

  1. 设计模式之美:Flyweight(享元)

    索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):使用 FlyweightFactory 管理 Flyweight 对象. 意图 运用共享技术有效地支持大量细粒度的对象. Use ...

  2. JavaScript实用技巧总结

    前言 总结一下最近接触到的JavaScript语法糖,与大家共享. 每块糖都有详细的说明和示例,就不多说了. 准确的类型检查 /* * @function: * 类型检查示例 * 通过此方法,可以检查 ...

  3. 一个格式化日期和时间的JavaScript类库

    原文地址:http://www.cnblogs.com/zhangpengshou/archive/2012/07/19/2599053.html 结合meizz的代码做了适当调整. Date.pro ...

  4. 成功安装mysql后,为何服务管理器里找不到MYSQL服务名

    1.打开cmd,切换到mysql的bin目录下 2. D:\Program Files\MySQL5.1\bin>mysqld.exe -installService successfully ...

  5. paip.powerdesign cdm pdm文件 代码生成器 java web 页面 实现

    paip.powerdesign cdm pdm文件 代码生成器 java web 页面 实现 准备从pd cdm生成java web 页面...但是,ms无直接地生成软件.... 只好自己解析cdm ...

  6. 【转】Oracle 执行动态语句

    1.静态SQLSQL与动态SQL Oracle编译PL/SQL程序块分为两个种:其一为前期联编(early binding),即SQL语句在程序编译期间就已经确定,大多数的编译情况属于这种类型:另外一 ...

  7. Jsp技术总结

    这节我们总结一下Jsp的相关技术. 1. 什么是JSP JSP即Java Server Pages,它和servlet技术一样,都是sun公司定义的一种用于开发动态web资源的技术.该技术的最大特点在 ...

  8. VC2010 调用 Webservice

    开发环境:VC2010,gsoap_2.8.23 http://blog.csdn.net/zhaiwenjuan/article/details/6590941 使用soapcpp2的时候要加参数- ...

  9. CSS3实践之路(六):CSS3的过渡效果(transition)与动画(animation)

    刚开始W3C CSS Workgroup拒绝将CSS3 transition与animation加入官方标准,一些成员认为过渡效果和动画并非样式属性,而且已经可以用脚本实现.所以请大家明白,特别是We ...

  10. 淘宝TOP之API测试

    下面的文章,是很早之前写的.内容过时了.主要是获取session的方法,很简单了.作为一个中小型网站开发者,淘宝API的开放大大缩短了网站的开发周期和运作效率,面对海量的数据,开发者只要仔细阅读开发文 ...