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.

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

原文地址:https://www.cnblogs.com/jallyx/p/9992247.html

时间: 2024-10-14 06:33:20

Lua的C++绑定库(二)的相关文章

Lua的C++绑定库(一)

C++是一门非常复杂的语言,然而更可怕的是,这门语言的各种奇葩特性还在继续增加,很多C++的程序员都会觉得这完全是在给自己添堵嘛,都已经这么复杂了,何必还要使劲往里面塞东西呢,明明C++03或者说是C++98的标准就已经完全够用了.我个人的看法呢,其实后续的标准还是很有必要的,因为这里面的很多新标准,对于一些写底层库的作者来说,真的是非常需要,换句话说,如果没有type_traits.右值语义.可变参数模板这些特性,那我就不会重新造这个轮子,也就不会写这篇文章了,正是因为C++11的标准里面引入

《Programming in Lua 3》读书笔记(二十一)

日期:2014.8.1 PartⅣ The C API 25 An Overview of the C API Lua是一种嵌入式语言.这就意味着Lua不是单独存在的,而是可以通过一系列的标准库将lua的特性嵌入至其他应用模块中. Lua以Lua interpreter(lua的解释器?)来解决了其不是独立程序,我们直到现在却又能独立使用Lua的问题.这个解释器是一个小型的程序(不超过500行代码),使用lua的标准库来实现独立解释程序,这个程序将处理与用户的交互等操作交给lua的标准库,这些库

LUA和C++绑定的一些天然的麻烦

最近在看Luatinker的源代码,打算自己改(仿写)写搞一个简单的封装C++和LUA的结合的库,结果发现其实麻烦和困惑比想象的多. 比如这些点: 1)有时候使用模板的时候,引用会退化. classtype &,经过模板参数后,会退化为class type,这个在LuaTinker的实现里面就有问题,你会发现tinker的引用就没有起到作用. 这个我打算研究一下<C++模板> 2)基本(非object)类型(比如int)的应用和指针的使用问题? 因为大部分C++ to LUA的参数传递

Go语言开发(十二)、Go语言常用标准库二

Go语言开发(十二).Go语言常用标准库二 一.os 1.os简介 os 包提供了不依赖平台的操作系统函数接口,设计像Unix风格,但错误处理是go风格,当os包使用时,如果失败后返回错误类型而不是错误数量. 2.os常用接口 func Hostname() (name string, err error) // Hostname返回内核提供的主机名 func Environ() []string // Environ返回表示环境变量的格式为"key=value"的字符串的切片拷贝 f

Xamarin.Android绑定库分享

使用Xamarin.Android时,会用到各种第三方库,而这些库基本上是java编写的,要在Xamarin.Android中使用这些库,就需要通过Android Binding Project绑定对应的java库,然后在C#中使用.绑定时,可能需要编写相关的转换规则或者修改对应的java源代码,遇到问题时,可以到Xamarin的官网查看,也可以Google. Xamarin.Android绑定参考文档地址: http://developer.xamarin.com/guides/android

《Programming in Lua 3》读书笔记(二十二)

日期:2014.8.6 PartⅣ The C API 26 Extending Your Application 使用Lua很重要的一点是用来做配置语言.配合主语言做一些功能的配置. 26.1 The Basics 有的时候程序需要配置一些功能信息,很多时候可能有许多别的方法比用lua做配置要更简单:如使用环境变量或者读取文件,读取文件涉及到文件的解析.如果使用Lua进行配置的话,相当于用lua文件替代了要读取的如csv.txt文件等. 使用Lua进行配置的时候,就需要使用Lua API去控制

快速掌握Lua 5.3 —— 字符串库 (3)

Q:什么情况下"pattern"会匹配空串? A:要小心的使用*和-,因为它们可以匹配零次. -- 如果你打算用"%a*"匹配单词,你会发现到处都是单词. print(string.find(";$% **#$hello13", "%a*")) --> 1 0 print(string.find(";$% **#$hello13", "%a*", 6)) --> 6 5 --

用lua nginx module搭建一个二维码

用lua nginx module搭建一个二维码(qr code)生成器 作者 vinoca 發布於 2014年10月31日 如果有VPS,或者开源的路由器,安装一个nginx,添加lua-nginx-module,再编译安装qrencode for lua ,用下面的lua代码,访问http://youip/qr?t=hello就可以看到效果啦: local qr = require "qrencode" local args = ngx.req.get_uri_args() ngx

《Programming in Lua 3》读书笔记(二十三)

日期:2014.8.7 PartⅣ The C API 27 Calling C from Lua 在这里说Lua调用C函数,并不意味着Lua可以调用任意的C函数.与之前C调用Lua函数一样,在这里同样需要遵循一些规则:传递参数,得到结果.不仅如此,Lua要调用C函数,我们首先需要注册这个函数,即需要将该函数的地址传递给Lua. 当Lua调用C函数的时候,也是使用栈来做参数和返回结果的传递.C函数从栈中得到参数,然后将结果push至栈中. 在这里一个重要的概念是:栈不是一个全局结构:每个函数都有