c++对象导出到lua

举报
香菜聊游戏 发表于 2021/07/15 02:17:36 2021/07/15
【摘要】 http://www.cnblogs.com/ringofthec/archive/2010/10/26/luabindobj.html 这些东西是平时遇到的, 觉得有一定的价值, 所以记录下来, 以后遇到类似的问题可以查阅, 同时分享出来也能方便需要的人, 转载请注明来自RingOfTheC[ring.of.the.c@gmail.com]   虽...

http://www.cnblogs.com/ringofthec/archive/2010/10/26/luabindobj.html

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

 

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

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

定义目标:

  有一个c++类


  
  1. class Foo
  2. {
  3. public:
  4. Foo(int value)
  5. {
  6. _value = value;
  7. printf(“Foo Constructor!\n”);
  8. }
  9. ~Foo()
  10. {
  11. printf(“Foo Destructor!\n”);
  12. }
  13. int add(int a, int b)
  14. {
  15. return a + b;
  16. }
  17. void setV(int value)
  18. {
  19. _value = value;
  20. }
  21. int getV()
  22. {
  23. return _value;
  24. }
  25. int _value;
  26. };
一个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类包裹起来, 解决参数数据源和返回值数据去向的问题



  
  1. class FooWrapper : public Foo
  2. {
  3. public:
  4. Foo(lua_State* L) : Foo(luaL_checknumber(L, -1))
  5. {
  6. }
  7. int add(lua_State* L)
  8. {
  9. int a = luaL_checknumber(L, -1);
  10. int b = luaL_checknumber(L, -2);
  11. int res = Foo::add(a, b);
  12. lua_pushnumber(L, res);
  13. return 1;
  14. }
  15. int setV(lua_State* L)
  16. {
  17. int v = luaL_checknumber(L, -1);
  18. Foo::setV(v);
  19. return 0;
  20. }
  21. int getV(lua_State* L)
  22. {
  23. lua_pushnumber(L, Foo::getV());
  24. }
  25. };

 这样, 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, ...


  
  1. struct RegType
  2. {
  3. const char* name;
  4. int (FooPort::*mfunc)(lua_State* L);
  5. };
  6. class LuaPort
  7. {public:
  8. static void RegisterClass(lua_State* L)
  9. {
  10. // 导出一个方法创建c++, 因为创建c++对象是在lua中发起的
  11. lua_pushcfunction(L, &LuaPort::constructor);
  12. lua_pushglobal(L, "Foo");
  13. // 创建userdata要用的元表(其名为Foo), 起码要定义__gc方法, 以便回收内存
  14. luaL_newmetatable(L, “Foo”);
  15. lua_pushstring(L, “__gc”);
  16. lua_pushcfunction(L, &LuaPort::gc_obj);
  17. lua_settable(L, -3);
  18. }
  19. static int constructor(lua_State* L)
  20. {
  21. // 1. 构造c++对象
  22. FooWrapper* obj = new FooWrapper(L);
  23. // 2. 新建一个表 tt = {}
  24. lua_newtable(L);
  25. // 3. 新建一个userdata用来持有c++对象
  26. FooWrapper** a = (FooWrapper** )lua_newuserdata(L, sizeof(FooWrapper*));
  27. *a = obj;
  28. // 4. 设置lua userdata的元表
  29. luaL_getmetatable(L, “Foo”);
  30. lua_setmetatable(L, -2);
  31. // 5. tt[0] = userdata
  32. lua_pushnumber(L, 0);
  33. lua_insert(L, -2);
  34. lua_settable(L, –3);
  35. // 6. 向table中注入c++函数
  36. for (int i = 0; FooWrapper::Functions[i].name; ++i)
  37. {
  38. lua_pushstring(L, FooWrapper::Functions[i].name);
  39. lua_pushnumber(L, i);
  40. lua_pushcclosure(L, &LuaPort::porxy, 1);
  41. lua_settable(L, -3);
  42. }
  43. // 7. 把这个表返回给lua
  44. return 1;
  45. }
  46. static int porxy(lua_State* L)
  47. {
  48. // 取出药调用的函数编号
  49. int i = (int)lua_tonumber(L, lua_upvalueindex(1));
  50. // 取tt[0] 及 obj
  51. lua_pushnumber(L, 0);
  52. lua_gettable(L, 1);
  53. FooWrapper** obj = (FooWrapper**)luaL_checkudata(L, –1, “Foo”);
  54. lua_remove(L, -1);
  55. // 实际的调用函数
  56. return ((*obj)->*(FooWrapper::Functions[i].mfunc))(L);
  57. }
  58. static int gc_obj(lua_State* L)
  59. {
  60. FooWrapper** obj = (FooWrapper**)luaL_checkudata(L, –1, “Foo”);
  61. delete (*obj);
  62. return 0;
  63. }
  64. };

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

 

