用LuaBridge为Lua绑定C/C++对象
最近為了總結(jié)Lua綁定C/C++對象的各種方法、第三方庫和原理,學(xué)習(xí)了LuaBridge庫為Lua綁定C/C++對象,下面是學(xué)習(xí)筆記,實質(zhì)是對該庫的?Reference Manual?基本上翻譯了一遍,學(xué)習(xí)過程中測試代碼,放在?我的github?上。
LuaBridge的主要特點?
???? 源碼只有頭文件,沒有.cpp文件,沒有MakeFile,使用時只需一個#include即可。?
???? 支持不同的對象生命周期管理模式。?
???? 對Lua棧訪問方便并且是類型安全的(type-safe)。?
???? Automatic function parameter type binding.?
???? Easy access to Lua objects like tables and functions.?
???? LuaBridge的API是基于C++模板元編程(template metaprogramming)的。在編譯時這些模板自動生成各種Lua API調(diào)用,從而可以再Lua腳本中使用C++程序中的類和函數(shù)。?為了能在C++中使用Lua的數(shù)據(jù),比如number,string,table以及方便調(diào)用Lua的函數(shù),使用LuaBridge中的LuaRef類,可以方便做到。?
LuaBridge設(shè)計原則?
???? 由于LuaBridge的設(shè)計目標(biāo)盡可能方便使用,比如只有頭文件、沒有用到高級C++的語法、不需要配置。因此LuaBridge性能雖足夠好,但并不是最好的,比如?OOLua(https://code.google.com/p/oolua/)執(zhí)行效率就比它好,并且它也不像LuaBind(http://www.rasterbar.com/products/luabind.html)那樣功能全面。?LuaBridge不支持下面特性:?
???? 枚舉型常量?
???? 不支持8個以上的函數(shù)或方法的調(diào)用?
???? 重載函數(shù)、方法和構(gòu)造函數(shù)(Overloaded functions, methods, or constructors)?
???? 全局變量(變量必須被包裝在命名空間里)?
???? 自動地轉(zhuǎn)換STL容器類型和Table?
???? 在Lua中繼承C++類(Inheriting Lua classes from C++ classes)。?
???? Passing nil to a C++ function that expects a pointer or reference?
???? Standard containers like std::shared_ptr?
在Lua訪問C++?
???? 為了在Lua中使用C++中的數(shù)據(jù)和函數(shù),LuaBridge要求任何需要使用的數(shù)據(jù)的都需要注冊。LuaBridge可以注冊下面五種類型數(shù)據(jù):?
???? Namespaces? 一個Lua table包含了其他注冊信息?
???? Data? 全局變量或靜態(tài)變量、數(shù)據(jù)成員或靜態(tài)數(shù)據(jù)成員?
???? Functions? 一般函數(shù)、成員函數(shù)或靜態(tài)成員函數(shù)?
???? CFunctions? A regular function, member function, or static member function that uses the lua_CFunction calling convention?
???? Properties? Global properties, property members, and static property members. These appear like data to Lua,?
???? but are implemented in C++ using functions to get and set the values.
???? Data和Properties在注冊時被標(biāo)記為只讀(read-only)。這不同于const,這些對象的值能在C++中修改,但不能在Lua腳本中修改。
Namespaces?
???? LuaBridge索引的注冊都是在一個namespace中,namespace是從lua角度來看的,它實質(zhì)上就是table,注意這里的namespace不是C++中的namespace,C++的namespace?不是一定需要的。LuaBridge的namespace是對Lua腳本來說的,它們被作為邏輯組合工具(logical grouping tool)。為了訪問Lua的全局命名空間(global namespace),可以在C++?中,這樣調(diào)用:?
getGlobalNamespace (L); 上面的調(diào)用會返回一個對象(實質(zhì)是table)可用來進一步注冊,比如:?
getGlobalNamespace (L).beginNamespace ("test"); 上面的調(diào)用就會在Lua的_G中創(chuàng)建一個名為"test"的table,現(xiàn)在這個table還是空的。LuaBridge保留所有以雙下劃線開頭命名的標(biāo)識,因此__test是無效的命名,?盡管這樣命名LuaBridge不會報錯。我們可以進一步擴展上面的注冊:?
getGlobalNamespace (L).beginNamespace ("test").beginNamespace ("detail").endNamespace ().beginNamespace ("utility").endNamespace ().endNamespace (); 這樣注冊后,我們就可以在Lua中使用test, test.detail,和test.utility。這里的引入的endNamespace函數(shù),也會返回一個對象(實質(zhì)也是table),該對象實質(zhì)就是上一層namespace,?表示當(dāng)前namespace注冊完成。 All LuaBridge functions which create registrations return an object upon which subsequent registrations can be made,?allowing for an unlimited number of registrations to be chained together using the dot operator。在一個namespace中,注冊相同命名的對象,對于LuaBridge來說是沒有?定義的行為。一個namespace可以多次使用增加更多的成員。比如下面兩段代碼是等價的:?
getGlobalNamespace (L).beginNamespace ("test").addFunction ("foo", foo).endNamespace ();getGlobalNamespace (L).beginNamespace ("test").addFunction ("bar", bar).endNamespace (); 和?
getGlobalNamespace (L).beginNamespace ("test").addFunction ("foo", foo).addFunction ("bar", bar).endNamespace (); Data, Properties, Functions, and CFunctions?
???? Data, Properties, Functions, and CFunctions可以依次使用addVariable,, addProperty, addFunction, and addCFunction來注冊。在Lua腳本中調(diào)用注冊的函數(shù)時,?LuaBridge會自動地傳入相應(yīng)的參數(shù),并對參數(shù)類型轉(zhuǎn)和檢查。同樣,函數(shù)的返回值也會自動處理。當(dāng)前LuaBridge最多可處理8個參數(shù)。Pointers, references, and objects?of class type as parameters are treated specially。如果我們在C++中有以下定義:?
int globalVar;static float staticVar;std::string stringProperty;std::string getString () { return stringProperty; }void setString (std::string s) { stringProperty = s; }int foo () { return 42; }void bar (char const*) { }int cFunc (lua_State* L) { return 0; } 為了在Lua使用這些變量和函數(shù),我們可以按以下方式注冊它們:?
getGlobalNamespace (L).beginNamespace ("test").addVariable ("var1", &globalVar).addVariable ("var2", &staticVar, false) // read-only.addProperty ("prop1", getString, setString).addProperty ("prop2", getString) // read only.addFunction ("foo", foo).addFunction ("bar", bar).addCFunction ("cfunc", cFunc).endNamespace (); Variables在注冊時,可以通過傳遞第二個參數(shù)為false,確保Variables不會在Lua被修改,默認第二個參數(shù)是true。Properties在注冊時,若不傳遞set函數(shù),則在腳本中是read-only。?
通過上面注冊后,則下面表達式在Lua是有效的:?
test -- a namespace,實質(zhì)就是一個table,下面都是table中的成員test.var1 -- a lua_Number variabletest.var2 -- a read-only lua_Number variabletest.prop1 -- a lua_String propertytest.prop2 -- a read-only lua_String propertytest.foo -- a function returning a lua_Numbertest.bar -- a function taking a lua_String as a parametertest.cfunc -- a function with a variable argument list and multi-return 注意test.prop1和test.prop2引用的C++中同一個變量,然后test.prop2是read-only,因此在腳本中對test.prop2賦值,會導(dǎo)致運行時錯誤(run-time error)。在Lua按以下方式使用:?
test.var1 = 5 -- okaytest.var2 = 6 -- error: var2 is not writabletest.prop1 = "Hello" -- okaytest.prop1 = 68 -- okay, Lua converts the number to a string.test.prop2 = "bar" -- error: prop2 is not writabletest.foo () -- calls foo and discards the return valuetest.var1 = foo () -- calls foo and stores the result in var1test.bar ("Employee") -- calls bar with a stringtest.bar (test) -- error: bar expects a string not a table
Class Objects?
類的注冊是以beginClass或deriveClass開始,以endClass結(jié)束。一個類注冊完后,還可以使用beginClass重新注冊更多的信息,但是deriveClass只能被使用一次。為了給已經(jīng)用deriveClass注冊的類,注冊更多的信息,可以使用beginClass。?
class A { public:A() { printf("A constructor\n");}static int staticData;static int getStaticData() {return staticData;}static float staticProperty;static float getStaticProperty () { return staticProperty; }static void setStaticProperty (float f) { staticProperty = f; }static int staticCFunc (lua_State *L) { return 0; }std::string dataMember;char dataProperty;char getProperty () const { return dataProperty; }void setProperty (char v) { dataProperty = v; }void func1 () {printf("func1 In Class A\n"); }virtual void virtualFunc () {printf("virtualFunc In Class A\n"); }int cfunc (lua_State* L) { printf("cfunc In Class A\n"); return 0; }};class B : public A {public:B() { printf("B constructor\n");}double dataMember2;void func1 () {printf("func1 In Class B\n"); }void func2 () { printf("func2 In Class B\n"); }void virtualFunc () {printf("virtualFunc In Class B\n"); }};int A::staticData = 3;float A::staticProperty = 0.5; 按下面方式注冊:?
getGlobalNamespace (L).beginNamespace ("test").beginClass<A>("A").addConstructor <void (*) (void)> ().addStaticData ("staticData", &A::staticData).addStaticProperty ("staticProperty", &A::getStaticData).addStaticFunction ("getStaticProperty", &A::getStaticProperty) //read-only.addStaticCFunction ("staticCFunc", &A::staticCFunc).addData ("data", &A::dataMember).addProperty ("prop", &A::getProperty, &A::setProperty).addFunction ("func1", &A::func1).addFunction ("virtualFunc", &A::virtualFunc).addCFunction ("cfunc", &A::cfunc).endClass ().deriveClass<B, A>("B").addConstructor <void (*) (void)> ().addData ("data", &B::dataMember2).addFunction ("func1", &B::func1).addFunction ("func2", &B::func2).endClass ().endNamespace (); 注冊后,可以再Lua腳本中按一下方式使用:?
local AClassObj = test.A () --create class A instanceprint("before:",test.A.staticData) -- access class A static member test.A.staticData = 8 -- modify class A static member print("after:",test.A.staticData) print("before:", test.A.getStaticProperty()) --test.A.staticProperty = 1.2 --error:can not modify print("staticCFunc")test.A.staticCFunc()AClassObj.data = "sting"print("dataMember:",AClassObj.data)AClassObj.prop = 'a'print("property:",AClassObj.prop)AClassObj:func1()AClassObj:virtualFunc()AClassObj:cfunc()BClassObj = test.B()BClassObj:func1()BClassObj:func2() BClassObj:virtualFunc() ? ? ???
其輸出結(jié)果為:?
A constructorbefore: 3after: 8before: 0.5staticCFuncdataMember: stingproperty: afunc1 In Class AvirtualFunc In Class Acfunc In Class AA constructorB constructorfunc1 In Class Bfunc2 In Class BvirtualFunc In Class B 類的方法注冊類似于通常的函數(shù)注冊,虛函數(shù)也是類似的,沒有特殊的語法。在LuaBridge中,能識別const方法并且在調(diào)用時有檢測的,因此如果一個函數(shù)返回一個const object或包含指向const object的數(shù)據(jù)給Lua腳本,則在Lua中這個被引用的對象則被認為是const的,它只能調(diào)用const的方法。對于每個類,析構(gòu)函數(shù)自動注冊的。無須在繼承類中重新注冊已在基類中注冊過的方法。If a class has a base class that is **not** registeredwith Lua, there is no need to declare it as a subclass.?
Constructors?
為了在Lua中,創(chuàng)建類的對象,必須用addConstructor為改類注冊構(gòu)造函數(shù)。并且LuaBridge不能自動檢測構(gòu)造函數(shù)的參數(shù)個數(shù)和類型(這與注冊函數(shù)或方法能自動檢測是不同的),因此在用注冊addConstructor時必須告訴LuaBridge在Lua腳本將用到的構(gòu)造函數(shù)簽名,例如:?
struct A {A ();};struct B {explicit B (char const* s, int nChars);};getGlobalNamespace (L).beginNamespace ("test").beginClass <A> ("A").addConstructor <void (*) (void)> ().endClass ().beginClass <B> ("B").addConstructor <void (*) (char const*, int)> ().endClass ();.endNamespace () 在Lua中,就可以一些方式,創(chuàng)建A和B的實例:?
a = test.A () -- Create a new A.b = test.B ("hello", 5) -- Create a new B.b = test.B () -- Error: expected string in argument 1 lua_State*?
有時候綁定的函數(shù)或成員函數(shù),需要lua_State*作為參數(shù)來訪問棧。使用LuaBridge,只需要在將要綁定的函數(shù)最后添加lua_State*類型的參數(shù)即可。比如:?
void useStateAndArgs (int i, std::string s, lua_State* L); getGlobalNamespace (L).addFunction ("useStateAndArgs", &useStateAndArgs); 在Lua中,就可按以下方式使用:?
useStateAndArgs(42,"hello") 在腳本中,只需傳遞前面兩個參數(shù)即可。注意 lua_State*類型的參數(shù)就放在定義的函數(shù)最后,否則結(jié)果是未定義的。?
Class Object Types?
一個注冊的類型T,可能以下方式傳遞給Lua腳本:?
`T*` or `T&`: Passed by reference, with _C++ lifetime_.`T const*` or `T const&`: Passed by const reference, with _C++ lifetime_.`T` or `T const`: Passed by value (a copy), with _Lua lifetime_. C++ Lifetime?
對于C++ lifetime的對象,其創(chuàng)建和刪除都由C++代碼控制,Lua GC不能回收這些對象。當(dāng)Lua通過lua_State*來引用對象時,必須確保該對象還沒刪除,否則將導(dǎo)致未定義的行為。例如,可按以下方法給Lua傳遞?
C++ lifetime的對象:?
A a;push (L, &a); // pointer to 'a', C++ lifetimelua_setglobal (L, "a");push (L, (A const*)&a); // pointer to 'a const', C++ lifetimelua_setglobal (L, "ac");push <A const*> (L, &a); // equivalent to push (L, (A const*)&a)lua_setglobal (L, "ac2");push (L, new A); // compiles, but will leak memorylua_setglobal (L, "ap"); Lua Lifetime 當(dāng)C++通過值傳遞給Lua一個對象時,則該對象是Lua lifetime。在值傳遞時,該對象將在Lua中以userdata形式保存,并且當(dāng)Lua不再引用該對象時,該對象可以被GC回收。當(dāng)userdata被回收時,其相應(yīng)對象的?
析構(gòu)函數(shù)也會被調(diào)用。在C++中應(yīng)用lua lifetime的對象時,必須確保該對象還沒被GC回收,否則其行為是未定義的。例如,可按以下方法給Lua傳遞的是Lua lifetime的催下:?
B b;push (L, b); // Copy of b passed, Lua lifetime.lua_setglobal (L, "b"); 當(dāng)在Lua中調(diào)用注冊的構(gòu)造函數(shù)創(chuàng)建一個對象時,該對象同樣是Lua lifetime的,當(dāng)該對象不在被引用時,GC會自動回收該對象。當(dāng)然你可以把這個對象引用作為參數(shù)傳遞給C++,但需要保證C++在通過引用使用該對象時,?
改對還沒有被GC回收。?
?Pointers, References, and Pass by Value?
當(dāng)C++對象作為參數(shù)從Lua中傳回到C++代碼中時,LuaBridge會盡可能做自動轉(zhuǎn)換。比如,向Lua中注冊了以下C++函數(shù):?
void func0 (A a); void func1 (A* a); void func2 (A const* a); void func3 (A& a); void func4 (A const& a); 則在Lua中,就可以按以下方式調(diào)用上面的函數(shù):?
func0 (a) -- Passes a copy of a, using A's copy constructor. func1 (a) -- Passes a pointer to a. func2 (a) -- Passes a pointer to a const a. func3 (a) -- Passes a reference to a. func4 (a) -- Passes a reference to a const a. 上面所有函數(shù),都可以通過a訪問對象的成員以及方法。并且通常的C++的繼承和指針傳遞規(guī)則也使用。比如:?
void func5 (B b); void func6 (B* b); 在lua中調(diào)用:?
func5 (b) - Passes a copy of b, using B's copy constructor. func6 (b) - Passes a pointer to b. func6 (a) - Error: Pointer to B expected. func1 (b) - Okay, b is a subclass of a. 當(dāng)C++給Lua傳遞的指針是NULL時,LuaBridge會自動轉(zhuǎn)換為nil代替。反之,當(dāng)Lua給C++傳遞的nil,相當(dāng)于給C++傳遞了一個NULL指針。
總結(jié)
以上是生活随笔為你收集整理的用LuaBridge为Lua绑定C/C++对象的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TCP解决connect函数的超时问题
- 下一篇: 读书-悟