如何自動生成lua綁定C++的代碼?

這裡討論的議題是「自動生成」

雖然to_lua++提供了生成,但是還算不上自動生成,因為需要提供pkg文件。

我看了一下cocos2dx-3.7.1的實現,它是借用了clang一個叫cindex.py的工具去解析C++語法,得到AST,再用Cheetah去生成代碼。

我的問題有以下幾個:

1. 想了解clang的cindex.py是什麼背景,為什麼是一個python的實現呢?沒有搜索到有用信息(直覺是自己沒找到)。

2. 有沒有其他也能提供解析C++語法的三方庫?(不包括類似於boost.spirit之類的通用語法解析庫,需要不用額外工作就直接提供C++ AST的庫,如果沒有的話,又有點想造輪子了)

3. 通過這種方式來生成lua代碼是最優解嗎,是否有其他優秀的方案推薦?因為我在實驗中發現有些情況還是需要手動修改(所以cocos2dx又出現了一個manual的文件夾.如果想去修改生成流程,還是蠻費力的,必須讀懂cindex和generator的邏輯)

最後,我覺得C++搭配lua是一件吃力不討好的事情。。。不知道各位怎麼看。。。但是手游熱更新貌似也就這一個方案了。。。

=========================================================================

補充一下:剛發現,其實神奇的地方在libclang.dll這個動態庫里,這裡提供了很多API用於語法分析,相信cindex.py的作者已經非常了解clang的細節了。如果努力啃一下clang的reference,應該也可以輕鬆寫出一個C++語法分析器。


1. 這個py文件就是LLVM中拿出來的,是libclang的一個python綁定,利用的python的ffi機制(ctypes)寫的,你可以在LLVM源碼里找到它。

2. 這應該所有c++編譯器的前端都有這個功能,比如這個簡單點的robertoraggi/cplusplus · GitHub,atomic engine就拿它寫了個綁定生成工具。

3. luabind,swig等等輪子好像挺多的,符合自己的需求就行。


謝邀

tolua++的pkg文件支持直接解析C++源碼的, 你可以試下, 他那個已經是差不多比較簡單的直接解析的方案了

我用luabridge配合一個配置文件, 自動生成綁定, 只要給出要綁定的名字, 就可以綁定了, 已經很簡單了

不推薦SWIG, 效率低, 遊戲大多還是用tolua++的綁定

反觀, 題主想全自動綁定, 其實有時未必是這樣的需求, 有些函數不需要注入到lua中, 所以DSL還是主流做法


class Module {
public:
Module(State state) {
state &<&< global &<&< key("Func") &<&< state.Adapt(Wrap(this, Module::Func)) &<&< endtable; } void Func(State request, int a, std::string b, double c) { cout &<&< a &<&< b &<&< c; } };

使用:

ZLuaState state;
Module module(state);
Ref ref = state.Load("Func(1, abc, 3.0)");
state.Call(ref);

這是我自己寫的一個簡單地將C++成員函數註冊到lua state的庫,只需要使用state.Adapt(Wrap(this, 成員函數地址))就可以直接註冊。所有的參數都是利用變長參數模板自動解析的。如果僅僅做「綁定函數到LUA「這麼簡單的事情的話,自己搞個PL其實過於麻煩了。

打算近期把這個庫發到github上,無奈訪問不了。。


Lua 的mail list 有過這個的討論,一半的人支持template,type safe的binding module, 另外一半堅持手寫。

個人推薦手寫,binding會隨著你的框架規模的增加,binding的過程中也要不斷思考究竟給user提供哪些API。

做Lua binding 重要的事情是管理好對象/closure的生命周期,分清userdata/lightuserdata。做不好/誤用就會弄出一堆性能瓶頸。


luajit的ffi庫,可以在lua直接調用c導出函數


cocos 使用bindings-generator腳本代替了toLua++. 編寫效率大大提高。

具體的在本機中分享:http://note.youdao.com/noteshare?id=0f4132271151c4b62f9afb712e8304d9

