码迷,mamicode.com
首页 > 编程语言 > 详细

Lua的C++绑定库(一)

时间:2018-09-01 12:40:38      阅读:150      评论:0      收藏:0      [点我收藏+]

标签:作者   程序员   local   temp   foreach   ast   end   object   data   

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是没有这些功能的,敬请期待。

 

Lua的C++绑定库(一)

标签:作者   程序员   local   temp   foreach   ast   end   object   data   

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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!