Lua的C++绑定库(一)

C++是一门非常复杂的语言,然而更可怕的是,这门语言的各种奇葩特性还在继续增加,很多C++的程序员都会觉得这完全是在给自己添堵嘛,都已经这么复杂了,何必还要使劲往里面塞东西呢,明明C++03或者说是C++98的标准就已经完全够用了。我个人的看法呢,其实后续的标准还是很有必要的,因为这里面的很多新标准,对于一些写底层库的作者来说,真的是非常需要,换句话说,如果没有type_traits、右值语义可变参数模板这些特性,那我就不会重新造这个轮子,也就不会写这篇文章了,正是因为C++11的标准里面引入了这些特性,以往一些库的实现就显得很笨重、很复杂,所以我决定重新造一个代码轻量级但是功能却不会有删减的轮子。最后,友情提示那些反感C++新标准的小伙伴一下,其实这些新标准里面大部分的内容我们根本就不必关心,因为对于写逻辑层的功能来说,这些功能我们根本就用不上,能用上的也就一些新增的语法糖,例如auto、foreach、lambda,其它的功能有个毛用啊,不过呢,这些语法糖还是建议了解一下,可以省很多事哦。

我个人最早接触的lua绑定库是ELuna,它是由我的一个同事引入项目中的,并且以此为基础进行了大量魔改操作,然后当我接手这个代码的时候,可读性有点伤人。我后来又在网上搜了一下,感觉luatinker这个库评价挺好的,于是我就想在项目里引入luatinker。可惜的是,luatinker是基于lua5.1写的,而当时lua5.3已经出来了,大家都知道,lua5.3是原生支持64位整数的,而在这之前,所有的lua绑定库为了支持64位整数,都得用一些奇葩手段才能简单支持,最重要的是,我有把64位整数传入脚本的需求,所以只好先做移植了,好不容易移植过去了,写了几个例子一跑,惨了,有bug啊。好吧,那就继续改吧,等这些都做完了,静下来想了想,好像对于这个luatinker自己已经完全掌握了,而且这个库里面还有好多代码是c++11已经原生支持了的,干脆我自己用c++11实现一份吧,这样子代码肯定简洁的多,代码写得越少,bug也就写得越少嘛。所以总结就一句话,以下这个库的api设计跟luatinker几乎完全一致,只要你用过luatinker,那么用这个库,基本上是0成本,除非是luatinker缺失的功能。

好了,开始进入正题。我魔改过的luatinker:https://github.com/jallyx/LuaTinker,新轮子:https://gitee.com/jallyx/fusion/tree/master/src/lua。友情提示,std::string_view是c++17引入的,所以c++11编译这个代码需要先自己处理一下,方案1、删掉这个功能,具体做法就是哪儿编译出错就删哪儿,方案2、使用boost提供的boost::string_view或boost::string_ref,然后这样包装一下https://gitee.com/jallyx/fusion/blob/master/feature/string_view,方案3、使用c++17吧。

让我们先从luatinker的例子走起。

 1 #include "lua/LuaFunc.h"
 2
 3 int cpp_func(int arg1, int arg2) {
 4     return arg1 + arg2;
 5 }
 6
 7 const char *lua_str = R"(
 8     print(‘cpp_func(1,2) = ‘ .. cpp_func(1, 2)) --调用c++全局函数
 9     function lua_func(arg1, arg2) --定义lua全局函数
10         return arg1 + arg2
11     end
12 )";
13
14 int main(int argc, char **argv)
15 {
16     lua_State *L = lua::open();  // 新建lua虚拟机,并做一些必要的初始化工作。
17     lua::def(L, "cpp_func", cpp_func);  // 向lua注册c++全局函数
18     lua::dostring(L, lua_str);  // 执行lua脚本,如果是文件则需要调用lua::dofile。
19     printf("lua_func(3,4) = %d\n", LuaFunc(L, "lua_func").Call<int>(3, 4));  // 调用lua的全局函数
20     lua::close(L);  // 关闭lua虚拟机,释放资源
21     return 0;
22 }

程序输出:

cpp_func(1,2) = 3
lua_func(3,4) = 7