bindings-generator腳本的工作機制是:

1、不用挨個類地寫橋接.pkg和.h文件了,直接定義一個ini文件,告訴腳本哪些類的哪些方法要暴露出來,註冊到Lua環境里的模塊名是什麼

2、摸清了toLua++工具的生成方法,改由Python腳本動態分析C++類,自動生成橋接的.h和.cpp代碼,不調用tolua++命令了

3、雖然不再調用tolua++命令了,但是底層仍然使用toLua++的庫函數,比如tolua_function,bindings-generator腳本生成的代碼就跟使用toLua++工具生成的幾乎一樣

bindings-generator腳本掌握了生成toLua++橋接代碼的主動權,不僅可以省下大量的.pkg和.h文件,而且可以更好地插入自定義代碼,達到cocos2d-x環境下的一些特殊目的,比如內存回收之類的。

接下來說怎麼用bindings-generator腳本:

1、寫自己的C++類,按照cocos2d-x的規矩,繼承cocos2d::Ref類,以便使用cocos2d-x的內存回收機制。

2、編寫一個.ini文件,讓bindings-generator可以根據這個配置文件知道C++類該怎麼暴露出來

3、修改bindings-generator腳本,讓它去讀取這個.ini文件

4、執行bindings-generator腳本,生成橋接C++類方法

5、將自定義的C++類和生成的橋接文件加入工程,不然編譯不到

6、修改AppDelegate.cpp,執行橋接方法,自定義的C++類就註冊進Lua環境里了

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

1.首先是自定義的C++類。我習慣將文件保存在frameworks/runtime-src/Classes/目錄下:

frameworks/runtime-src/Classes/MyClass.h

#include "cocos2d.h"usingnamespace cocos2d;

class MyClass : public Ref
{
public:
MyClass() {};
~MyClass() {};
boolinit(){ returntrue; };
CREATE_FUNC(MyClass);

intfoo(int i);
};

frameworks/runtime-src/Classes/MyClass.cpp

#include "MyClass.h"int MyClass::foo(int i)
{
return i + 100;
}

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

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

[MyClass]prefix = MyClasstarget_namespace = myheaders = %(cocosdir)s/../runtime-src/Classes/MyClass.hclasses = MyClass

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

然後修改genbindings.pyMyClass.ini文件加進去:

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

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

4.至此,生成橋接文件的準備工作就做好了,執行genbindings.py腳本:

python ./genbindings.py

成功執行genbindings.py腳本後,會在frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto/目錄下看到新生成的文件:

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

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

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

然後在正確的代碼位置加入對register_all_MyClass函數的調用:

如何是lua工程則在:lua_module_register.h 中添加上述調用。

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

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

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

修改main.lua文件中,嘗試調用一下MyClass類:

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

6.android上運行的話需要做的事情是要將生成的橋接文件lua_MyClass_auto.cpp放到android.mk中。

配置ini時需要注意的選項:

  • [title]:要配置將被使用的工具/ tolua的/ gengindings.py腳本的稱號。一般來說,標題可以是文件名。

  • prefix:要配置一個函數名的前綴,通常,我們還可以使用文件名作為前綴 生成函數一次為前綴。

  • target_namespace:要配置在腳本層模塊的名字。在這裡,我們使用cc作為模塊名,當你想在腳本層REF的名稱,您必須將一個名為前綴,CC在名稱的前面。例如,CustomClass可以參考作為cc.CustomClass。

  • headers:要配置所有需要解析的頭文件和%(cocosdir)s是的Cocos2d-x的引擎的根路徑。

  • classes:要配置所有綁定所需的類。在這裡,它支持正則表達式。因此,我們可以設置MyCustomClass。*在這裡,用於查找多個特定的用法,你可以對照到tools/tolua/cocos2dx.ini。

  • skip:要配置需要被忽略的功能。現在綁定發電機無法解析的void *類型,並委託類型,所以這些類型的需要進行手動綁定。而在這種情況下,你應該忽略所有這些類型,然後再手動將它們綁定。你可以對照到配置文件路徑下的cocos/scripting/lua-bindings/auto 。

  • rename_functions:要配置的功能需要在腳本層進行重命名。由於某些原因,開發者希望更多的腳本友好的API,所以配置選項就是為了這個目的。

  • rename_classes:不在使用。

  • remove_prefix:不在使用。

  • base_classes_to_skip = #當被它們的子類發現的時候會跳過的基類
  • classes_have_no_parents:要配置是過濾器所需要的父類。這個選項是很少修改。

  • abstract_classes:要配置的公共構造並不需要導出的類。

  • script_control_cpp:是的。要配置腳本層是否管理對象的生命周期。如果沒有,那麼C++層關心他們的生命周期。

    現在,它是不完善的,以控制原生對象的續航時間在腳本層。所以,你可以簡單地把它設置為no。

