[轉]Cocos2d-x下Lua呼叫自定義C++類和函式的最佳實踐

ffxiangyu發表於2020-12-15

【轉自】https://segmentfault.com/a/1190000000718145

關於cocos2d-x下Lua呼叫C++的文件看了不少,但沒有一篇真正把這事給講明白了,我自己也是個初學者,摸索了半天,總結如下:

cocos2d-x下Lua呼叫C++這事之所以看起來這麼複雜、網上所有的文件都沒講清楚,是因為存在5個層面的知識點:

1、在純C環境下,把C函式註冊進Lua環境,理解Lua和C之間可以互相呼叫的本質
2、在cocos2d-x專案裡,把純C函式註冊進Lua環境,理解cocos2d-x是怎樣建立Lua環境的、以及怎樣得到這個環境並繼續自定義它
3、瞭解為什麼要使用toLua++來註冊C++類
4、在純C++環境下,使用toLua++來把一個C++類註冊進Lua環境,理解toLua++的用法
5、在cocos2d-x專案裡,使用cocos2d-x註冊自身的方式把自定義的C++類註冊進Lua環境,理解cocos2d-x是怎樣通過bindings-generator指令碼來封裝toLua++的用法來節省工作量的

只有理解了前4層,在最後使用bindings-generator指令碼的時候心裡才會清清楚楚。而網上的文件,要麼是隻解釋了第1層,要麼是隻填鴨式地告訴你第5層怎麼用bindings-generator指令碼,不僅中間重要的知識點一概不提,示例程式碼往往也寫的不夠簡潔,這讓我這種看見C++就眼暈的人理解起來大為頭疼(不是我不會C++,而是我非常不接受C++的設計哲學,能避就避)。所以接下來的講解我會對每一層知識點逐一講解,示例程式碼也不求完整嚴謹,而是儘量用最簡潔的方式把程式的關鍵點說明白。

 

第一層:純C環境下,把C函式註冊進Lua環境

直接看程式碼比囉哩囉嗦講一大堆概念要清晰明瞭的多。建立一個a.lua和一個a.c檔案,內容如下,一看就明白是怎麼回事了:

a.lua

print(foo(99))

a.c

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int foo(lua_State *L)
{
  int n = lua_tonumber(L, 1);

  lua_pushnumber(L, n + 1);

  return 1;
}

int main()
{
  lua_State *L = lua_open();

  luaL_openlibs(L);

  lua_register(L, "foo", foo);

  luaL_dofile(L, "a.lua");

  lua_close(L);

  return 0;
}

怎麼樣,這程式碼簡單吧?一看就明白,簡單的不能再簡單了。我特別煩示例程式碼裡又是判斷錯誤又是加程式碼註釋的,本來看自己不會的程式碼就夠吃力的了,還加那麼多花花綠綠的干擾項,純粹增加學習負擔。

在命令列下用gcc來編譯並執行吧:

gcc a.c -llua && ./a.out

注意-llua選項是必要的,因為要連線lua的庫。

看完上面那段程式碼,再解釋起來就容易多了:

1、要想註冊進Lua環境,函式需要定義為這個樣:int xxx(lua_State *L)
2、使用lua_tonumberlua_tostring等函式,來取得傳入的引數,比如lua_tonumber(L, 1)就是得到傳入的第一個引數,且型別為數字
3、使用lua_pushnumberlua_pushstring等函式,來將返回值壓入Lua的環境中,因為Lua支援函式返回多個值,所以可以push多個返回值進Lua環境
4、最終函式返回的數字表示有多少個返回值被壓入了Lua環境
5、使用lua_register巨集定義來將這個函式註冊進Lua環境,Lua指令碼里就可以用它了,大功告成!就這麼簡單!

 

第二層:在cocos2d-x環境下,把C函式註冊進Lua環境

也簡單:

1、在frameworks/runtime-src/Classes/目錄下,找到AppDelegate.cpp檔案。如果frameworks目錄不存在,則需要參考這篇Blog:用Cocos Code IDE寫Lua,如何與專案中的C++程式碼和諧相處