似乎太简单了,我都不知道需要解释点啥,接下来进入第二个例子。

 1 #include "lua/LuaBus.h"
 2
 3 static int cpp_int = 100;
 4
 5 const char *lua_str = R"(
 6     print(‘cpp_int = ‘ .. cpp_int) -- 打印c++的全局变量
 7     lua_int = 200 -- 定义lua的全局变量
 8 )";
 9
10 int main(int argc, char **argv)
11 {
12     lua_State *L = lua::open();  // 新建lua虚拟机,并做一些必要的初始化工作。
13     lua::set(L, "cpp_int", cpp_int);  // 向lua注册c++全局变量
14     lua::dostring(L, lua_str);  // 执行lua脚本,如果是文件则需要调用lua::dofile。
15     printf("lua_int = %d\n", lua::get<int>(L, "lua_int"));  // 获取lua的全局变量
16     lua::close(L);  // 关闭lua虚拟机,释放资源
17     return 0;
18 }

程序输出:

cpp_int = 100
lua_int = 200

第三个例子,稍微有点复杂,主要是对c++类成员函数、成员变量、类继承的支持。在最后的lua代码中,对用到的c++类变量进行了一个内省输出的操作,如果大家对自己的定位是这个库的使用者,则完全没必要关心。PS:我写这段代码也只是因为luatinker的测试代码是这样的而已。

 1 #include "lua/LuaBus.h"
 2
 3 struct A {
 4     A(int v) : value(v) {}
 5     int value;
 6 };
 7
 8 struct base {
 9     base() {}
10     const char* is_base() { return "this is base"; }
11 };
12
13 class test : public base {
14 public:
15     test(int val) : _test(val) {}
16     ~test() {}
17     const char* is_test() { return "this is test"; }
18     void ret_void() {}
19     int ret_int() { return _test; }
20     int ret_mul(int m) const { return _test * m; }
21     A get() { return A(_test); }
22     void set(A a) { _test = a.value; }
23     int _test;
24 };
25
26 test g_test(11);
27
28 const char *lua_str = R""(
29     print(g_test._test) --直接访问c++类对象的成员变量
30     print(g_test:is_test()) --调用c++类对象的成员函数
31     print(g_test:ret_int()) --调用int返回值的成员函数
32     temp = test(4) --直接在脚本中创建c++类对象
33     print(temp._test) --打印由脚本创建的c++类对象变量的成员变量
34     a = g_test:get() --类成员函数返回一个c++类对象
35     temp:set(a) --类成员函数的参数为一个c++类对象
36     print(temp._test) --打印以上调用结果
37     print(temp:is_base()) --调用基类的成员函数
38     print(temp:is_test()) --调用自己的成员函数
39     -------------------------------------------------------------------------------
40     function objinfo(obj)
41         local meta = getmetatable(obj)
42         if meta ~= nil then
43             metainfo(meta)
44         else
45             print("no object infomation !!")
46         end
47     end
48     function metainfo(meta)
49         if meta ~= nil then
50             local name = meta["__name"]
51             if name ~= nil then
52                 metainfo(meta["__parent"])
53                 print("<"..name..">")
54                 for key,value in pairs(meta) do
55                     if not string.find(key, "__..") then
56                         if type(value) == "function" then
57                             print("\t[f] "..name..":"..key.."()")
58                         elseif type(value) == "userdata" then
59                             print("\t[v] "..name..":"..key)
60                         end
61                     end
62                 end
63             end
64         end
65     end
66     ------------------------------------------------------------------------------ -
67     print("g_test    -> ", g_test)
68     print("temp    -> ", temp)
69     print("a    -> ", a)
70     print("objinfo(g_test)")
71     objinfo(g_test)
72     print("objinfo(temp)")
73     objinfo(temp)
74     print("objinfo(a)")
75     objinfo(a)
76 )"";
77
78 int main(int argc, char **argv)
79 {
80     lua_State *L = lua::open();  // 新建lua虚拟机,并做一些必要的初始化工作。
81     lua::class_add<base>(L, "base");  // 注册base类
82     lua::class_def<base>(L, "is_base", &base::is_base);  // 注册is_base成员函数
83     lua::class_add<test>(L, "test");  // 注册test类
84     lua::class_inh<test, base>(L);  // 定义c++类之间的继承关系
85     lua::class_con<test>(L, lua::constructor<test, int>);  // 注册test构造函数
86     lua::class_def<test>(L, "is_test", &test::is_test);  // 注册is_test成员函数
87     lua::class_def<test>(L, "ret_void", &test::ret_void);  // 无返回值的成员函数
88     lua::class_def<test>(L, "ret_int", &test::ret_int);  // int返回值的成员函数
89     lua::class_def<test>(L, "ret_mul", &test::ret_mul);  // const修饰的成员函数
90     lua::class_def<test>(L, "get", &test::get);  // 类对象返回值的成员函数
91     lua::class_def<test>(L, "set", &test::set);  // 参数为类对象的成员函数
92     lua::class_mem<test>(L, "_test", &test::_test);  // 注册_test成员变量
93     lua::set(L, "g_test", &g_test);  // 向lua注册c++类对象
94     lua::dostring(L, lua_str);  // 执行lua脚本,如果是文件则需要调用lua::dofile。
95     lua::close(L);  // 关闭lua虚拟机,释放资源
96     return 0;
97 }

