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

健忘豬發表於2013-12-16

匯出 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 -

相關文章