AppDelegate.cpp檔案中的關鍵程式碼如下:
```c++
auto engine = LuaEngine::getInstance();
ScriptEngineManager::getInstance()->setScriptEngine(engine);

LuaStack* stack = engine->getLuaStack();
stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));

//register custom function
//LuaStack* stack = engine->getLuaStack();
//register_custom_function(stack->getLuaState());
可以看到cocos2d-x已經為我們留出了註冊自定義C函式的位置,在註釋程式碼後面這麼寫就可以了:
```cpp
lua_State *L = stack->getLuaState();

lua_register(L, "test_lua_bind", test_lua_bind);

也可以通過ScriptEngineManager類從頭取得當前的LuaEngine物件,然後再getLuaStack()方法得到封裝的LuaStack物件,再呼叫getLuaState()得到原始的lua_State結構指標。只要知道了入口位置,其他一切就不成問題了,還是挺簡單的。

感興趣的話可以去看一下ScriptEngineManager類的詳細定義,在frameworks/cocos2d-x/cocos/base/CCScriptSupport.h檔案中。

BTW:這裡還有一個小知識點,插入在AppDelegate.cpp中的自定義程式碼儘量寫在COCOS2D_DEBUG巨集定義的判斷前面,因為在除錯環境下和真機環境下後續執行的程式碼是不一樣的:

#if (COCOS2D_DEBUG>0)
    if (startRuntime())
        return true;
#endif

    // 除錯環境下程式碼就不會走到這裡了
    engine->executeScriptFile(ConfigParser::getInstance()->getEntryFile().c_str());
    return true;

2、接下來,找個地方把test_lua_bind函式定義寫進去就算大功告成了。如果追求檔案組織的優雅,按理說應該新建一個.c檔案,但這樣的話搞不好會把自己陷入到編譯階段的泥潭裡,所以先不追求優雅,而就在AppDelegate.cpp檔案末尾寫上函式的定義就可以了,簡單清楚明瞭:

int test_lua_bind(lua_State *L)
{
    int number = lua_tonumber(L, 1);

    number = number + 1;

    lua_pushnumber(L, number);

    return 1;
}

3、大功告成,現在就可以在main.lua檔案裡使用test_lua_bind()函式了:

  local i = test_lua_bind(99)
  print("lua bind: " .. tostring(i))

4、如果是新建一個.c檔案呢?把AppDelegate.cpp檔案裡test_lua_bind函式定義的程式碼刪掉,在頭部#include後面加入:

#include "test_lua_bind.h"

frameworks/runtime-src/Classes目錄下建立test_lua_bind.h檔案,內容如下:

extern "C" {
#include "lua.h"
#include "lualib.h"
}

int test_lua_bind(lua_State *L);

再建立test_lua_bind.c檔案,內容不變:

#include "test_lua_bind.h"

int test_lua_bind(lua_State *L)
{
    int number = lua_tonumber(L, 1);

    number = number + 1;

    lua_pushnumber(L, number);

    return 1;
}

此時用cocos compile -p mac命令編譯,會發現test_lua_bind.c檔案並沒有被編譯。這是當然的,普通的C/C++專案都是用Makefile來指定編譯哪些.c/cpp檔案的,當前的cocos2d-x專案雖然沒有Makefile檔案,但也是遵循這個原則的,也即肯定是有一個地方來指定所有要編譯的檔案的,需要在這個地方把test_lua_bind.c加進去,使得整個專案編譯時把它也作為專案的一部分。

答案是,cocos2d-x專案沒有使用Makefile,而是非常聰明地使用了與具體環境相關的工程檔案來作為命令列編譯的環境,比如在編譯iOS或Mac時就使用Xcode工程檔案,在編譯Android時就使用Android.mk檔案。

所以,新增好了test_lua_bind.htest_lua_bind.c檔案後,用Xcode開啟專案,將這倆檔案新增進工程中就行了。

Xcode中新增.h和.cpp檔案進工程