程序输出:

11
this is test
11
4
11
this is base
this is test
g_test  ->      test: 00000000004D9DC8
temp    ->      test: 00000000004D9F08
a       ->      (_GC_META_): 00000000004D9F88
objinfo(g_test)
<test>
        [f] test:downcast()
        [f] test:ret_mul()
        [f] test:is_object_alive()
        [f] test:get()
        [v] test:_test
        [f] test:reinterpret()
        [f] test:ret_int()
        [f] test:set()
        [f] test:ret_void()
        [f] test:is_test()
objinfo(temp)
<test>
        [f] test:downcast()
        [f] test:ret_mul()
        [f] test:is_object_alive()
        [f] test:get()
        [v] test:_test
        [f] test:reinterpret()
        [f] test:ret_int()
        [f] test:set()
        [f] test:ret_void()
        [f] test:is_test()
objinfo(a)
<(_GC_META_)>

第四个例子,是对lua table的支持。

 1 #include "lua/LuaFunc.h"
 2
 3 const char *lua_str = R"(
 4     print(haha) --打印全局table
 5     print(haha.value) --打印全局table的键值
 6     print(haha.inside) --打印全局table的键值,这个键值是一个table.
 7     print(haha.inside.value) --嵌套table键值测试
 8     haha.test = "input from lua" --添加键值,c++会获取它。
 9     function print_table(arg) --函数参数为lua table的测试例子
10         print("arg = ", arg)
11         print("arg.name = ", arg.name)
12     end
13     function return_table(arg) --函数返回一个lua table的测试例子
14         local ret = {}
15         ret.name = arg
16         return ret
17     end
18 )";
19
20 int main(int argc, char **argv)
21 {
22     lua_State *L = lua::open();  // 新建lua虚拟机,并做一些必要的初始化工作。
23     LuaTable haha(L, "haha");  // 关联一个全局lua table,如果没有就新建一个全局的lua table.
24     haha.set("value", 1);  // 给lua table添加一个键值对
25     haha.set("inside", LuaTable(L));  // 给lua table添加一个键值对,值是一个临时表
26     LuaTable inside = haha.get<LuaTable>("inside");  // 获取上一步加进去的临时表
27     inside.set("value", 2);  // 修改键值,lua脚本里面会打印它。
28     lua::dostring(L, lua_str);  // 执行lua脚本,如果是文件则需要调用lua::dofile。
29     const char* test = haha.get<const char*>("test");  // 获取在脚本中加进去的键值
30     printf("haha.test = %s\n", test);  // 不要问我这在干嘛!
31     LuaTable temp(L);  // 在栈上新建一个lua table
32     temp.set("name", "local table !!");  // 添加一个键值对
33     LuaFunc(L, "print_table").Call<void, const LuaTable&>(temp);  // 调用lua函数,参数为lua table.
34     LuaFunc func = LuaFunc(L, "return_table");  // 获取一个lua的全局函数,并存放在栈上,返回变量是对这个值的一个引用,我们后面就可以多次重用这个函数对象了。
35     LuaTable ret = func.Call<LuaTable>("give me a table !!");  // 可以对这个函数对象执行调用方法。
36     printf("ret.name =\t%s\n", ret.get<const char*>("name"));  // 打印上一步返回的lua table的一个键值
37     ret = LuaFunc(L, "return_table").Call<LuaTable>("give me a table !!");  // 这两行代码执行下来绝对不会出现你期望的输出结果,要想理解其具体原理可能需要多了解一点lua与c交互的知识才行,当然与此相关的c++特性你也需要十分了解。不过嘛,你也可以选择记住一定不能这样写,或者代码出错了回过头来看看,是不是有代码这样写了。
38     printf("ret.name =\t%s\n", ret.get<const char*>("name"));
39     lua::close(L);  // 关闭lua虚拟机,释放资源
40     return 0;
41 }

