最近爲了總結Lua綁定C/C++對象的各類方法、第三方庫和原理,學習了LuaBridge庫爲Lua綁定C/C++對象,下面是學習筆記,實質是對該庫的Reference Manual基本上翻譯了一遍,學習過程當中測試代碼,放在個人github上。 html
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調用,從而能夠再Lua腳本中使用C++程序中的類和函數。
爲了能在C++中使用Lua的數據,好比number,string,table以及方便調用Lua的函數,使用LuaBridge中的LuaRef類,能夠方便作到。
LuaBridge設計原則
因爲LuaBridge的設計目標儘量方便使用,好比只有頭文件、沒有用到高級C++的語法、不須要配置。所以LuaBridge性能雖足夠好,但並非最好的,好比
OOLua(https://code.google.com/p/oolua/)執行效率就比它好,而且它也不像LuaBind(http://www.rasterbar.com/products/luabind.html)那樣功能全面。
LuaBridge不支持下面特性:
枚舉型常量
不支持8個以上的函數或方法的調用
重載函數、方法和構造函數(Overloaded functions, methods, or constructors)
全局變量(變量必須被包裝在命名空間裏)
自動地轉換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++中的數據和函數,LuaBridge要求任何須要使用的數據的都須要註冊。LuaBridge能夠註冊下面五種類型數據:
Namespaces 一個Lua table包含了其餘註冊信息
Data 全局變量或靜態變量、數據成員或靜態數據成員
Functions 通常函數、成員函數或靜態成員函數
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在註冊時被標記爲只讀(read-only)。這不一樣於const,這些對象的值能在C++中修改,但不能在Lua腳本中修改。
Namespaces
LuaBridge索引的註冊都是在一個namespace中,namespace是從lua角度來看的,它實質上就是table,注意這裏的namespace不是C++中的namespace,C++的namespace
不是必定須要的。LuaBridge的namespace是對Lua腳原本說的,它們被做爲邏輯組合工具(logical grouping tool)。爲了訪問Lua的全局命名空間(global namespace),能夠在C++
中,這樣調用:
上面的調用會返回一個對象(實質是table)可用來進一步註冊,好比:
- getGlobalNamespace (L)
- .beginNamespace ("test")
上面的調用就會在Lua的_G中建立一個名爲"test"的table,如今這個table仍是空的。LuaBridge保留全部以雙下劃線開頭命名的標識,所以__test是無效的命名,
儘管這樣命名LuaBridge不會報錯。咱們能夠進一步擴展上面的註冊:
- getGlobalNamespace (L)
- .beginNamespace ("test")
- .beginNamespace ("detail")
- .endNamespace ()
- .beginNamespace ("utility")
- .endNamespace ()
- .endNamespace ();
這樣註冊後,咱們就能夠在Lua中使用test, test.detail,和test.utility。這裏的引入的endNamespace函數,也會返回一個對象(實質也是table),該對象實質就是上一層namespace,
表示當前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腳本中調用註冊的函數時,
LuaBridge會自動地傳入相應的參數,並對參數類型轉和檢查。一樣,函數的返回值也會自動處理。當前LuaBridge最多可處理8個參數。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使用這些變量和函數,咱們能夠按如下方式註冊它們:
- 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在註冊時,能夠經過傳遞第二個參數爲false,確保Variables不會在Lua被修改,默認第二個參數是true。Properties在註冊時,若不傳遞set函數,則在腳本中是read-only。
經過上面註冊後,則下面表達式在Lua是有效的:
- test -- a namespace,實質就是一個table,下面都是table中的成員
- test.var1 -- a lua_Number variable
- test.var2 -- a read-only lua_Number variable
- test.prop1 -- a lua_String property
- test.prop2 -- a read-only lua_String property
- test.foo -- a function returning a lua_Number
- test.bar -- a function taking a lua_String as a parameter
- test.cfunc -- a function with a variable argument list and multi-return
注意test.prop1和test.prop2引用的C++中同一個變量,而後test.prop2是read-only,所以在腳本中對test.prop2賦值,會致使運行時錯誤(run-time error)。在Lua按如下方式使用:
- test.var1 = 5 -- okay
- test.var2 = 6 -- error: var2 is not writable
- test.prop1 = "Hello" -- okay
- test.prop1 = 68 -- okay, Lua converts the number to a string.
- test.prop2 = "bar" -- error: prop2 is not writable
-
- test.foo () -- calls foo and discards the return value
- test.var1 = foo () -- calls foo and stores the result in var1
- test.bar ("Employee") -- calls bar with a string
- test.bar (test) -- error: bar expects a string not a table
Class Objects
類的註冊是以beginClass或deriveClass開始,以endClass結束。一個類註冊完後,還可使用beginClass從新註冊更多的信息,可是deriveClass只能被使用一次。爲了給已經用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 instance
-
- print("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()
其輸出結果爲:
- A constructor
- before: 3
- after: 8
- before: 0.5
- staticCFunc
- dataMember: sting
- property: a
- func1 In Class A
- virtualFunc In Class A
- cfunc In Class A
- A constructor
- B constructor
- func1 In Class B
- func2 In Class B
- virtualFunc In Class B
類的方法註冊相似於一般的函數註冊,虛函數也是相似的,沒有特殊的語法。在LuaBridge中,能識別const方法而且在調用時有檢測的,所以若是一個函數返回一個const object或包含指向const object的數據給Lua腳本,則在Lua中這個被引用的對象則被認爲是const的,它只能調用const的方法。對於每一個類,析構函數自動註冊的。無須在繼承類中從新註冊已在基類中註冊過的方法。If a class has a base class that is **not** registeredwith Lua, there is no need to declare it as a subclass.
Constructors
爲了在Lua中,建立類的對象,必須用addConstructor爲改類註冊構造函數。而且LuaBridge不能自動檢測構造函數的參數個數和類型(這與註冊函數或方法能自動檢測是不一樣的),所以在用註冊addConstructor時必須告訴LuaBridge在Lua腳本將用到的構造函數簽名,例如:
- 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中,就能夠一些方式,建立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*
有時候綁定的函數或成員函數,須要lua_State*做爲參數來訪問棧。使用LuaBridge,只須要在將要綁定的函數最後添加lua_State*類型的參數便可。好比:
- void useStateAndArgs (int i, std::string s, lua_State* L);
- getGlobalNamespace (L).addFunction ("useStateAndArgs", &useStateAndArgs);
在Lua中,就可按如下方式使用:
- useStateAndArgs(42,"hello")
在腳本中,只需傳遞前面兩個參數便可。注意 lua_State*類型的參數就放在定義的函數最後,不然結果是未定義的。
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的對象,其建立和刪除都由C++代碼控制,Lua GC不能回收這些對象。當Lua經過lua_State*來引用對象時,必須確保該對象還沒刪除,不然將致使未定義的行爲。例如,可按如下方法給Lua傳遞
C++ lifetime的對象:
- A a;
-
- push (L, &a); // pointer to 'a', C++ lifetime
- lua_setglobal (L, "a");
-
- push (L, (A const*)&a); // pointer to 'a const', C++ lifetime
- lua_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 memory
- lua_setglobal (L, "ap");
Lua Lifetime
當C++經過值傳遞給Lua一個對象時,則該對象是Lua lifetime。在值傳遞時,該對象將在Lua中以userdata形式保存,而且當Lua再也不引用該對象時,該對象能夠被GC回收。當userdata被回收時,其相應對象的
析構函數也會被調用。在C++中應用lua lifetime的對象時,必須確保該對象還沒被GC回收,不然其行爲是未定義的。例如,可按如下方法給Lua傳遞的是Lua lifetime的催下:
- B b;
-
- push (L, b); // Copy of b passed, Lua lifetime.
- lua_setglobal (L, "b");
當在Lua中調用註冊的構造函數建立一個對象時,該對象一樣是Lua lifetime的,當該對象不在被引用時,GC會自動回收該對象。固然你能夠把這個對象引用做爲參數傳遞給C++,但須要保證C++在經過引用使用該對象時,
改對尚未被GC回收。
Pointers, References, and Pass by Value
當C++對象做爲參數從Lua中傳回到C++代碼中時,LuaBridge會盡量作自動轉換。好比,向Lua中註冊瞭如下C++函數:
- void func0 (A a);
- void func1 (A* a);
- void func2 (A const* a);
- void func3 (A& a);
- void func4 (A const& a);
則在Lua中,就能夠按如下方式調用上面的函數:
- 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.
上面全部函數,均可以經過a訪問對象的成員以及方法。而且一般的C++的繼承和指針傳遞規則也使用。好比:
- void func5 (B b);
- void func6 (B* b);
在lua中調用:
- 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.
當C++給Lua傳遞的指針是NULL時,LuaBridge會自動轉換爲nil代替。反之,當Lua給C++傳遞的nil,至關於給C++傳遞了一個NULL指針。