注意,千萬不要勾選“Copy items into destination group's folder(if needed)”,因為cocos2d-x的Xcode工程目錄組織不是常規的結構,一旦勾選這個,會導致這兩個檔案被拷貝至frameworks/runtime-src/proj.ios_mac目錄下,原來frameworks/runtime-src/Classes目錄下的檔案就廢掉了,這樣的組織方式會亂,而且會影響Android那邊對這倆檔案的引用。

test_lua_bind.htest_lua_bind.cpp這倆檔案新增進Xcode工程後,再去命令列執行cocos compile -p mac,編譯就能成功了。

網上有其他文章說還要修改Xcode工程的“User Headers Path”,這個經過試驗是不需要的,哪怕把這倆檔案放進新建的資料夾裡也不需要,只要加入了Xcode工程即可,因為Xcode內部根本就不是按照資料夾的形式來組織檔案的,它自己有一套叫做“Group”的東西。搞了好幾年iOS開發,對Xcode的這個特性還是熟悉的。

Xcode工程中新增了test_lua_bind.h/cpp檔案後的樣子

說到這就不禁要插一句對網上所有cocos2d-x文件的吐槽了,學習cocos2d-x的人水平實在是良莠不齊,大部分人似乎都是對遊戲熱衷的程式設計初學者,他們大多底子薄基礎差,甚至一大部分人之前都沒做過移動APP的開發,他們學習cocos2d-x只想知其然而不想知其所以然,給他們講他們也看不明白(因為程式設計基礎差),所以網上不少cocos2d-x文章都是隻講123步驟,而不告訴你為什麼這麼做,包括cocos2d-x官方的大量文件也是基於這個思路寫的,中文和英文都一樣。我看這些文章就特別痛苦,一邊看一邊心裡就總是在想,“憑什麼要這麼做啊”、“這一步是為了什麼啊”、“怎麼這麼麻煩啊”、“這個步驟明顯不是最佳實踐啊”、“解決這事為啥要這麼麻煩”、“有更好的方法嗎”,所以我這種初學者來看cocos2d-x文件就變成了不是單純的學習,而是學習、質疑、求證、反思、優化的過程,對別人來說cocos2d-x的入門比較容易,到我這裡反倒成了入門比較難、入門之後比較容易了,因為文件中的垃圾資訊和無效資訊實在是太多了,別人可以照單全收、以後懂了之後再慢慢剔除,我是必須從一開始就自己甄別垃圾、只保留最佳實踐,這也是這篇Blog寫的比較長的原因。

扯遠了。反正經過以上步驟,就完成了在cocos2d-x專案中把C函式註冊進Lua環境這件事。至此,算是徹底搞懂了Lua和C函式之間的互相呼叫關係,也能在cocos2d-x的Lua環境中使用自定義的C函式了。但這還不夠,因為一個正規的專案是需要狠好的組織結構的,全域性C函式滿天飛肯定是不行的,好一點的情況是把所有的C函式都在Lua中組織為模組註冊進去,更好一點的情況是把C++類註冊進Lua、並且C++類也是以Lua模組為組織方式註冊進Lua環境的。這其實就是cocos2d-x自己把自己註冊進Lua環境的方式。


第三層:瞭解為什麼要使用toLua++來註冊C++類

因為Lua的本質是C,不是C++,Lua提供給C用的API也都是基於程式導向的C函式來用的,要把C++類註冊進Lua形成一個一個的table環境是不太容易一下子辦到的事,因為這需要繞著彎地把C++類變成各種其他型別註冊進Lua,相當於用程式導向的思維來維護一個物件導向的環境。這其中的細節就不去深究了,總之正是因為如此,所以單純地手寫lua_register()等程式碼來註冊C++類是行不通的、代價高昂的,所以需要藉助toLua++這個工具。

這一層的知識點看似簡單,但其實是非常重要的,只有理解了手工用lua_register()去註冊C++類的難度,才能理解使用toLua++這類工具的必要性。只有理解了使用toLua++工具的必要性,才會潛下心來冷靜地接受toLua++本身的優點和缺點。只有看到了toLua++本身的缺點和使用上的麻煩,才會真心理解cocos2d-x使用bindings-generator指令碼帶來的好處。只有理解了bindings-generator指令碼帶來的好處,才能諒解這個指令碼本身在使用上的一些不便之處。


