• 把C++类成员函数集成到lua


           有时我们会把C++类导入到lua,这样方便在lua里面对C++类进行操作,这在游戏编程里面经常使用,本文只是简单介绍一种实现。

          

    1.       lua里面面向对象的实现

          
          
    lua里面,我们可以这样操作表,如下:

     

           Account = {balance = 0}

           function Account:withdraw (v)

                  self.balance = self.balance - v

           end

     

           在这里,我们定义了一个table并且有一个withdraw的函数,我们可以这样使用

          

           a = Account

           a:withdraw(100)

     

           这样就跟C++的类形式一样了。但是在lua里面没有类的概念,但是每个对象都有一个prototype(原型),当调用不属于对象的某些操作时,会最先会到prototype中查找这些操作。所以如果我们又两个对象ab,如果我们把b作为aprototype,我们就实现了最基本的继承。在lua中只需如下实现

     

           setmetatable(a, {__index = b})

     

           这样a调用任何不存在的成员都会到对象b里面去查找。在这里我们使用了__index metamethod,至于什么是metatablemetamethod lua相关文档有详细说明。(另外特别推荐programming in lua这本书,里面几乎全部介绍了这些知识)

          

     

    2.       C++类在lua中的表现形式
    lua中,我们假设C++类的操作形式如下,假设有一个类A

     

                  obj = A:new()

                  obj:dosomething()

                  obj:delete()

          

           这里我们没考虑成员变量的操作,因为对于我们来说只是对成员函数进行操作,而且考虑到封装原则不建议对类成员变量进行直接操作,如果要操作我们会在类里面定义相关的成员函数。

          

     

    3.       设计思想

           参考上面的表现形式,我们会把A设定成一个metatable,然后把相关函数与它关联,   也就是把new等函数通过lua_pushfunction使其作为改metatable的函数。而对于obj来说,他是一个A的实例,我们会把它的指针传给lualua使用,而这个指针值在lua里面就是用userdata来表示。由于只涉及到函数的操作,所以我们会把metatable__index与相关处理函数进行关联。

           另外,假设A继承于类B,而objA的实例,那么objmetatableAmetatable,而AmetatablemetatableBmetatable,这样就保证了继承性。我们通过obj调用函数,obj首先会在A里面查找,如果A里面没有,A会在B里面进行查找。

     

           基于上面的思路,我们首先定义函数:

           int reg_type(lua_State *L, const char *name)

         这个函数把我们类注册进lua中,即是把我们的类当成一个metatable,代码如下:

         //registry type to lua

         int reg_type(lua_State *L, const char *name)

         {

             //创建metatable,如果成功返回1,失败返回0

              int r = luaL_newmetatable(L, name);

     

              if(r)

              {       

                  //把该metatable注册为全局的

                  lua_pushstring(L, name);

                  lua_pushvalue(L, 1);

                  lua_settable(L, LUA_GLOBALSINDEX);  

              }

     

              if(r)

              {

                  classevent(L);

              }

     

              lua_pop(L, 1);

     

              return r;

         }

     

         对于classevent(),我们实现如下:

         void classevent(lua_State *L)

         {

              lua_pushstring(L,"__index");

              lua_pushcfunction(L,class_index_event);

              lua_rawset(L,-3);

         }

         这里就是关联metatable__index域,我们所有的操作都会转换到函数class_index_event进行。

         int class_index_event(lua_State *L)

         {

              int t = lua_type(L,1);

              if (t == LUA_TUSERDATA)

              {

                  //这里我们只考虑了函数类型,所以简单起见没有进行很多其他的处理

                  //我们会得到metatable,然后找到该metatable对应的函数,进行调用

                   lua_getmetatable(L, 1);

                   lua_pushvalue(L, 2);

                   lua_rawget(L, -2);

                   if(!lua_isnil(L, -1))

                   {

                       return 1;

                   }

              }

     

              return 1;

         }

     

         同时我们提供了设定继承关系的函数

         int reg_class(lua_State *L, const char *name, const char *base)

         {

              luaL_getmetatable(L, name);          //stack: mt

        

             //如果有基类,我们就把基类作为该类metatablemetatable

              if(base && *base)

              {

                  luaL_getmetatable(L, base);     //stack: mt basemt

                  lua_setmetatable(L, -2);        //stack: mt

              }

     

              lua_pop(L, 1);

        

              return 0;

         }

     

         注册函数的函数,使函数与特定的metatable关联:

         int reg_function(lua_State* L, const char * type, const char* name, lua_CFunction func)

         {   

              luaL_getmetatable(L, type);

              lua_pushstring(L, name);

              lua_pushcfunction(L, func);

              lua_rawset(L, -3);

              lua_pop(L, 1);

              return 0;

         }

     

         注册userdata的函数,在这里我们必须明确,对于我们自定义的类型的实例,比如obj,我们是通过指针传递给lua的,这其实是一个light userdata,而在lua里面,light userdata不具有meta     制,但是我们又要设定该类型的meta为类Ameta,这样我们就要实现一下包装,我们把这个obj 首先赋值给一个full userdata,在lua里面full userdata具有meta属性,然后存贮在一个注册table里面,这样其实我们返回的不是这个obj的指针了,而是包装了这个obj的一个full userdata 指针。在lua5.0里面定义了lua_unboxpointerlua_boxpointer宏,但不知怎么5.1里面没有了,这两个宏如下:

        

         #define lua_boxpointer(L,u) /

         (*(void **)(lua_newuserdata(L, sizeof(void *))) = (u))

     

         #define lua_unboxpointer(L,i)    (*(void **)(lua_touserdata(L, i)))

         当我们需要取出obj指针时候,我们通过(*(void **)转换得到。

     

         int reg_userdata(lua_State* L, void* value, const char* type)

         {

              luaL_getmetatable(L, type);

        

              lua_pushstring(L, "script_ubox");

              lua_rawget(L, LUA_REGISTRYINDEX);

        

              lua_pushlightuserdata(L,value);

              lua_rawget(L,-2);                       /* stack: mt ubox ubox[u] */

              if (lua_isnil(L,-1))

              {

                   lua_pop(L,1);                          /* stack: mt ubox */

                   lua_pushlightuserdata(L,value);

                   *(void**)lua_newuserdata(L,sizeof(void *)) = value;   /* stack: mt ubox u newud */

                   lua_pushvalue(L,-1);                   /* stack: mt ubox u newud newud */

                   lua_insert(L,-4);                      /* stack: mt newud ubox u newud */

                   lua_rawset(L,-3);                      /* stack: mt newud ubox */

                   lua_pop(L,1);                          /* stack: mt newud */

     

                   lua_pushvalue(L, -2);            /* stack: mt newud mt */

                   lua_setmetatable(L,-2);          /* stack: mt newud */

              }

     

              lua_remove(L, -2);

     

              return 1;

         }

     

         然后我们定义:

         这个函数只是注册一个table供我们操作。

         int script_open(lua_State *L)

         {

              lua_pushstring(L,"script_ubox");

              lua_newtable(L);  

              lua_rawset(L,LUA_REGISTRYINDEX);

     

              return 0;

         }

     

         接下来就是我们的实现:

         首先定义类以及类成员函数对应的注册函数:

         class test

         {

         public:

              test(){}

              ~test(){}

              void myprint() { printf("test::Hello World/n"); }

         };

     

         int test_new(lua_State *L)

         {

              test *t =  new test();

              reg_userdata(L, (void*)t, "test");

              return 1;

         }

     

         int test_delete(lua_State *L)

         {

              test *t = (test*)(*(void**)lua_touserdata(L, 1));

              delete t;

              lua_settop(L, 0);

              return 0;

         }

     

         int test_myprint(lua_State *L)

         {

              test *t = (test*)(*(void**)lua_touserdata(L, 1));

              t->myprint();

              return 0;

         }

     

         然后实现:

         script_open(L);

     

         reg_type(L, "test");

         reg_class(L, "test", NULL);

         reg_function(L, "test", "new", test_new);

         reg_function(L, "test", "delete", test_delete);

         reg_function(L, "test", "myprint", test_myprint);

     

         这样在lua里面,我们就可以这样调用了:

         obj = test:new()

         obj:myprint()

         obj:delete()

     

         注意,这只是一个非常简单的实现,有很多缺陷

         首先,它把类直接注册进GLOBALSINDEX,这样可能会导致命名冲突,其次每个类的成员函数都要单独对应一个注册函数,这样工作量很大,虽然我以前写过关于通过一个代理方法将不同的C类型与类成员函数注册进lua的方法,但是该方法对现在这种实现并不怎么适用。再就是这个例子很多方面没有考虑完全,只是单纯的把成员函数导入,功能很有限。看以后能不能写出一套更好的绑定类的东西吧。

  • 相关阅读:
    Python 爬虫 —— BeautifulSoup
    sklearn、theano、TensorFlow 以及 theras 的理解
    sklearn、theano、TensorFlow 以及 theras 的理解
    keras 的使用
    keras 的使用
    古人的字、号、别称
    古人的字、号、别称
    hdu1226 超级密码 (BFS,里面用了大数取余原理)
    2013渣打科营编程马拉松赛样题
    对象序列化实现深度克隆
  • 原文地址:https://www.cnblogs.com/xiaowangba/p/6313801.html
Copyright © 2020-2023  润新知