程序输出:

table: 00000000002EDD80
1
table: 00000000002EDDC0
2
haha.test = input from lua
arg =   table: 0000000000321F70
arg.name =      local table !!
ret.name =      give me a table !!
ret.name =      (null)

第五个例子,其实这个例子的用法,在前面几个例子中都有涉及,我不清楚为啥luatinker还要单独设计这个例子,由于它的例子的注释是韩语的,我也只好表示看不懂了。或许是用来测试调用一个不存在的函数吧?

 1 #include "lua/LuaFunc.h"
 2
 3 void show_error(const char* error) {
 4     printf("_ALERT -> %s\n", error);
 5 }
 6
 7 const char *lua_str = R"(
 8     function test_error()
 9         print("test_error() called !!")
10         test_error_1()
11     end
12     function test_error_1()
13         print("test_error_1() called !!")
14         test_error_2()
15     end
16     function test_error_2()
17         print("test_error_2() called !!")
18         test_error_3()
19     end
20 )";
21
22 int main(int argc, char **argv)
23 {
24     lua_State *L = lua::open();  // 新建lua虚拟机,并做一些必要的初始化工作。
25     printf("%s\n", "-------------------------- current stack");
26     lua::enum_stack(L);
27     lua_pushnumber(L, 1);
28     printf("%s\n", "-------------------------- stack after push ‘1‘");
29     lua::enum_stack(L);
30     lua::dostring(L, lua_str);  // 执行lua脚本,如果是文件则需要调用lua::dofile。
31     printf("%s\n", "-------------------------- calling test_error()");
32     LuaFunc(L, "test_error").Call<void>();
33     printf("%s\n", "-------------------------- calling test_error_3()");
34     LuaFunc(L, "test_error_3").Call<void>();
35     lua::def(L, "_ALERT", show_error);
36     LuaFunc(L, "_ALERT").Call<void>("test !!!");
37     printf("%s\n", "-------------------------- calling test_error()");
38     LuaFunc(L, "test_error").Call<void>();
39     lua::close(L);  // 关闭lua虚拟机,释放资源
40     return 0;
41 }

程序输出:

-------------------------- current stack
stack: 0
-------------------------- stack after push ‘1‘
stack: 1
        number  1
-------------------------- calling test_error()
test_error() called !!
test_error_1() called !!
test_error_2() called !!
[string "dobuffer()"]:12: attempt to call a nil value (global ‘test_error_3‘)
        <call stack>
->      test_error_3() : line -1 [[C] : line -1]
        test_error_2() : line 12 [[string "dobuffer()"] : line 10]
        test_error_1() : line 8 [[string "dobuffer()"] : line 6]
        unknown : line 4 [[string "dobuffer()"] : line 2]
-------------------------- calling test_error_3()
lua attempt to call global ‘test_error_3‘ (not a function)
_ALERT -> test !!!
-------------------------- calling test_error()
test_error() called !!
test_error_1() called !!
test_error_2() called !!
[string "dobuffer()"]:12: attempt to call a nil value (global ‘test_error_3‘)
        <call stack>
->      test_error_3() : line -1 [[C] : line -1]
        test_error_2() : line 12 [[string "dobuffer()"] : line 10]
        test_error_1() : line 8 [[string "dobuffer()"] : line 6]
        unknown : line 4 [[string "dobuffer()"] : line 2]