有幾篇值得參考的筆記。都是本人使用過且自動生成成功的經驗總結。

文章編輯格式copy自己的有道雲筆記,格式太亂。但是筆記可以以看得。如下筆記講述的是原理和過程。

http://note.youdao.com/noteshare?id=142d2127d439b820f0aeeaf28631d0f5

http://note.youdao.com/noteshare?id=1d2703cd69de404e7237c44097696e02


雖然討論的是自動生成,但是本人用的是手動導出,前後花了2周多把cocos2dx的主要常用介面一個個導出,剩下的介面如果項目要用到再慢慢增加。維護到現在一共有6千多行C++導出成LUA的代碼。性能已經優化到極致,為什麼這麼說?看下面的導出代碼就知道

int lua_ccNode_setPosition(lua_State*L){
auto self = LUA_GET_SELF(ccNode);
auto x = LUA_GET_NUMBER();
auto y = LUA_GET_NUMBER();
self-&>setPosition(x, y);
return 0;
}

LUA_GET_SELF是一個宏,用lua_topointer把lua的lightuserdata強轉ccNode;LUA_GET_NUMBER直接用的是luaL_checknumber,然後每次調用這些宏的時候,把numArg自增1就可以少掉一些麻煩。如果你還能想到比這個性能更高的方案,請一定要告訴我。

手動導出有更強的可控性和靈活性,雖然會增加出錯的概率,時間上估計和自動導出沒差多少。

現在這套方案沒有像cocoslua那麼使用方便,cocoslua只要導出就可以使用。這套方案還需要到lua層進行二次封裝,我現在的做法是創一個table,[0]保存c++對象的指針,調用方法時把[0]傳進去。雖然麻煩了點,但是性能已經不是cocoslua能比的了!


swig


最近在調研SWIG, 號稱是完全兼容ANSI C++ 的語法解析, 並且target language很豐富。需要輸入一個介面文件。不過通常都是直接include head.hxx 了事。


我舉個例子,一個對象new出來後如果傳給某構造函數參數,則生命周期和該對象綁定了,如果沒有這樣調用的話則需要自己delete掉來管理。你如何管理對象生命周期

再舉個例子,有個對象但是他的某些函數執行時需要在特定線程執行的,而lua是單線程的。

某些對象的函數裡面有delete this的,如何管理lua對象的生命周期。

再比如說我限定某對象只能放棧上,無法用new,咋辦。

再舉個例子,某些對象創建要用create,而某些用new,怎麼自動處理。

總結下,某些可以用手動處理,但是自動處理起來很麻煩。

如果本來是些簡單的值語義的對象,那隨便處理都可以。


swig


推薦閱讀:

文明5渲染分析:樹林
3D遊戲開發團隊使用git進行版本控制時,怎麼處理二進位文件?
為什麼遠程合作的遊戲項目不好做(1)
ActionScript3現在是否還值得學?
遊戲伺服器全服廣播消息正確的做法?

TAG:遊戲開發 | C | Lua | Cocos2d-x | cocos2d-xlua |