Lua的C++绑定库(二)

下面的这个例子是用于展示,对于一个没有向LuaBus注册的类(本例中为类GCTest2),绑定库依然会在对象生命周期结束后,调用其析构函数。其实这算不上一个特性,只是luatinker未能正确处理这种情况,我需要证明LuaBus是不存在这种错误的。如果一个lua的绑定库没有处理这种情况,它将可能会导致严重的资源泄漏问题。

 1 #include "lua/LuaFunc.h"
 2 
 3 struct GCTest1 {
 4     GCTest1() { printf("GCTest1()\n"); }
 5     GCTest1(const GCTest1&) { printf("GCTest(const GCTest1&)\n"); }
 6     ~GCTest1() { printf("~GCTest1()\n"); }
 7 };
 8 GCTest1 getGCTest1() { return GCTest1(); }
 9 
10 struct GCTest2 {
11     GCTest2() { printf("GCTest2()\n"); }
12     GCTest2(const GCTest2&) { printf("GCTest(const GCTest2&)\n"); }
13     ~GCTest2() { printf("~GCTest2()\n"); }
14 };
15 GCTest2 getGCTest2() { return GCTest2(); }
16 
17 const char *lua_str = R"(
18     a1 = getGCTest1()
19     print(a1)
20     a1 = nil
21     a2 = getGCTest2()
22     print(a2)
23     a2 = nil
24     collectgarbage()
25  )";
26 
27 int main(int argc, char **argv)
28 {
29     lua_State *L = lua::open();
30 
31     lua::class_add<GCTest1>(L, "GCTest1");
32 
33     lua::def(L, "getGCTest1", getGCTest1);
34     lua::def(L, "getGCTest2", getGCTest2);
35     lua::dostring(L, lua_str);
36 
37     lua::close(L);
38     return 0;
39 }

 程序输出:

GCTest1()
GCTest(const GCTest1&)
GCTest(const GCTest1&)
GCTest(const GCTest1&)
~GCTest1()
~GCTest1()
~GCTest1()
GCTest1: 00000000004BF218
GCTest2()
GCTest(const GCTest2&)
GCTest(const GCTest2&)
GCTest(const GCTest2&)
~GCTest2()
~GCTest2()
~GCTest2()
(_GC_META_): 00000000004BF318
~GCTest2()
~GCTest1()

 下面的例子用于演示c++的多继承,因为效率的原因,LuaBus并不直接支持多继承,但是提供了一种方式让脚本能够访问多继承的父类。

 1 #include "lua/LuaFunc.h"
 2 
 3 struct BaseA {
 4     void testA() { printf("testA()\n"); }
 5 };
 6 struct BaseB {
 7     void testB() { printf("testB()\n"); }
 8 };
 9 struct SubAB : public BaseA, public BaseB {
10     void testAB() { printf("testAB()\n"); }
11 };
12 
13 const char *lua_str = R"(
14     ab = SubAB()
15     ab:testAB() --调用本对象的方法
16     ab:testA() -- 调用父类的方法
17     b = ab:castToBaseB() --获取本对象的其它父类
18     b:testB() --调用其它父类的方法
19  )";
20 
21 int main(int argc, char **argv)
22 {
23     lua_State *L = lua::open();
24 
25     lua::class_add<BaseA>(L, "BaseA");
26     lua::class_def<BaseA>(L, "testA", &BaseA::testA);
27     lua::class_add<BaseB>(L, "BaseB");
28     lua::class_def<BaseB>(L, "testB", &BaseB::testB);
29     lua::class_add<SubAB>(L, "SubAB");
30     lua::class_con<SubAB>(L, lua::constructor<SubAB>);
31     lua::class_inh<SubAB, BaseA>(L); //向脚本注册父类
32     lua::class_cast<SubAB, BaseB>(L); //向脚本注册其它父类
33     lua::class_def<SubAB>(L, "testAB", &SubAB::testAB);
34     lua::dostring(L, lua_str);
35 
36     lua::close(L);
37     return 0;
38 }

