下面的这个例子是用于展示,对于一个没有向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.
本例子运行完后有报错,请无视,毕竟这只是一个功能验证单元,我觉得它已经很好的完成了它的使命( ╯□╰ )
原文地址:https://www.cnblogs.com/jallyx/p/9992247.html