第四層:在純C++環境下,使用toLua++來把一個C++類註冊進Lua環境

雖然終極方法是用bindings-generator指令碼來註冊C++類進cocos2d-x的Lua環境,但理解toLua++本身的用法還是狠有必要的,只有知道了toLua++原本的用法,才能更好地理解cocos2d-x是怎麼把自己的C++類都註冊進Lua環境的,這不僅能讓程式設計時的思路更加清晰,也能為日後在原始碼中尋找各種介面文件的過程中不至於看不懂那一大堆tolua_beginmoduletolua_function是什麼意思。影響程式設計師學習提高的一大障礙就是忽略那些一知半解的程式碼,不去刨根究底地搞明白。

使用toLua++的標準做法是:

1、準備好自己的C++類,該怎麼寫就怎麼寫
2、仿造這個類的.h檔案,改一個.pkg檔案出來,具體格式要按照toLua++的規定,比如移除所有的private成員等
3、建一個專門用來橋接C++和Lua之間的C++類,使用特殊的函式簽名來寫它的.h檔案,.cpp檔案不寫,等著toLua++來生成
4、給這個橋接的C++類寫一個.pkg檔案,按照toLua++的特殊格式來寫,目的是把真正做事的C++類給定義進去
5、在命令列下用toLua++生成橋接類的.cpp檔案
6、程式入口引用這個橋接類,執行生成的橋接函式,Lua環境中就可以使用真正做事的C++類了

toLua++這種自己手寫.pkg檔案的方式古老又難受,所以我沒有仔細地去學習,這套流程放在10年前的那個年代是沒有太大問題的,作者怎麼規定就怎麼用好了,但是放在2014年的今天,任何程式的架構設計都講究學習成本低、輕量化、符合以往的習慣,因此toLua++用起來我覺得其實是難受的。

下面我以儘量最少的程式碼來走一遍toLua++的流程,注意這是在純C++環境下,跟任何框架都沒關係,也不考慮記憶體釋放等細節:

MyClass.h

class MyClass {
public:
  MyClass() {};

  int foo(int i);
};

MyClass.cpp

#include "MyClass.h"

int MyClass::foo(int i)
{
  return i + 100;
}

MyClass.pkg

class MyClass
{
  MyClass();
  int foo(int i);
};

MyLuaModule.h

extern "C" {
#include "tolua++.h"
}

#include "MyClass.h"

TOLUA_API int tolua_MyLuaModule_open(lua_State* tolua_S);

MyLuaModule.pkg

$#include "MyLuaModule.h"

$pfile "MyClass.pkg"

main.cpp

extern "C" { 
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}

#include "MyLuaModule.h"

int main()
{
  lua_State *L = lua_open();

  luaL_openlibs(L);

  tolua_MyLuaModule_open(L);

  luaL_dofile(L, "main.lua");

  lua_close(L);

  return 0;
}

main.lua

local test = MyClass:new()
print(test:foo(99))

先在命令列下執行:

tolua++ -o MyLuaModule.cpp MyLuaModule.pkg

此命令用來生成橋接檔案MyLuaModule.cpp。注意命令列中-o引數的順序不能隨意擺放,從這個小事也能看出tolua++的古老和難用

生成好MyLuaModule.cpp檔案後,就能看到它裡面的那一大堆橋接程式碼了,比如tolua_beginmoduletolua_function等。以後看到這些東西就不陌生了,就明白這些函式只是toLua++用來做橋接的必備程式碼了,簡單看一下程式碼,就理解toLua++是怎樣把MyClass這個C++類註冊進Lua中的了:

toLua++生成的橋接程式碼

接下來,用g++來編譯:

g++ MyClass.cpp MyLuaModule.cpp main.cpp -llua -ltolua++