程序输出:

testAB()
testA()
testB()

 对于上面的例子,LuaBus要求向脚本注册的父类必须为子类的第一个父类,否则脚本调用回到c++层后,父类的指针将会是错误的。LuaBus提供了一种方式可以绕开这个限制,让需要被注册为父类的类继承于lua::tracker。这个特性可能在重构祖传代码时会用到,如果全新设计的代码也需要这个特性,请让我表示无语了。

 1 #include "lua/LuaFunc.h"
 2 
 3 struct BaseA {
 4     void testA() { printf("testA(%p)\n", this); }
 5 };
 6 struct BaseB : public lua::tracer { //这个类可以在脚本中被注册为SubAB的父类,而且也只能是这个类被注册为SubAB的父类。
 7     void testB() { printf("testB(%p)\n", this); }
 8 };
 9 struct SubAB : public BaseA, public BaseB {
10     void testAB() { printf("testAB(%p)\n", this); }
11 };
12 
13 const char *lua_str = R"(
14     ab:testB() --验证c++层能够获得正确的this指针
15  )";
16 
17 int main(int argc, char **argv)
18 {
19     lua_State *L = lua::open();
20 
21     auto ab = SubAB();
22     ab.testAB();
23     ab.testA();
24     ab.testB();
25 
26     lua::class_add<BaseA>(L, "BaseA");
27     lua::class_def<BaseA>(L, "testA", &BaseA::testA);
28     lua::class_add<BaseB>(L, "BaseB");
29     lua::class_def<BaseB>(L, "testB", &BaseB::testB);
30     lua::class_add<SubAB>(L, "SubAB");
31     lua::class_inh<SubAB, BaseB>(L);
32     lua::class_def<SubAB>(L, "testAB", &SubAB::testAB);
33     lua::set(L, "ab", &ab);
34     lua::dostring(L, lua_str);
35 
36     lua::close(L);
37     return 0;
38 }

程序输出:

testAB(00000000002CF7F4)
testA(00000000002CF7F4)
testB(00000000002CF7F5)
testB(00000000002CF7F5)

 LuaBus的核心特性,处理野指针问题。让需要被注册的类继承于lua::binder即可,这样我们就可以在脚本层用is_object_alive()接口来判断当前指针是否有效,即便是在脚本层直接操纵了野指针,错误也只会发生在脚本层,而不会抛出到C++层,继而导致程序崩溃。

#include "lua/LuaBus.h"

struct PtrTest : lua::binder {
	void test() { printf("test(%p)\n", this); }
};

const char *lua_str = R"(
	print(pt1:is_object_alive()) --检查指针的有效性
	print(pt1, pt2, pt1 == pt2) --指针的相等性测试
	pt1:test() --验证对象的可用性,如果对象是野指针,脚本层就会抛出错误,但是绑定库会捕获它。
	print('ok') --如果执行了这行代码,就表明脚本层没有发生任何错误。
 )";

int main(int argc, char **argv)
{
	lua_State *L = lua::open();

	lua::class_add<PtrTest>(L, "PtrTest");
	lua::class_def<PtrTest>(L, "test", &PtrTest::test);

	auto pt = new PtrTest();
	lua::set(L, "pt1", pt);
	lua::set(L, "pt2", pt);
	lua::dostring(L, lua_str);  // 这次是正确的,所以一切正常。

	delete pt;  // 这会让脚本层产生野指针。
	lua::dostring(L, lua_str);  // 这次脚本层会因为野指针问题发生错误。

	printf("all ok.\n");  // 验证脚本层野指针错误不会导致程序崩溃。
	lua::close(L);
    return 0;
}

 程序输出:

true
PtrTest: 00000000006BEEE8       PtrTest: 00000000006BEEE8       true
test(00000000006B8150)
ok
false
PtrTest: 00000000006BEEE8       PtrTest: 00000000006BEEE8       true
[string "dobuffer()"]:4: userdata[00000000006BEEE8] is a nil value.
        <call stack>
