如何导出 C和C++ 的API 给 Lua 使用

地址:http://write.blog.csdn.net/postedit/9904727

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


使用 tolua++的基本步骤:


C/C++源代码复制头文件的内容到 .toluatolua++文档中称为 .pkg)文件中。

修改 .tolua 文件内容,去掉 tolua++无法识别的内容,以及不需要导出到 Lua 的定义。

运行 tolua++工具,根据 .tolua 文件生成 luabinding 接口文件(由 .cpp文件和 .h 文件自称)。

AppDelegate.cpp中加载 luabinding 文件。

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:
    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 -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 "%DIR%"
call %MAKE_LUABINDING% -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 的实现,只保留声明。




展开宏


有些宏是不能直接删除的,例如 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修饰符。唯一例外的就是 constchar*不需要修改为 char*


例如:


CCPoint convertToNodeSpace(const CCPoint& worldPoint);


应该修改为:


CCPoint convertToNodeSpace(CCPoint& worldPoint);


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




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


如果一个函数的所有参数都是引用或指针类型,并且不是 constchar*,那么在 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++很简单,使用 constchar*参数类型和 int类型参数分别指定二进制数据的指针和数据长度。


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




更多用法


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


- END -

你可能感兴趣的:(如何导出 C和C++ 的API 给 Lua 使用)