預設就生成了a.out檔案,執行,就能看到main.lua的執行結果了:

toLua++的執行結果

至此,對toLua++的運作原理心裡就透亮了,無非就是:

1、把自己該寫的類寫好
2、寫個.pkg檔案,告訴toLua++這個類暴露出哪些介面給Lua環境
3、再寫個橋接的.h和.pkg檔案,讓toLua++去生成橋接程式碼
4、在程式裡使用這個橋接程式碼,類就註冊進Lua環境裡了


第五層:使用cocos2d-x的方式來將C++類註冊進Lua環境

cocos2d-x在2.x版本里就是用toLua++和.pkg檔案這麼把自己註冊進Lua環境裡的。不過這種方法明顯笨拙,既要寫真正做事的.pkg檔案,也要寫橋接的.pkg檔案和.h檔案,工作量又大又枯燥。所以從cocos2d-x 3.x開始,用bindings-generator指令碼代替了toLua++。

bindings-generator指令碼的工作機制是:

1、不用挨個類地寫橋接.pkg和.h檔案了,直接定義一個ini檔案,告訴指令碼哪些類的哪些方法要暴露出來,註冊到Lua環境裡的模組名是什麼,就行了,等於將原來的每個類乘以3個檔案的工作量變成了所有類只需要1個.ini檔案
2、摸清了toLua++工具的生成方法,改由Python指令碼動態分析C++類,自動生成橋接的.h和.cpp程式碼,不呼叫tolua++命令了
3、雖然不再呼叫tolua++命令了,但是底層仍然使用toLua++的庫函式,比如tolua_function,bindings-generator指令碼生成的程式碼就跟使用toLua++工具生成的幾乎一樣

bindings-generator指令碼掌握了生成toLua++橋接程式碼的主動權,不僅可以省下大量的.pkg和.h檔案,而且可以更好地插入自定義程式碼,達到cocos2d-x環境下的一些特殊目的,比如記憶體回收之類的。所以cocos2d-x從3.x開始放棄了toLua++和.pkg而改用了自己寫的bindings-generator指令碼是非常值得讚賞的聰明做法。

接下來說怎麼用bindings-generator指令碼:

1、寫自己的C++類,按照cocos2d-x的規矩,繼承cocos2d::Ref類,以便使用cocos2d-x的記憶體回收機制。當然不這麼幹也行,但是不推薦,不然在Lua環境下物件的釋放狠麻煩。
2、編寫一個.ini檔案,讓bindings-generator可以根據這個配置檔案知道C++類該怎麼暴露出來
3、修改bindings-generator指令碼,讓它去讀取這個.ini檔案
4、執行bindings-generator指令碼,生成橋接C++類方法
5、用Xcode將自定義的C++類和生成的橋接檔案加入工程,不然編譯不到
6、修改AppDelegate.cpp,執行橋接方法,自定義的C++類就註冊進Lua環境裡了

看著步驟挺多,其實都狠簡單。下面一步一步來。

首先是自定義的C++類。我習慣將檔案儲存在frameworks/runtime-src/Classes/目錄下:

frameworks/runtime-src/Classes/MyClass.h

#include "cocos2d.h"

using namespace cocos2d;

class MyClass : public Ref
{
public:
  MyClass()   {};
  ~MyClass()  {};
  bool init() { return true; };
  CREATE_FUNC(MyClass);

  int foo(int i);
};

frameworks/runtime-src/Classes/MyClass.cpp

#include "MyClass.h"

int MyClass::foo(int i)
{
  return i + 100;
}

然後編寫.ini檔案。在frameworks/cocos2d-x/tools/tolua/目錄下能看到genbindings.py指令碼和一大堆.ini檔案,這些就是bindings-generator的實際執行環境了。隨便找一個內容比較少的.ini檔案,複製一份,重新命名為MyClass.ini。大部分內容都可以湊合不需要改,這裡僅列出必須要改的重要部分:

frameworks/cocos2d-x/tools/tolua/MyClass.ini

