分享

用ruby语言编写一个将c++对象方法导入到lua脚本中的解析器

 quasiceo 2014-01-17

用ruby语言编写一个将c++对象方法导入到lua脚本中的解析器

返回脚本百事通

最近学习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.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多