方法2

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

       这里只列出不一样的地方


  
  1. static void Register(lua_State* L)
  2. {
  3. lua_pushcfunction(L, LuaPort::constructor);
  4. lua_setglobal(L, “Foo”);
  5. luaL_newmetatable(L, “Foo”);
  6. lua_pushstring(L, “__gc”);
  7. lua_pushcfunction(L, &LuaPort::gc_obj);
  8. lua_settable(L, -3);
  9. // ----------- 不一样的地方
  10. // 创建一个方法元表
  11. lua_newtable(L);
  12. // 指定__index方法
  13. int meta = lua_gettop(L);
  14. lua_pushstring(L, “__index”);
  15. lua_pushvalue(L, meta);
  16. lua_settable(L, –3);
  17. // 注册所有方法
  18. for (int i = 0; FooWrapper::Functions[i].name; ++i)
  19. {
  20. lua_pushstring(L, FooWrapper::Functions[i].name);
  21. lua_pushnumber(L, i);
  22. lua_pushcclosure(L, &LuaPort::porxy, 1);
  23. lua_settable(L, -3);
  24. }
  25. // 把这个表放入元表以便后用, 起名为methods
  26. lua_pushstring(L, “methods”);
  27. lua_insert(L, -2);
  28. lua_settable(L, -3);
  29. }
  30. static int constructor(lua_State* L)
  31. {
  32. // 1. 构造c++对象
  33. FooWrapper* obj = new FooWrapper(L);
  34. // 2. 新建一个表 tt = {}
  35. lua_newtable(L);
  36. // 3. 新建一个userdata用来持有c++对象
  37. FooWrapper** a = (FooWrapper** )lua_newuserdata(L, sizeof(FooWrapper*));
  38. *a = obj;
  39. // 4. 设置lua userdata的元表
  40. luaL_getmetatable(L, “Foo”);
  41. lua_pushvalue(L, -1);
  42. lua_setmetatable(L, -3);
  43. // ------------不一样的地方
  44. // 5. tt[0] = userdata
  45. lua_insert(L, -2);
  46. lua_pushnumber(L, 0);
  47. lua_insert(L, -2);
  48. lua_settable(L, -4);
  49. // 6. 绑定方法元表
  50. lua_pushstring(L, “methods”);
  51. lua_gettable(L, -2);
  52. lua_setmetatable(L, -3);
  53. lua_pop(L, 1);
  54. // 返回表
  55. return 1;
  56. }

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

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

 

方法3

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



  
  1. static void Register(lua_State* L)
  2. {
  3. lua_pushcfunction(L, LuaPort::construct);
  4. lua_setglobal(L, “Foo”);
  5. luaL_newmetatable(L, “Foo”);
  6. lua_pushstring(L, “__gc”);
  7. lua_pushcfunction(L, &LuaPort::gc_obj);
  8. lua_settable(L, -3);
  9. // ----- 不一样的
  10. // 把方法也注册进userdata的元表里
  11. for (int i = 0; FooWrapper::Functions[i].name; ++i)
  12. {
  13. lua_pushstring(L, FooWrapper::Functions[i].name);
  14. lua_pushnumber(L, i);
  15. lua_pushcclosure(L, &LuaPort::porxy, 1);
  16. lua_settable(L, -3);
  17. }
  18. // 注册__index方法
  19. lua_pushstring(L, “__index”);
  20. lua_pushvalue(L, -2);
  21. lua_settable(L, -3);
  22. }
  23. static int constructor(lua_State* L)
  24. {
  25. FooWrapper* obj = new FooWrapper(L);
  26. FooWrapper** a = (FooWrapper**)lua_newuserdata(L, sizeof(FooWrapper*));
  27. *a = obj;
  28. luaL_getmetatable(L, “Foo”);
  29. lua_setmetatable(L, -2);
  30. return 1;
  31. }
  32. static int porxy(lua_State* L)
  33. {
  34. int i = (int)lua_tonumber(L, lua_upvalueindex(1));
  35. FooPort** obj = (FooPort**)luaL_checkudata(L, 1, “Foo”);
  36. return ((*obj)->*(FooWrapper::FunctionS[i].mfunc))(L);
  37. }

    这个方法是最简洁的.

文章来源: gamwatcher.blog.csdn.net,作者:香菜聊游戏,版权归原作者所有,如需转载,请联系作者。

原文链接:gamwatcher.blog.csdn.net/article/details/77407137

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。