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

虽然有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()
v = ff:add(, ) // v = 5
ff:foo()
ff:setV()
ff2 = Foo()
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, -))
{
} int add(lua_State* L)
{
int a = luaL_checknumber(L, -);
int b = luaL_checknumber(L, -);
int res = Foo::add(a, b);
lua_pushnumber(L, res);
return ;
} int setV(lua_State* L)
{
int v = luaL_checknumber(L, -);
Foo::setV(v);
return ;
} 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, -);
} 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, -); // 5. tt[0] = userdata
lua_pushnumber(L, );
lua_insert(L, -);
lua_settable(L, –); // 6. 向table中注入c++函数
for (int i = ; FooWrapper::Functions[i].name; ++i)
{
lua_pushstring(L, FooWrapper::Functions[i].name);
lua_pushnumber(L, i);
lua_pushcclosure(L, &LuaPort::porxy, );
lua_settable(L, -);
} // 7. 把这个表返回给lua
return ;
} static int porxy(lua_State* L)
{
// 取出药调用的函数编号
int i = (int)lua_tonumber(L, lua_upvalueindex()); // 取tt[0] 及 obj
lua_pushnumber(L, );
lua_gettable(L, );
FooWrapper** obj = (FooWrapper**)luaL_checkudata(L, –, “Foo”);
lua_remove(L, -); // 实际的调用函数
return ((*obj)->*(FooWrapper::Functions[i].mfunc))(L);
} static int gc_obj(lua_State* L)
{
FooWrapper** obj = (FooWrapper**)luaL_checkudata(L, –, “Foo”); delete (*obj);
return ;
}
};

这个方法的主要部分是把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, -); // ----------- 不一样的地方
// 创建一个方法元表
lua_newtable(L); // 指定__index方法
int meta = lua_gettop(L);
lua_pushstring(L, “__index”);
lua_pushvalue(L, meta);
lua_settable(L, –); // 注册所有方法
for (int i = ; FooWrapper::Functions[i].name; ++i)
{
lua_pushstring(L, FooWrapper::Functions[i].name);
lua_pushnumber(L, i);
lua_pushcclosure(L, &LuaPort::porxy, );
lua_settable(L, -);
} // 把这个表放入元表以便后用, 起名为methods
lua_pushstring(L, “methods”);
lua_insert(L, -);
lua_settable(L, -);
} 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, -);
lua_setmetatable(L, -); // ------------不一样的地方
// 5. tt[0] = userdata
lua_insert(L, -);
lua_pushnumber(L, );
lua_insert(L, -);
lua_settable(L, -); // 6. 绑定方法元表
lua_pushstring(L, “methods”);
lua_gettable(L, -);
lua_setmetatable(L, -);
lua_pop(L, ); // 返回表
return ;
}

这样的话, 只是在注册类型的时候把函数导入到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, -); // ----- 不一样的
// 把方法也注册进userdata的元表里
for (int i = ; FooWrapper::Functions[i].name; ++i)
{
lua_pushstring(L, FooWrapper::Functions[i].name);
lua_pushnumber(L, i);
lua_pushcclosure(L, &LuaPort::porxy, );
lua_settable(L, -);
} // 注册__index方法
lua_pushstring(L, “__index”);
lua_pushvalue(L, -);
lua_settable(L, -);
} 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, -);
return ;
} static int porxy(lua_State* L)
{
int i = (int)lua_tonumber(L, lua_upvalueindex());
FooPort** obj = (FooPort**)luaL_checkudata(L, , “Foo”);
return ((*obj)->*(FooWrapper::FunctionS[i].mfunc))(L);
}

这个方法是最简洁的.