->      unknown : line -1 [[C] : line -1]
        test() : line -1 [[C] : line -1]
        unknown : line 4 [[string "dobuffer()"] : line 0]
all ok.

 当某个类继承于lua::binder后,这也给我们带来了另外一个收益,那就是我们能够像写C++代码一样直接用==运算符来判断两个对象是否相等(前提是在没有重载==运算符的情况下),而且速度非常快。从底层来讲,还有另外一个收益,那就是如果我们多次向脚本层传入同一个对象,LuaBus不会多次重复创建userdata,它会重用第一次创建的userdata,由此带来的结果自然是传递参数的速度更快了,内存占用也会更少。

 最后,LuaBus还附带了一些工具类,它们让Lua与C++之间的交互尽量无缝化。小例子看不出来有啥用处,不过对于大型项目来说,这些功能几乎是必备的。如果没有这些工具类的加持,做项目时,可能就相当于不用lua绑定库,直接基于lua的c接口撸代码吧。

下面的例子简单介绍了这个工具类的作用与用法,更多功能请自行查看相关类的接口。

#include "lua/LuaFunc.h"

struct Test {
	Test() : a(0) {}
	Test(int a) : a(a) {}
	void test() { printf("a = %d\n", a); }
	int a;
};

LuaRef tmp;
void SaveTmp(LuaRef f) {
	tmp = std::move(f);
}

const char *lua_str = R"(
	v1, v2 = Test(), Test_int(3)

	t = {}
	t.print1 = function(arg)
		print('print1', t, arg)
		v1:test()
	end
	t.print2 = function(arg)
		print('print2', t, arg)
		v2:test()
	end

	f = function()
		print('all ok.')
	end

	r = function()
		print('ref ok.')
	end

	SaveTmp(r) -- 将变量保存到C++层
)";

int main(int argc, char **argv)
{
	lua_State *L = lua::open();

	// 给类注册多个构造函数
	lua::class_add<Test>(L, "Test");
	lua::class_con<Test>(L, lua::constructor<Test>);  // 1
	lua::class_con(L, "Test_int", lua::constructor<Test, int>);  // 2
	lua::class_def<Test>(L, "test", &Test::test);
	lua::def(L, "SaveTmp", SaveTmp);
	lua::dostring(L, lua_str);

	// 给变量'f'创建一个引用,引用不会受到lua堆栈影响;
	// 我们后续会对变量'f'进行操作,但是它也不会影响到本引用的值。
	LuaRef r(L, "f");

	// 调用lua table的函数
	LuaFuncs tfs(L, "t");
	tfs.CallMethod<void>("print1"); // 会将本对象作为第一个参数传入,类似于C++调用成员函数。
	tfs.CallStaticMethod<void>("print2"); // 不会将本对象作为参数传入,类似于C++调用静态成员函数。

	// 调用lua的全局函数
	LuaFunc f(L, "f");
	f.Call<void>();

	// 将变量'f'置空,变量'f'不再有效。
	lua::set(L, "f", nullptr);

	// 验证变量'f'的状态
	LuaFunc f1(L, "f");
	f1.Call<void>();

	// 验证引用的作用。
	r.Get<LuaFunc>().Call<void>();

	// 在实际应用中,我们可以把lua的临时变量长久保存在C++层,被保存的变量不会被lua回收。
	// 这可能也是LuaRef最多的用法。
	tmp.Get<LuaFunc>().Call<void>();

	lua::close(L);
    return 0;
}

  程序输出:

print1  table: 00000000005BF100 table: 00000000005BF100
a = 0
print2  table: 00000000005BF100 nil
a = 3
all ok.
lua attempt to call global 'f' (not a function)
all ok.
ref ok.

本例子运行完后有报错,请无视,毕竟这只是一个功能验证单元,我觉得它已经很好的完成了它的使命( ╯□╰ )

猜你喜欢

转载自www.cnblogs.com/jallyx/p/9992247.html