導出 C/C++ API 給 Lua 使用[轉]

導出 C/C++ API 給 Lua 使用

cocos2d-x 和 quick-cocos2d-x 的底層代碼都是使用 C++ 語言開發的。爲了使用 Lua 腳本語言進行開發,咱們利用 tolua++ 工具,將大量的 C/C++ API 導出到了 Lua 中。javascript

使用 tolua++ 的基本步驟:html

  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。

根據實踐,咱們建議採用以下的方案來完成整個導出工做。java

 

從 C/C++ 源文件建立 .tolua 文件

假設咱們的 MyClass.h 頭文件內容以下:node

#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++ 源文件衝突。python

建立 MyClass_luabinding.tolua 文件,並修改內容爲:nginx

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

這裏能夠看到 .tolua 的內容有明顯簡化。詳細的內容修改規則,會在本文後續部分說明。c++

 

生成 luabinding 接口文件

quick 爲了簡化這一步工做,提供了相應工具,咱們只須要建立一個腳本文件來調用工具便可。shell

  1. 建立 build_luabinding.sh 文件,內容以下:ruby

    #!/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,不然沒法執行該腳本。bash

    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 -

相關文章
相關標籤/搜索