lua bridge
最近为了总结Lua绑定C/C++对象的各种方法、第三方库和原理,学习了LuaBridge库为Lua绑定C/C++对象,下面是学习笔记,实质是对该库的Reference Manual基本上翻译了一遍,学习过程中测试代码,放在我的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调用,从而可以再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.
源码只有头文件,没有.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++中,这样调用:
- getGlobalNamespace (L);
- getGlobalNamespace (L)
- .beginNamespace ("test")
- getGlobalNamespace (L)
- .beginNamespace ("test")
- .beginNamespace ("detail")
- .endNamespace ()
- .beginNamespace ("utility")
- .endNamespace ()
- .endNamespace ();
- 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可以依次使用addVariable,, addProperty, addFunction, and addCFunction来注册。在Lua脚本中调用注册的函数时,LuaBridge会自动地传入相应的参数,并对参数类型转和检查。同样,函数的返回值也会自动处理。当前LuaBridge最多可处理8个参数。Pointers, references, and objectsof 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; }
- 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 ();
通过上面注册后,则下面表达式在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.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 ();
- 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
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 ()
- 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*作为参数来访问栈。使用LuaBridge,只需要在将要绑定的函数最后添加lua_State*类型的参数即可。比如:
- void useStateAndArgs (int i, std::string s, lua_State* L);
- getGlobalNamespace (L).addFunction ("useStateAndArgs", &useStateAndArgs);
- useStateAndArgs(42,"hello")
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的催下:
当在Lua中调用注册的构造函数创建一个对象时,该对象同样是Lua lifetime的,当该对象不在被引用时,GC会自动回收该对象。当然你可以把这个对象引用作为参数传递给C++,但需要保证C++在通过引用使用该对象时,
改对还没有被GC回收。
Pointers, References, and Pass by Value
当C++对象作为参数从Lua中传回到C++代码中时,LuaBridge会尽可能做自动转换。比如,向Lua中注册了以下C++函数:
则在Lua中,就可以按以下方式调用上面的函数:
上面所有函数,都可以通过a访问对象的成员以及方法。并且通常的C++的继承和指针传递规则也使用。比如:
在lua中调用:
当C++给Lua传递的指针是NULL时,LuaBridge会自动转换为nil代替。反之,当Lua给C++传递的nil,相当于给C++传递了一个NULL指针。
析构函数也会被调用。在C++中应用lua lifetime的对象时,必须确保该对象还没被GC回收,否则其行为是未定义的。例如,可按以下方法给Lua传递的是Lua lifetime的催下:
- B b;
- push (L, b); // Copy of b passed, Lua lifetime.
- lua_setglobal (L, "b");
改对还没有被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);
- 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.
- void func5 (B b);
- void func6 (B* b);
- 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.