[MyClass]
prefix           = MyClass
target_namespace = my
headers          = %(cocosdir)s/../runtime-src/Classes/MyClass.h
classes          = MyClass

也即在MyClass.ini中指定MyClass.h檔案的位置,指定要暴露出來的類,指定註冊進Lua環境的模組名。

注意,這個地方我踩了個坑。如果.ini配置檔案中存在macro_judgement = ...巨集定義,要特別小心,我第一次是從cocos2dx_controller.ini檔案複製來的,結果沒注意macro_judgement,導致生成的橋接類檔案加入了不該加入的巨集,只在iOS和Android平臺上才起作用,對Mac平臺無效,這個要特別注意。

然後修改genbindings.py檔案129行附近,將MyClass.ini檔案加進去:

frameworks/cocos2d-x/tools/tolua/genbindings.py

cmd_args = {'cocos2dx.ini' : ('cocos2d-x', 'lua_cocos2dx_auto'), \
            'MyClass.ini' : ('MyClass', 'lua_MyClass_auto'), \
            ...

(其實這一步本來是可以省略的,只要讓genbindings.py指令碼自動搜尋當前目錄下的所有ini檔案就行了,不知道將來cocos2d-x團隊會不會這樣優化)

至此,生成橋接檔案的準備工作就做好了,執行genbindings.py指令碼:

python genbindings.py

(在Mac系統上可能會遇到缺少yaml、Cheetah包的問題,安裝這些Python包狠簡單,先sudo easy_install pip,把pip裝好,然後用pip各種pip searchsudo pip install就可以了)

成功執行genbindings.py指令碼後,會在frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto/目錄下看到新生成的檔案:

成功執行genbindings.py後生成的橋接C++檔案

每次執行genbindings.py指令碼時間都挺長的,因為它要重新處理一遍所有的.ini檔案,建議大膽修改指令碼檔案,靈活處理,讓它每次只處理需要的.ini檔案就可以了,比如像這個樣子:

修改genbindings.py使其只生成自定義的橋接類

frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto/目錄下觀察一下生成的C++橋接檔案lua_MyClass_auto.cpp,裡面的註冊函式名字為register_all_MyClass(),這就是將MyClass類註冊進Lua環境的關鍵函式:

生成的橋接檔案內容

編輯frameworks/runtime-src/Classes/AppDelegate.cpp檔案,首先在檔案頭加入對lua_MyClass_auto.hpp檔案的引用:

AppDelegate.cpp檔案的頭加入對lua_MyClass_auto.hpp檔案的引用

然後在正確的程式碼位置加入對register_all_MyClass函式的呼叫:

修改AppDelegate.cpp檔案,將MyClass類註冊進Lua環境

最後在執行編譯前,將新加入的這幾個C++檔案都加入到Xcode工程中,使得編譯環境知道它們的存在:

在Xcode中加入新冒出來的C++檔案

這其中還有一個小坑,由於lua_MyClass_auto.cpp檔案要引用MyClass.h檔案,而這倆檔案分屬於不同的子專案,互相不認識標頭檔案的搜尋路徑,因此需要手工修改一下cocos2d_lua_bindings.xcodeproj子專案的User Header Search Paths配置。特別注意一共有幾個../

需要修改cocos2d_lua_bindings子專案的User Header Search Paths配置

最後,就可以用cocos compile -p mac命令重新編譯整個專案了,不出意外的話編譯一定是成功的。

修改main.lua檔案中,嘗試呼叫一下MyClass類:

local test = my.MyClass:create()
print("lua bind: " .. test:foo(99))

然後執行程式(用cocos rum -p mac或在Cocos Code IDE中均可),見證奇蹟的時刻~~~~咦我擦?!程式崩潰!為毛?

第一次執行繫結了C++類的程式居然崩潰

這是我作為cocos2d-x初學者遇到的最大的坑,坑了我整整一天半,具體的研究細節就不詳細說了,總之罪魁禍首是cocos2d-x框架中的CCLuaEngine.cpp檔案的這段程式碼:

CCLuaEngine.cpp執行的這段程式碼要了命了

原因是executeScriptFile函式執行時,對當前Lua環境中的棧進行了清理,當register_all_MyClass函式被呼叫時,Lua棧是全空的狀態,函式內部執行到tolua_module函式呼叫時就崩潰了:

CCLuaEngine.cpp中對executeScriptFile方法的定義清理了Lua棧

解決辦法是修改AppDelegate.cpp為這個樣子:

AppDelegate.cpp呼叫register_all_MyClass函式時要重新恢復一下棧的內容才行

文字形式的程式碼如下:

AppDelegate.cpp

lua_State *L = stack->getLuaState();
lua_getglobal(L, "_G");
register_all_MyClass(L);
lua_settop(L, 0);

重新編譯並執行,程式就正確執行了:

程式終於正確執行了

至此,就徹底搞清楚應該怎樣在cocos2d-x專案裡繫結一個C函式或者C++類到Lua環境中了,感興趣的話可以再進一步深入研究Lua內部metatable的運作原理、類物件的生成與釋放、以及垃圾回收。我自己也是剛接觸cocos2d-x不到一個星期,理解不深,以上難免會有用詞不當或理解錯誤的地方,如有錯誤請多包涵。


後記補充:如果C++類定義了namespace,則需要修改frameworks/cocos2d-x/tools/bindings-generator/targets/lua/conversions.yaml檔案,定義namespace與Lua之間的對映關係,否則會報conversion wasn't set錯誤:

C++的類如果定義了namespace,則需要在匯出Lua時修改conversions.yaml檔案


後記補充2:上面的配置完成後iOS的部分是可以正常執行的,但是這個時候編譯android是不通過的,因為AppDelegate.cpp裡面呼叫的register_all_MyClass(L)方法在android不存在,android的專案裡並沒有配置去編譯對應的MyClass.cpp檔案和後續生成的lua_MyClass_auto.cpp,所以需要在android端配置Android.mk檔案,讓專案編譯時去編譯這兩個C++檔案才行。

1、首先配置JNI下面的Android.mk檔案,讓JNI部分編譯時去編譯MyClass.cpp

編輯frameworks/runtime-src/proj.android/jni/Android.mk,在LOCAL_SRC_FILES引數的後面新增:

../../Classes/MyClass.cpp

這裡需要注意的是LOCAL_SRC_FILES裡面有很多個配置,如果是最後一個不需要帶\,之前的都需要在後面帶\

圖片描述

有一種情況是MyClass.cpp#include "MyClass.h"檔案時找不到MyClass.h檔案所在位置,那就需要修改LOCAL_C_INCLUDES配置,將MyClass.h所在的目錄加入到標頭檔案搜尋路徑中。如果MyClass.h檔案本身就放在frameworks/runtime-src/Classes目錄下的話就不用再單獨設定了。

圖片描述

2、然後配置lua-bindings下面的Android.mk檔案,讓lua-bindings部分編譯時去編譯lua_MyClass_auto.cpp

編輯frameworks/cocos2d-x/cocos/scripting/lua-bindings/Android.mk,在LOCAL_SRC_FILES配置裡新增lua_MyClass_auto.cpp

圖片描述

因為lua_MyClass_auto.cpp裡引用到了MyClass.h,所以還需要配置LOCAL_C_INCLUDES,使得lua_MyClass_auto.cpp能正常的找到MyClass.h:

圖片描述

這樣在Android端的編譯就完整了,執行:

cocos compile -p android

不出意外的話就能正常編譯了,可以用Android真機測試了。

--
參考資料:

Calling C++ Functions From Lua
子龍山人:Lua教程(4)Lua呼叫C/C++函式
wtyqm:tolua++實現分析
wtyqm:cocos2dx的lua繫結
cocos2d-x-lua如何匯出自定義類到lua指令碼環境
How to bind a custom class to lua runtime
如何使用 bindings-generator 自動生成 lua繫結
cocos2d-x 3.0 + lua tolua_module崩潰問題與解決吐槽
cocos2d-x 3.0rc0 - bindings-generator 問題與解決

相關文章