加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

用LuaBridge为Lua绑定C/C++对象

发布时间:2020-12-14 21:52:50 所属栏目:大数据 来源:网络整理
导读:最近为了总结Lua绑定C/C++对象的各种方法、第三方库和原理,学习了LuaBridge库为Lua绑定C/C++对象,下面是学习笔记,实质是对该库的? Reference Manual ?基本上翻译了一遍,学习过程中测试代码,放在? 我的github ?上。 LuaBridge的主要特点 ? ???? 源码只有

最近为了总结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.


???? 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);
上面的调用会返回一个对象(实质是table)可用来进一步注册,比如:?
     getGlobalNamespace (L)
            .beginNamespace ("test");
上面的调用就会在Lua的_G中创建一个名为"test"的table,现在这个table还是空的。LuaBridge保留所有以双下划线开头命名的标识,因此__test是无效的命名,?尽管这样命名LuaBridge不会报错。我们可以进一步扩展上面的注册:?
    getGlobalNamespace (L)
           "test")
          "detail")
          .endNamespace ()
          "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可以多次使用增加更多的成员。比如下面两段代码是等价的:?
.endNamespace ();
Data,Properties,Functions,and CFunctions?
???? Data,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 (string s) { stringProperty = s; }

	int foo () { return 42; }
	bar (char const*) { }
	cFunc (lua_State* L) { 0; }
为了在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 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 constructorn");}

		int staticData;
		getStaticData() {return staticData;}

		float staticProperty;
		float getStaticProperty () { return staticProperty; }
		setStaticProperty (float f) { staticProperty = f; }

		staticCFunc (lua_State *L) { 0; }

		string dataMember;

		char dataProperty;
		char getProperty () const { return dataProperty; }
		setProperty (char v) { dataProperty = v; }


		func1 () {"func1 In Class An"); }
		virtual virtualFunc () {"virtualFunc In Class An");  }

		cfunc (lua_State* L) { "cfunc In Class An");  0; }
	};

	class B : public A {
	    public:
		B() { "B constructorn");}

		double dataMember2;

		"func1 In Class Bn"); }
		func2 () { "func2 In Class Bn"); }
		"virtualFunc In Class Bn");  }
	};

	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",0)">A::getStaticData)
		.addStaticFunction ("getStaticProperty",0)">A::getStaticProperty) //read-only
		.addStaticCFunction ("staticCFunc",0)">A::staticCFunc)
		.addData ("data",0)">A::dataMember)
		.addProperty ("prop",0)">A::getProperty,0)">A::setProperty)
		.addFunction ("func1",0)">A::func1)
		.addFunction ("virtualFunc",0)">A::virtualFunc)
		.addCFunction (A::cfunc)
	    .endClass ()
	    .deriveClass<B,A>("B")
		.addConstructor <void)> ()
		.addData (B::dataMember2)
		.addFunction (B::func1)
		.addFunction ("func2",0)">B::func2)
	    .endClass ()
	.endNamespace ();
注册后,可以再Lua脚本中按一下方式使用:?
local AClassObj = test.A ()  --create class A instance

	print("before:",test.A.staticData) -- access static member                                     
	test.A.staticData = 8  -- modify member                                                 
	"after:",test.A.staticData)                                                                      

	1.2 --error:can not modify                                                   
		
	"staticCFunc")
	test.A.staticCFunc()
			
	AClassObj.data = "sting"
	"dataMember:",AClassObj.data)
			
	AClassObj.prop = 'a'
	"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 Class A
	cfunc Class A
	A B constructor
	func1 In Class B
	func2 virtualFunc 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脚本将用到的构造函数签名,例如:?
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*类型的参数即可。比如:?
useStateAndArgs (int i,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` 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 ("a")A const*);   // pointer to 'a const',0)">"ac");

	push <A const*> (;  // equivalent to push (L,(A const*)&a)
	lua_setglobal ("ac2");	    // compiles,but will leak memory
	lua_setglobal ("ap");
Lua Lifetime
当C++通过值传递给Lua一个对象时,则该对象是Lua lifetime。在值传递时,该对象将在Lua中以userdata形式保存,并且当Lua不再引用该对象时,该对象可以被GC回收。当userdata被回收时,其相应对象的?
析构函数也会被调用。在C++中应用lua lifetime的对象时,必须确保该对象还没被GC回收,否则其行为是未定义的。例如,可按以下方法给Lua传递的是Lua lifetime的催下:?
B b;

      push (;                    // Copy of b passed,Lua lifetime.
      lua_setglobal ("b");
当在Lua中调用注册的构造函数创建一个对象时,该对象同样是Lua lifetime的,当该对象不在被引用时,GC会自动回收该对象。当然你可以把这个对象引用作为参数传递给C++,但需要保证C++在通过引用使用该对象时,?
改对还没有被GC回收。?
?Pointers,References,and Pass by Value?
当C++对象作为参数从Lua中传回到C++代码中时,LuaBridge会尽可能做自动转换。比如,向Lua中注册了以下C++函数:?
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++的继承和指针传递规则也使用。比如:?
func5 (b)   - Passes a copy of b,51); font-weight:700">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指针。? ? ? ?

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读