zoukankan      html  css  js  c++  java
  • c++对象导出到lua

    这些东西是平时遇到的, 觉得有一定的价值, 所以记录下来, 以后遇到类似的问题可以查阅, 同时分享出来也能方便需要的人, 转载请注明来自RingOfTheC[ring.of.the.c@gmail.com]

    虽然有tolua++, luabind等等, 不过自己手动绑定还是有助于更深的了解lua的机制, 以及锻炼自己如何使用lua提供的现有机制来实现自己的需求

    [部分内容来自网络, 我这里就是做一些总结和扩展, 感谢分享知识的人:)]

    定义目标:

      有一个c++类

       class Foo

      {

      public:

            Foo(int value)

            {

                   _value = value;

                   printf(“Foo Constructor!\n”);

            }

            ~Foo()

            {

                   printf(“Foo Destructor!\n”);

            }

            int add(int a, int b)

            {

                  return  a  +  b;

            }

            void setV(int value)

            {

                   _value = value;

            }

            int getV()

            {

                  return _value;

            }

            int _value;

      };

      一个lua文件, test.lua, 想用如下方式访问, 问题: 如何实现?

          ff = Foo(3)

          v = ff:add(1, 4)        // v = 5

          ff:foo()

          ff:setV(6)

          ff2 = Foo(4)

          print(ff:getV())       // v = 6

          print(ff2:getV())     // v = 4

    要求:  1. Foo() 可以创建一个c++对象, 并返回给lua一个对象的引用ref

             2. lua中可以使用ref:function(arg, ...)的形式调用c++对象的方法

             这里有两个问题, 第一, 不同于c++中的对象创建和对象方法调用, 创建和调用方法的参数都是来自于lua中, 而且方法调用的返回值也是要传回给lua的, 而lua和c++是靠lua_State栈来交换数据的, 所以必须使用一个wrapper类, 将Foo类包裹起来, 解决参数数据源和返回值数据去向的问题

    class FooWrapper : public Foo

      {

      public:

            Foo(lua_State* L) : Foo(luaL_checknumber(L, -1))

            {

            }

            int add(lua_State* L)

            {

                   int a = luaL_checknumber(L, -1);

                   int b = luaL_checknumber(L, -2);            

                   int res = Foo::add(a, b);

                   lua_pushnumber(L, res);

                   return 1;

            }

            int setV(lua_State* L)

            {

                   int v = luaL_checknumber(L, -1);

                   Foo::setV(v);

                   return 0;

            }

            int getV(lua_State* L)

            {

                   lua_pushnumber(L, Foo::getV());

            }

    };

         这样, FooWrapper就成为lua和c++对象的一个通信界面, 里面本身不实现任何逻辑, 只实现数据通信, 转发调用. 这样就解决了数据流的来源和去向问题.

         第二, 调用的发起者问题, 在c++中, 调用对象的方法本质上就是函数调用, 而在lua中调用c++对象的方法, 有几个要注意的地方:

            1. 需要在lua中调用的方法 func 必须导出到lua中.

            2. lua调用对象方法的时候, 必须能够获取到该对象, 因为必须使用 obj->(*func)(L) 这样的形式调用成员函数.

            3. 在lua中, 把func 和 obj 关联起来.

            其中, 解决1的方法是lua提供的, 通过压入c 闭包到lua中就可以实现函数的导出, 这个是比较简单的.

                   对于2, 一般lua中持有c++对象是使用userdata来实现的(userdata 类型用来将任意 C 数据保存在 Lua 变量中. 这个类型相当于一块原生的内存, 除了赋值和相同性判断, Lua 没有为之预定义任何操作. 然而, 通过使用 metatable (元表), 程序员可以为 userdata 自定义一组操作. userdata 不能在 Lua 中创建出来, 也不能在 Lua 中修改. 这样的操作只能通过 C API, 这一点保证了宿主程序完全掌管其中的数据. metatable 中还可以定义一个函数,让 userdata 作垃圾收集时调用它  ---  lua 5.1 参考手册).

            好了, 现在函数可以导入到lua中, c++对象也可以导入到lua中, 唯一剩下的就是如何关联, 这个方法有几种, 下面可以用代码来说明

    方法1

            创建c++对象的时候, 创建一个表tt = {}  tt[0] = obj [userdata]  tt[1 ...] = func1, func2, ...

            struct RegType

            {

                 const char* name;

                 int (FooPort::*mfunc)(lua_State* L);

            };

            class LuaPort

            {public:

               static void RegisterClass(lua_State* L)

               {

                    // 导出一个方法创建c++, 因为创建c++对象是在lua中发起的

                    lua_pushcfunction(L, &LuaPort::constructor);

                    lua_pushglobal(L, "Foo");

                    // 创建userdata要用的元表(其名为Foo), 起码要定义__gc方法, 以便回收内存

                    luaL_newmetatable(L, “Foo”);

                    lua_pushstring(L, “__gc”);

                    lua_pushcfunction(L, &LuaPort::gc_obj);

                    lua_settable(L, -3);

               }

               static int constructor(lua_State* L)

               {

                    // 1. 构造c++对象

                    FooWrapper* obj = new FooWrapper(L);

                    // 2. 新建一个表 tt = {}

                    lua_newtable(L);

                     // 3. 新建一个userdata用来持有c++对象

                     FooWrapper** a = (FooWrapper** )lua_newuserdata(L, sizeof(FooWrapper*));

                     *a = obj;

                     // 4. 设置lua userdata的元表

                     luaL_getmetatable(L, “Foo”);

                     lua_setmetatable(L, -2);

                     // 5. tt[0] = userdata

                     lua_pushnumber(L, 0);

                     lua_insert(L, -2);

                     lua_settable(L, –3);

                     // 6. 向table中注入c++函数

                     for (int i = 0; FooWrapper::Functions[i].name; ++i)

                     {

                            lua_pushstring(L, FooWrapper::Functions[i].name);

                            lua_pushnumber(L, i);

                            lua_pushcclosure(L, &LuaPort::porxy, 1);

                            lua_settable(L, -3);

                     }

                     // 7. 把这个表返回给lua

                     return 1;

               }

               static int porxy(lua_State* L)

               {

                     // 取出药调用的函数编号

                     int i = (int)lua_tonumber(L, lua_upvalueindex(1));

                    // 取tt[0] 及 obj

                     lua_pushnumber(L, 0);

                     lua_gettable(L, 1);

                     FooWrapper** obj = (FooWrapper**)luaL_checkudata(L, –1, “Foo”);

                     lua_remove(L, -1);

                     // 实际的调用函数

                     return ((*obj)->*(FooWrapper::Functions[i].mfunc))(L);

               }

               static int gc_obj(lua_State* L)

               {

                      FooWrapper** obj = (FooWrapper**)luaL_checkudata(L, –1, “Foo”);

                      delete (*obj);

                      return 0;

               }

            };

         这个方法的主要部分是把obj 和 obj的函数组织成lua中的一张表, 思路比较简单, 但是有一个问题就是新建一个obj时, 都要在新建一个表并在里面加导出所有的方法, 感觉这样是冗余的.       

    方法2

           和方法1类似, 但是用过使用元表, 来避免方法1中重复注册方法的问题

           这里只列出不一样的地方

           static void Register(lua_State* L)

           {

                lua_pushcfunction(L, LuaPort::constructor);

                lua_setglobal(L, “Foo”);

                luaL_newmetatable(L, “Foo”);

                lua_pushstring(L, “__gc”);

                lua_pushcfunction(L, &LuaPort::gc_obj);

                lua_settable(L, -3);

                // ----------- 不一样的地方

                // 创建一个方法元表

                lua_newtable(L);

                // 指定__index方法

               int meta = lua_gettop(L);

               lua_pushstring(L, “__index”);

               lua_pushvalue(L, meta);

               lua_settable(L, –3);

               // 注册所有方法

               for (int i =  0; FooWrapper::Functions[i].name; ++i)

               {

                            lua_pushstring(L, FooWrapper::Functions[i].name);

                            lua_pushnumber(L, i);

                            lua_pushcclosure(L, &LuaPort::porxy, 1);

                            lua_settable(L, -3);

               }

               // 把这个表放入元表以便后用, 起名为methods

               lua_pushstring(L, “methods”);

               lua_insert(L, -2);

               lua_settable(L, -3);

           }

           static int constructor(lua_State* L)

           {

                   // 1. 构造c++对象

                    FooWrapper* obj = new FooWrapper(L);

                    // 2. 新建一个表 tt = {}

                    lua_newtable(L);

                     // 3. 新建一个userdata用来持有c++对象

                     FooWrapper** a = (FooWrapper** )lua_newuserdata(L, sizeof(FooWrapper*));

                     *a = obj;

                     // 4. 设置lua userdata的元表

                     luaL_getmetatable(L, “Foo”);

                     lua_pushvalue(L, -1);

                     lua_setmetatable(L, -3);

                    // ------------不一样的地方

                    // 5. tt[0] = userdata

                    lua_insert(L, -2);

                    lua_pushnumber(L, 0);

                    lua_insert(L, -2);

                    lua_settable(L, -4);

                   // 6. 绑定方法元表

                   lua_pushstring(L, “methods”);

                   lua_gettable(L, -2);

                   lua_setmetatable(L, -3);

                   lua_pop(L, 1);

                   // 返回表

                   return 1;

           }

            这样的话, 只是在注册类型的时候把函数导入到lua中, 在以后的每次创建对象时, 只要将方法表值为其元表就可以了, 这样就避免了多次导入函数

            但是这个方法还是有问题, 其实本身userdata就可有有元表, 用这个元表就可以了.

    方法3

            直接使用一个表做 userdata 的元表, 方法表等等.

            static void Register(lua_State* L)

            {

                lua_pushcfunction(L, LuaPort::construct);

                lua_setglobal(L,  “Foo”);

                luaL_newmetatable(L, “Foo”);

                lua_pushstring(L, “__gc”);

                lua_pushcfunction(L, &LuaPort::gc_obj);

                lua_settable(L, -3);

                // ----- 不一样的

                // 把方法也注册进userdata的元表里

               for (int i =  0; FooWrapper::Functions[i].name; ++i)

               {

                            lua_pushstring(L, FooWrapper::Functions[i].name);

                            lua_pushnumber(L, i);

                            lua_pushcclosure(L, &LuaPort::porxy, 1);

                            lua_settable(L, -3);

               }

               // 注册__index方法

               lua_pushstring(L, “__index”);

               lua_pushvalue(L, -2);

               lua_settable(L, -3);

             }

             static int constructor(lua_State* L)

             {

                  FooWrapper* obj = new FooWrapper(L);

                  FooWrapper** a = (FooWrapper**)lua_newuserdata(L, sizeof(FooWrapper*));

                  *a = obj;

                  luaL_getmetatable(L, “Foo”);

                  lua_setmetatable(L, -2);

                  return 1;

             }

             static int porxy(lua_State* L)

             {

                  int  i = (int)lua_tonumber(L, lua_upvalueindex(1));

                  FooPort** obj = (FooPort**)luaL_checkudata(L, 1, “Foo”);

                  return ((*obj)->*(FooWrapper::FunctionS[i].mfunc))(L);

             }

            这个方法是最简洁的.

  • 相关阅读:
    面试题:你了解对象分配规则吗
    面试题:Java 对象的创建过程
    面试题:Minor GC、Major GC、Full GC的触发时机
    常见的垃圾回收算法
    Java GUI快速入门
    Java Swing 介绍
    Java 开发环境配置
    Java (windows)安装教程
    Gamma、Linear、sRGB 和Unity Color Space,你真懂了吗?
    Qt OpenGL 蒙板
  • 原文地址:https://www.cnblogs.com/ringofthec/p/luabindobj.html
Copyright © 2011-2022 走看看