最近学习ruby语言后,顿时就喜欢上了ruby语言,它的简洁,优美,灵活给我留下了深刻的印象。 之前一直从事游戏服务器研发相关工作,而核心语言是c++和lua, c++是一门编译型语言,所以运行效率非常高,但缺点是每次代码的一个小改动都得重新编译,这大大增加了项目的开发时间,也不适合需求多变的业务环境,而lua脚本语言正好能解决这种矛盾。 所以, 可以将那些需求多变的业务放在脚本中来执行, c++提供一些基础功能供lua调用,这样做既能保证运行效率,也能兼顾灵活多变,实乃一举两得。(顺便澄清一下, 虽然c++和lua这样的组合经常用在游戏开发中, 但在其他有同样需求的领域里也能采用这样的方式进行开发) 以往需要将c++的函数或对象方法导入到lua脚本的时候,也采用过luaplus, tolua++等开源工具,但给我的感觉是用起来太繁琐,不简洁,不方便,因此,我才打算自己写一套能更方便将c++中的对象方法或函数导入进lua脚本的解析器。我的思路是通过一个纯文本txt文件来描述需要导入的对象方法或普通函数,然后通过对文本的解析来自动生成注册和绑定的代码。ruby语言的文本解析能力非常强,而且ruby语言本身设计得非常灵活,简洁,同样的功能如果用python或c++来编写的话,估计要多出来很多代码。这个ruby版本的解析器我已经编写完成了,寄存在github上面,地址是:https://github.com/lichuan/lua2cpp ,解析器文件是lua2cpp.rb,描述注册信息文件是lua2cpp.txt,运行lua2cpp.rb解析器后生成的绑定和注册代码文件是lua2cpp.cpp(运行ruby文件之前请确保已安装ruby解释器),还有测试的代码放在test目录下, 欢迎有需要的同学pull下来查看,或是修改,如果你有更好的建议或意见,希望你能告诉我,我的qq是:308831759 描述文件lua2cpp.txt的语法非常简单,比如在c++类定义如下: #include <string> #include <cstring> #include <iostream> #include "lua.hpp" using namespace std; typedef unsigned int uint32; typedef int int32; int32 get_global_id() { return 0; } class Cls_Global { public: Cls_Global(string name) { m_name = name; } string get_name() { return m_name; } private: string m_name; }; namespace ns1 { class Cls1 { public: Cls1(string name) { m_name = name; } string get_1_name() { return m_name; } private: string m_name; }; } namespace ns2 { class Cls2 { public: Cls2(string name) { m_name = name; } string get_2_name() { return m_name; } private: string m_name; }; } 因为函数get_global_id位于全局命名空间中,所以描述文件如下书写: _{ int32 get_globa_id() } 只有一个下划线时,就表进这个块是全局命名空间,里面可以定义全局命名空间的函数,而类Cls_Global虽然位于全局命名空间, 但描述文件中一个块要么表求一个命名空间, 要么表示一个类,类在描述文件中有单独的区块, 应该按照下面的方式书写: Cls_Global { (string) string get_name() } 上面的块中的单独一行括号表示这个类的构造函数, 里面是构造函数的参数类型, 如果有多个参数, 需要写成 (int32, string, number)类似这样的形式。 类Cls1和Cls2都位于各自的命名空间中,这时需要加上命名空间前缀, 如下所示: _ns1.Cls1 { (string) string get_1_name() } _ns2.Cls2 { (string) string get_2_name() } 描述文件还支持指针和引用, 以及垃圾回收的标志等语法, 假如有一个c++文件定义如下: namespace dat_ns { struct Data { Data() { id = 0; name = "no name"; } uint32 id; string name; }; } class Test_Lua { public: Test_Lua() { } dat_ns::Data get_data() { return m_data; } dat_ns::Data& get_ref_data() { return m_data; } dat_ns::Data* get_ptr_data_no_gc() { return &m_data; } dat_ns::Data* get_ptr_data_gc() { dat_ns::Data *new_obj = new dat_ns::Data; *new_obj = m_data; return new_obj; } void set_data_id(uint32 id) { m_data.id = id; } void set_data_name(string name) { m_data.name = name; } string get_data_name() { return m_data.name; } uint32 get_data_id() { return m_data.id; } void replace_data(dat_ns::Data &data) { m_data = data; } private: dat_ns::Data m_data; }; 那么如果我要导出Test_Lua这个类给lua脚本呢,应该如何做呢? 这个也简单, 描述文件如下: _dat_ns.Data { () uint32 id set_id : id=uint32 string name set_name : name=string } Test_Lua { () _dat_ns.Data get_data() _dat_ns.Data& get_ref_data() _dat_ns.Data* get_ptr_data_no_gc() _dat_ns.Data*|gc| get_ptr_data_gc() set_data_id(uint32) set_data_name(string) string get_data_name() uint32 get_data_id() replace_data(_dat_ns.Data&) } 如果是返回的是引用类型,就加上一个&符号, 如果是指针就加上一个*符号,如果返回的对象需要在lua进行垃圾回收的时候,释放掉,就加上|gc|标志, 而且|gc|必须放在&或*的后面。 再看一下上面的一个块: _dat_ns.Data { () uint32 id set_id : id=uint32 string name set_name : name=string } 这里有新的语法了, uint32 id而不是uint32 id(),为什么没有括号呢,这是因为c++里dat_ns::Data类里没有id()这个函数,但有时候需要在lua里访问这个id值,怎么办呢? 不可能对每个类似Data这种结构体的字段都加上一个访问函数吧,那样也太繁琐,太不简洁了,我之所以编写这个lua2cpp解析器,就是为了达到简洁,方便。类似这样的直接访问字段的导出函数叫is_get函数,解析器在对uint32 id这一行进行解析的时候,会直接对Data结构体的id字段进行访问,然后把这个值传递到lua中,lua里如果要访问这个值的话,可以类似这样的写法: obj = _dat_ns.Data.new() print(obj:id()) 同样的道理,上面的描述块中,还有一行 set_id : id=uint32这种导出函数叫is_set函数,解析器会生成对结构或类或命名空间中的一个成员直接存储的代码,但是这个冒号是干什么用呢? 因为在lua中,obj:id()这个id函数名称已经占用了,所以必须要取一个不同的名字来表示这个is_set函数,冒号之前的名字是在lua中可以直接访问的名称,其实在描述文件中,除了构造函数不能有冒号之外,其他的导出函数都可以加一个冒号来重新取一个在lua中使用的名字,那么在lua代码中可以这样使用is_set函数: obj = _dat_ns.Data.new() obj:set_id(123) print(obj:id()) -----------------------> output: 123 其实,上面讲的这些,一部分正好是lua2cpp在github上的tes测试目录中的内容, test目录下的lua2cpp.txt内容如下: _dat_ns.Data { () uint32 id set_id : id=uint32 string name set_name : name=string } Test_Lua { () _dat_ns.Data get_data() _dat_ns.Data& get_ref_data() set_data_id(uint32) set_data_name(string) string get_data_name() uint32 get_data_id() replace_data(_dat_ns.Data&) } 在命令行上执行:./lua2cpp.rb, 会生成这个描述文件所对应的注册绑定代码文件叫lua2cpp.cpp,其内容如下: /* author: lichuan qq: 308831759 email: 308831759@qq.com homepage: www.lichuan.me github: https://github.com/lichuan/lua2cpp date: 2013-05-11 desc: this is the binding code between lua and c++ generated by lua2cpp.rb */ static void get_global_table(lua_State *lua_state, const char *nodes_name) { char buf[1024]; strcpy(buf, nodes_name); char *p = buf; const char *q = p; int count = 0; while(*p != 0) { if(*p == '.') { *p = 0; if(count == 0) { lua_getglobal(lua_state, q); if(lua_isnil(lua_state, -1)) { return; } } else { lua_pushstring(lua_state, q); lua_rawget(lua_state, -2); if(lua_isnil(lua_state, -1)) { return; } } q = p + 1; ++count; } ++p; } if(count == 0) { lua_getglobal(lua_state, q); if(lua_isnil(lua_state, -1)) { return; } } else { lua_pushstring(lua_state, q); lua_rawget(lua_state, -2); if(lua_isnil(lua_state, -1)) { return; } } } static void build_global_table(lua_State *lua_state, const char *nodes_name) { char buf[1024]; strcpy(buf, nodes_name); char *p = buf; const char *q = p; int count = 0; while(*p != 0) { if(*p == '.') { *p = 0; if(count == 0) { lua_getglobal(lua_state, q); if(lua_isnil(lua_state, -1)) { lua_newtable(lua_state); lua_pushvalue(lua_state, -1); lua_setglobal(lua_state, q); } } else { lua_pushstring(lua_state, q); lua_rawget(lua_state, -2); if(lua_isnil(lua_state, -1)) { lua_pop(lua_state, 1); lua_pushstring(lua_state, q); lua_newtable(lua_state); lua_pushvalue(lua_state, -1); lua_insert(lua_state, -4); lua_rawset(lua_state, -3); lua_pop(lua_state, 1); } } q = p + 1; ++count; } ++p; } if(count == 0) { lua_getglobal(lua_state, q); if(lua_isnil(lua_state, -1)) { lua_newtable(lua_state); lua_setglobal(lua_state, q); } } else { lua_pushstring(lua_state, q); lua_rawget(lua_state, -2); if(lua_isnil(lua_state, -1)) { lua_pop(lua_state, 1); lua_pushstring(lua_state, q); lua_newtable(lua_state); lua_rawset(lua_state, -3); } } lua_settop(lua_state, 0); } static int lua____dat_ns___Data__new(lua_State *lua_state) { lua_settop(lua_state, 0); uint32 *udata = (uint32*)lua_newuserdata(lua_state, sizeof(uint32) + sizeof(dat_ns::Data*)); uint32 &gc_flag = *udata; gc_flag = 1; /* need gc default in constructor */ udata += 1; *(dat_ns::Data**)udata = new dat_ns::Data(); luaL_setmetatable(lua_state, "_dat_ns.Data"); return 1; } static int lua____dat_ns___Data__id(lua_State *lua_state) { uint32 *udata_self = (uint32*)luaL_checkudata(lua_state, 1, "_dat_ns.Data"); udata_self += 1; dat_ns::Data *obj = *(dat_ns::Data**)udata_self; uint32 v = obj->id; lua_pushunsigned(lua_state, v); return 1; } static int lua____dat_ns___Data__set_id(lua_State *lua_state) { uint32 *udata = (uint32*)luaL_checkudata(lua_state, 1, "_dat_ns.Data"); udata += 1; dat_ns::Data *obj = *(dat_ns::Data**)udata; uint32 arg_1 = luaL_checkunsigned(lua_state, 2); lua_settop(lua_state, 0); obj->id = arg_1; return 0; } static int lua____dat_ns___Data__name(lua_State *lua_state) { uint32 *udata_self = (uint32*)luaL_checkudata(lua_state, 1, "_dat_ns.Data"); udata_self += 1; dat_ns::Data *obj = *(dat_ns::Data**)udata_self; std::string v = obj->name; lua_pushstring(lua_state, v.c_str()); return 1; } static int lua____dat_ns___Data__set_name(lua_State *lua_state) { uint32 *udata = (uint32*)luaL_checkudata(lua_state, 1, "_dat_ns.Data"); udata += 1; dat_ns::Data *obj = *(dat_ns::Data**)udata; const char *arg_1 = luaL_checkstring(lua_state, 2); lua_settop(lua_state, 0); obj->name = arg_1; return 0; } static int lua____dat_ns___Data__garbage_colloect(lua_State *lua_state) { uint32 *udata = (uint32*)luaL_checkudata(lua_state, 1, "_dat_ns.Data"); uint32 &gc_flag = *udata; if(gc_flag == 1) { udata += 1; dat_ns::Data *obj = *(dat_ns::Data**)udata; delete obj; } return 0; } static int lua___Test_Lua__new(lua_State *lua_state) { lua_settop(lua_state, 0); uint32 *udata = (uint32*)lua_newuserdata(lua_state, sizeof(uint32) + sizeof(Test_Lua*)); uint32 &gc_flag = *udata; gc_flag = 1; /* need gc default in constructor */ udata += 1; *(Test_Lua**)udata = new Test_Lua(); luaL_setmetatable(lua_state, "Test_Lua"); return 1; } static int lua___Test_Lua__get_data(lua_State *lua_state) { uint32 *udata_self = (uint32*)luaL_checkudata(lua_state, 1, "Test_Lua"); udata_self += 1; Test_Lua *obj = *(Test_Lua**)udata_self; lua_settop(lua_state, 0); dat_ns::Data *v = new dat_ns::Data; *v = obj->get_data(); uint32 *udata = (uint32*)lua_newuserdata(lua_state, sizeof(uint32) + sizeof(dat_ns::Data*)); uint32 &gc_flag = *udata; gc_flag = 1; /* no ptr, no ref, it's a new obj, so it need gc */ udata += 1; *(dat_ns::Data**)udata = v; luaL_setmetatable(lua_state, "_dat_ns.Data"); return 1; } static int lua___Test_Lua__get_ref_data(lua_State *lua_state) { uint32 *udata_self = (uint32*)luaL_checkudata(lua_state, 1, "Test_Lua"); udata_self += 1; Test_Lua *obj = *(Test_Lua**)udata_self; lua_settop(lua_state, 0); const dat_ns::Data *v = &obj->get_ref_data(); uint32 *udata = (uint32*)lua_newuserdata(lua_state, sizeof(uint32) + sizeof(dat_ns::Data*)); uint32 &gc_flag = *udata; gc_flag = 0; udata += 1; *(dat_ns::Data**)udata = (dat_ns::Data*)v; luaL_setmetatable(lua_state, "_dat_ns.Data"); return 1; } static int lua___Test_Lua__set_data_id(lua_State *lua_state) { uint32 *udata_self = (uint32*)luaL_checkudata(lua_state, 1, "Test_Lua"); udata_self += 1; Test_Lua *obj = *(Test_Lua**)udata_self; uint32 arg_1 = luaL_checkunsigned(lua_state, 2); lua_settop(lua_state, 0); obj->set_data_id(arg_1); return 0; } static int lua___Test_Lua__set_data_name(lua_State *lua_state) { uint32 *udata_self = (uint32*)luaL_checkudata(lua_state, 1, "Test_Lua"); udata_self += 1; Test_Lua *obj = *(Test_Lua**)udata_self; const char *arg_1 = luaL_checkstring(lua_state, 2); lua_settop(lua_state, 0); obj->set_data_name(arg_1); return 0; } static int lua___Test_Lua__get_data_name(lua_State *lua_state) { uint32 *udata_self = (uint32*)luaL_checkudata(lua_state, 1, "Test_Lua"); udata_self += 1; Test_Lua *obj = *(Test_Lua**)udata_self; lua_settop(lua_state, 0); std::string v = obj->get_data_name(); lua_pushstring(lua_state, v.c_str()); return 1; } static int lua___Test_Lua__get_data_id(lua_State *lua_state) { uint32 *udata_self = (uint32*)luaL_checkudata(lua_state, 1, "Test_Lua"); udata_self += 1; Test_Lua *obj = *(Test_Lua**)udata_self; lua_settop(lua_state, 0); uint32 v = obj->get_data_id(); lua_pushunsigned(lua_state, v); return 1; } static int lua___Test_Lua__replace_data(lua_State *lua_state) { uint32 *udata_self = (uint32*)luaL_checkudata(lua_state, 1, "Test_Lua"); udata_self += 1; Test_Lua *obj = *(Test_Lua**)udata_self; uint32 *udata_1 = (uint32*)luaL_checkudata(lua_state, 2, "_dat_ns.Data"); udata_1 += 1; dat_ns::Data *arg_1 = *(dat_ns::Data**)udata_1; lua_settop(lua_state, 0); obj->replace_data(*arg_1); return 0; } static int lua___Test_Lua__garbage_colloect(lua_State *lua_state) { uint32 *udata = (uint32*)luaL_checkudata(lua_state, 1, "Test_Lua"); uint32 &gc_flag = *udata; if(gc_flag == 1) { udata += 1; Test_Lua *obj = *(Test_Lua**)udata; delete obj; } return 0; } static void register_lua(lua_State *lua_state) { /* register non-global namespace */ lua_settop(lua_state, 0); build_global_table(lua_state, "_dat_ns.Data"); get_global_table(lua_state, "_dat_ns.Data"); luaL_newmetatable(lua_state, "_dat_ns.Data"); lua_pushvalue(lua_state, -2); lua_setfield(lua_state, -2, "__index"); lua_settop(lua_state, 0); build_global_table(lua_state, "Test_Lua"); get_global_table(lua_state, "Test_Lua"); luaL_newmetatable(lua_state, "Test_Lua"); lua_pushvalue(lua_state, -2); lua_setfield(lua_state, -2, "__index"); { luaL_Reg _dat_ns_Data[] = { {"new", lua____dat_ns___Data__new}, {"id", lua____dat_ns___Data__id}, {"set_id", lua____dat_ns___Data__set_id}, {"name", lua____dat_ns___Data__name}, {"set_name", lua____dat_ns___Data__set_name}, {"__gc", lua____dat_ns___Data__garbage_colloect}, {NULL, NULL} }; lua_settop(lua_state, 0); get_global_table(lua_state, "_dat_ns.Data"); luaL_setfuncs(lua_state, _dat_ns_Data, 0); } { luaL_Reg Test_Lua[] = { {"new", lua___Test_Lua__new}, {"get_data", lua___Test_Lua__get_data}, {"get_ref_data", lua___Test_Lua__get_ref_data}, {"set_data_id", lua___Test_Lua__set_data_id}, {"set_data_name", lua___Test_Lua__set_data_name}, {"get_data_name", lua___Test_Lua__get_data_name}, {"get_data_id", lua___Test_Lua__get_data_id}, {"replace_data", lua___Test_Lua__replace_data}, {"__gc", lua___Test_Lua__garbage_colloect}, {NULL, NULL} }; lua_settop(lua_state, 0); get_global_table(lua_state, "Test_Lua"); luaL_setfuncs(lua_state, Test_Lua, 0); } }
test目录下还有一个测试c++文件叫test.cpp, 其内容如下: #include <string> #include <cstring> #include <list> #include <map> #include <iostream> #include "lua.hpp" using namespace std; typedef unsigned int uint32; typedef int int32; namespace dat_ns { struct Data { Data() { id = 0; name = "no name"; } uint32 id; string name; }; } class Test_Lua { public: Test_Lua() { } dat_ns::Data get_data() { return m_data; } dat_ns::Data& get_ref_data() { return m_data; } void set_data_id(uint32 id) { m_data.id = id; } void set_data_name(string name) { m_data.name = name; } string get_data_name() { return m_data.name; } uint32 get_data_id() { return m_data.id; } void replace_data(dat_ns::Data &data) { m_data = data; } private: dat_ns::Data m_data; }; #include "lua2cpp.cpp" int main() { lua_State *lua_state = luaL_newstate(); luaL_openlibs(lua_state); register_lua(lua_state); if(luaL_dofile(lua_state, "test.lua") != 0) { cout << "err: " << lua_tostring(lua_state, -1) << endl; } } 看到了吗, 在test.cpp文件里有一行是 #include "lua2cpp.cpp", 这里就将生成的代码包含到test.cpp中了,main函数里,会执行test.lua这个lua脚本,编译好test.cpp后,运行就可以看到执行test.lua的输出了, test.lua的内容如下(箭头后面表示实际的输出结果): print "------start lua script--------" dat_obj = _dat_ns.Data.new() print(dat_obj:id()) ---------------------------------------------------------> output: 0 print(dat_obj:name()) -------------------------------------------------------> output: no name dat_obj:set_id(92) dat_obj:set_name("I'm old object") print(dat_obj:id()) ---------------------------------------------------------> output: 92 print(dat_obj:name()) -------------------------------------------------------> output: I'm old object print "" test_lua_obj = Test_Lua.new() print(test_lua_obj:get_data_id()) -------------------------------------------> output: 0 print(test_lua_obj:get_data_name()) -----------------------------------------> output: no name test_lua_obj:replace_data(dat_obj) print(test_lua_obj:get_data_id()) -------------------------------------------> output: 92 print(test_lua_obj:get_data_name()) -----------------------------------------> output: I'm old object print "" tmp_obj = test_lua_obj:get_data() tmp_obj:set_name("I'm tmp object") print(tmp_obj:name()) -------------------------------------------------------> output: I'm tmp object print(test_lua_obj:get_data_name()) -----------------------------------------> output: I'm old object print "" ref_obj = test_lua_obj:get_ref_data() ref_obj:set_name("I'm ref object") print(ref_obj:name()) -------------------------------------------------------> output I'm ref object print(test_lua_obj:get_data_name()) -----------------------------------------> output I'm ref object print "------end lua script-------" 好了,大概介绍到这里了,如果要深入研究,可以去github上查看ruby语言写成的解析器的源码,欢迎留言或qq交流。
作者:lcabcdefg 发表于2013-8-14 20:10:06 原文链接
阅读:25 评论:0 查看评论
如果您喜欢IT技术或者对IT技术感兴趣,请加入脚本百事通QQ交流群:246889341 请记住永久域名:http://www. |
|