本人一直从事游戏服务器开发工作,而核心语言是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解析器,就是为了达到简洁,方便。类似这样的直接访问字段的导出函数,我给它们取名叫做get函数,解析器在对uint32 id这一行进行解析的时候,会直接对Data结构体的id字段进行访问,然后把这个值传递到lua中,lua里如果要访问这个值的话,可以类似这样的写法:
obj = _dat_ns.Data.new()
print(obj:id())
同样的道理,上面的描述块中,还有一行set_id : id=uint32这种带一个=符号的导出函数取名叫做set函数,解析器会生成对该区块所代表的类或命名空间中的一个成员直接赋值的代码,但是这个冒号是干什么用呢? 因为在lua中,obj:id()这个id函数名称已经占用了,所以必须要取一个不同的名字来表示这个set函数,冒号之前的名字是在lua中可以直接访问的名称,其实在描述文件中,除了构造函数不能有冒号之外,其他的导出函数都可以加一个冒号来重新取一个在lua中使用的名字,那么在lua代码中可以这样使用set函数:
obj = _dat_ns.Data.new()
obj:set_id(123)
print(obj:id()) -----------------------> output: 123
描述文件也支持类的继承的语法,比如有如下c++文件:
class Base1
{
public:
void base1_func()
{
cout << "base1_func()" << endl;
}
};
class Base2
{
public:
void base2_func()
{
cout << "base2_func()" << endl;
}
};
class Derived : public Base1, public Base2
{
public:
void derived_func()
{
cout << "derived_func()" << endl;
}
};
如果希望导出子类Derived的话,描述文件可以这样写:
Base1
{
()
base1_func()
}
Base2
{
()
base2_func()
}
Derived extends Base1, Base2
{
()
derived_func()
}
解析器在解析上面的描述文件中的Derived extends Base1, Base2这一行的时候,会自动将基类Base1和Base2中的成员函数合并到子类Derived中去,所以在lua脚本中可以这样使用子类Derived:
obj = Derived.new()
obj:base1_func() ----------------->output: base1_func()
obj:base2_func() ----------------->output: base2_func()
obj:derived_func() --------------->output: derived_func()
是不是很方便呢,这就是我设计解析器的一个宗旨:方便,简洁。实际上,前面讲到的这些,一部分正好是lua2cpp在github上的test测试目录中的内容,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-------"