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