由于例子比较简单,我也就没有添加啥注释了。唯一需要注意的就是,当调用一个不存在的函数时(更准确的说,应该是当lua抛出异常时),底层绑定库会自动打印当前的调用堆栈,以帮助调用者定位问题所在。

第六个例子是关于协程的,然而luatinker对协程并没有提供任何的支持,当然了,我也没有对协程提供支持,所以这个例子就不贴出来了。

以上这些例子所展示的功能,应该算是一个lua的c++绑定库最基础而且必须存在的功能,它们的作用有这么几个,1、验证绑定库在功能上的正确性,2、用于帮助玩家快速入门,3、展示绑定库给玩家带来的便利性。关于这最后的一点嘛,大家可以在网上搜一下最原始版的lua与c交互的相关文章,看看c++给我们带来了多少便利。例如百度最靠前的这篇文章:https://www.cnblogs.com/sevenyuan/p/4511808.html,我们可以看到,lua与c的交互是非常麻烦的,绑定库只需要一行代码就能完成,并且还简洁易懂的代码,用原始的交互方式,那是需要很多行代码的,而且大家还得时时关心当前的堆栈状态,不知道大家有没有写汇编的感觉?c++给我们带来了很多的复杂性,然而也给我们带来了很多的方便之处,c的绑定库也有很多,然而跟c++相比,那是一个能打的都没有。

以上有一个例子的写法存在着一个bug,如果大家想要深入研究一下,不妨参考我上面提到的那篇文章,那绝对算得上一篇精品文章。

在接下来的系列文章里,我会开始介绍fusion/lua这个绑定库的一些高级功能,不说这些功能都是特有的,但是至少我所接触到的eluna、luatinker是没有这些功能的,敬请期待。

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

时间: 2024-08-05 16:48:23

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

Lua的C++绑定库(二)

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

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

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

Xamarin.Android绑定库分享

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

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

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

我所理解cocos2d-x 3.6 lua -- Cocos如何绑定Lua自定义类

cocos2d-x 2.x 与 cocos2d-x 3.x 差异(tolua++) cocos2d-x在2.x版本里就是用toLua++和.pkg文件这么把自己注册进Lua环境里的,然而从cocos2d-x 3.x开始,用bindings-generator脚本代替了toLua++. bindings-generator脚本的工作机制是:        1.不用编写.pkg和.h文件了,直接定义一个ini文件,注册到Lua环境里的模块名是什么,就行了.        2.摸清了toLua++工具

Lua中的基本函数库

基本函数库为Lua内置的函数库,不需要额外装载 assert (v [, message])功能:相当于C的断言,参数:v:当表达式v为nil或false将触发错误,message:发生错误时返回的信息,默认为"assertion failed!" -------------------------------------------------------------------------------- collectgarbage (opt [, arg])功能:是垃圾收集器的通

快速掌握Lua 5.3 —— 调试库 (2)

Q:如何调试"Closure"的"upvalue"信息? A: --[[ debug.getupvalue(f, up) 返回函数("Closure")"f"的第"up"个"upvalue"的名字和值. Lua按照"upvalues"在匿名函数中出现的顺序对其编号.如果指定的"up"索引越界,则返回"nil". 以'('开头的变

Lua中的一些库(1)

[数学库] 数学库(math)由一组标准的数学函数构成.这里主要介绍几个常用的函数,其它的大家可以自行百度解决. 三角函数(sin,cos,tan--)所有的三角函数都使用弧度单位,可以用函数deg(角度)和rad(弧度)来转换角度和弧度.示例代码: print(math.sin(math.rad(30))) -- 0.5 谨记:三角函数的参数都是弧度,在实际使用中不要忘了,是弧度. 取整函数(floor,ceil) floor:返回不大于x的最大整数:向下取整:ceil:返回不小于x的最大整数

快速掌握Lua 5.3 —— 调试库 (1)

Q:什么是活动函数? A:程序中被调用但还未执行完成的函数. function g() --[[ 此时函数"g"被调用但还未执行完成,是活动函数.所以这里获取的是函数"g"的信息. "debug.getinfo(2)"获取的才是函数"f"的信息.]] local x = debug.getinfo(1, "n") for k, v in pairs(x) do print(k, v) end end fun