c++对象导出到lua的更多相关文章

  1. hiredis异步接口封装并导出到Lua

    hiredis异步接口封装并导出到Lua(金庆的专栏 2017.1)hiredis 不支持 Windows, Windows 下使用 wasppdotorg / hiredis-for-windows ...

  2. ES5和ES6对象导出和导入(转载,待整理)

    1.import ... form...替代 require() //不接收对象 require:require('s.css'); //(es5) improt 's.css' //(es6) // ...

  3. cocos2d-x中CCEditbox导出到lua

    自从工作后感觉时间较少(每天工作9-22,晚上就不想动了,早上想多睡点),工作中用的是 cocos2d-x.cocos2d-x是一款手机游戏引擎,虽然支持lua,但和love2d相比非纯lua游戏引 ...

  4. cocos:C++ 导出到lua, cocos2dx_extension.ini修改

    cocos:C++ 导出到lua, cocos2dx_extension.ini修改 [zq] //zq section, 需要和genbindings.py中的配置相同 # the prefix t ...

  5. cocos:C++ 导出到lua, genbindings.py修改

    cocos:C++ 导出到lua, genbindings.py修改 1. 准备 把tools目录下的cocos2dx_extension.ini, genbindings.py, userconf. ...

  6. 开源基于lua gc管理c++对象的cocos2dx lua绑定方案

    cocos2dx目前lua对应的c++对象的生命周期管理,是基于c++析构函数的,也就是生命周期可能存在不一致,比如c++对象已经释放,而lua对象还存在,如果这时候再使用,会有宕机的风险,为此我开发 ...

  7. UNITY 带spriterender的对象导出为prefab时主贴图丢失的BUG

    从场景导出带有sprite的对象为prefab时贴图丢失的BUG.解决方案:对场景中每个sprite重新赋一下贴图,然后导出就好了,原因不明. 补充:这个有时候是因为贴图类型不是 2D AND UI ...

  8. PostgreSQL 大对象导出报错问题分析

    1.前言 在处理用户问题过程遇到一个问题.用户通过pg_dump导出 bytea 对象时,当行的大小超过 1G时,会报错: [v8r6c5b41@dbhost01 ~]$ sys_dump -t t1 ...

  9. 3DMAX 批量 场景 对象 导出 .X格式 脚本

    一.首先你需要下载一个 Total Commader文件管理软件.利用这个软件你可以收集文件夹下包含子文件夹下的max文件(或完整路径)打开TotalCMD后使用查找文件:(如图红框中的操作)1. 2 ...

随机推荐

  1. YY大厅接受不到documentcompleted事件处理

    多玩大厅在接受到了页面的documentcompleted事件,才会把遮在页面前面的YY游戏中去掉,我们的游戏页面,YY大厅接收不到事件,所以就排查了下 发现原因在于js脚本里有个用iframe做上报 ...

  2. sublime运行Python

    1.首先安装Python 我这里安装的是Python的3.7版本. 这里有两种安装方式 第一种: 默认路径安装,勾选添加到path复选框(这种情况,sublime可以直接运行Python了) 第二种: ...

  3. VMware Integrated OpenStack (VIO)简介

    VMware Integrated OpenStack是一款由VMware提供支持的OpenStack发行版软件,用于帮助IT在现有的VMware基础架构之上更加轻松地运行基于生产级OpenStack ...

  4. python3函数内全局变量使用global

    def p_num(): global num num = 10 print (num) num = 5 p_num() print(num)

  5. Java多线程系列 JUC线程池07 线程池原理解析(六)

     关闭“线程池” shutdown()的源码如下: public void shutdown() { final ReentrantLock mainLock = this.mainLock; // ...

  6. 【leetcode刷题笔记】Sort List

    Sort a linked list in O(n log n) time using constant space complexity. 题解:实现一个链表的归并排序即可.主要分为三部分: 1.找 ...

  7. 大话设计模式--建造者模式 Builder -- C++实现实例

    1. 建造者模式,将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示. 用户只需要指定需要建造的类型就可以得到他们,而具体建造的过程和细节就不需要知道了. 关键类Directo ...

  8. HDU5371 Hotaru's problem

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...

  9. ANT+JMETER集成

    一.ant安装 环境准备: 1.安装jdk http://www.oracle.com/technetwork/java/javase/downloads/index.html 注意:最好与jmete ...

  10. SESSION应用的一个场合

    Session其实指的就是访问者从到达某个特定主页到离开为止的那段时间.每 一访问者都会单独获得一个Session.在Web应用程序中,当一个用户访问该应用 时,Session类型的变量可以供这个用户 ...