c++对象导出到lua
来源:互联网 发布:手机淘宝怎样用信用卡 编辑:程序博客网 时间:2024/05/19 05:04
转自:http://www.cnblogs.com/ringofthec/archive/2010/10/26/luabindobj.html
这些东西是平时遇到的, 觉得有一定的价值, 所以记录下来, 以后遇到类似的问题可以查阅, 同时分享出来也能方便需要的人, 转载请注明来自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);
}
这个方法是最简洁的.
===========================分割线===================================
上面的文章虽然原理上讲的比较清楚,但是有些地方看的我比较不明不白,然后搜到了最原始的代码:
#include <stdio.h>extern "C"{#include <lua.h>#include <lauxlib.h>#include <lualib.h>}#pragma comment(lib,"lua5.1.lib")template<class T> class luna{public:static void Register(lua_State *L){lua_pushcfunction(L, &luna<T>::construct);lua_setglobal(L, T::classname);luaL_newmetatable(L, T::classname);int meta_tb = lua_gettop(L);lua_pushstring(L, "__gc");lua_pushcfunction(L, &luna<T>::gc_obj);lua_settable(L, meta_tb); }static int construct(lua_State *L){T* obj = new T(L);lua_newtable(L);int tb1 = lua_gettop(L); lua_pushnumber(L, 0);T** pp = (T**)lua_newuserdata(L, sizeof(T*));*pp = obj;luaL_getmetatable(L, T::classname);lua_setmetatable(L, -2);lua_settable(L, tb1);for (int i=0; T::function[i].name; i++){lua_pushstring(L, T::function[i].name);lua_pushnumber(L, i);lua_pushcclosure(L, &luna<T>::thunk , 1);lua_settable(L,tb1);}return 1;}static int thunk(lua_State *L){int i = (int)lua_tonumber(L, lua_upvalueindex(1));lua_pushnumber(L, 0);lua_gettable(L, 1);T* p = *((T**)luaL_checkudata(L, -1, T::classname));lua_remove(L, -1);return (p->*(T::function[i].mfunc))(L);}static int gc_obj(lua_State *L){T* p = *((T**)luaL_checkudata(L, -1, T::classname));if (p)delete p;return 0;}struct RegType{const char *name;int (T::*mfunc)(lua_State *);};};class Base{public:Base() {}~Base() {}int add(int a, int b){ return a + b; }};class Foo :public Base{public:Foo(lua_State *L) { printf("call Foo constructor\n"); }~Foo() { printf("call Foo destructor\n"); }int foo(lua_State *L) { printf("in foo function\n"); return 0; }int get(lua_State *L) { printf("in get function\n"); return 0; }int n_add(lua_State *L){int a = NULL;int b = NULL;a = (int)luaL_checknumber(L, -2);b = (int)luaL_checknumber(L, -1);double result = add(a, b);lua_pushnumber(L, result);//int index = lua_gettop(L);//printf("index=%d",index);return 1;}static const char classname[];static const luna<Foo>::RegType function[];private:int m_value;};const char Foo::classname[] = "Foo";const luna<Foo>::RegType Foo::function[] ={{ "foo", &Foo::foo},{ "get", &Foo::get},{ "add", &Foo::n_add},{ NULL , NULL }};int main(){lua_State *L = lua_open();luaopen_base(L);luna<Foo>::Register(L);luaL_dofile(L, "test.lua");lua_close(L);return 0;}
test.lua:
local f = Foo()f:foo()f:get()print("add is:",f:add(5,6))
- c++对象导出到lua
- c++对象导出到lua
- c++对象导出到lua
- c++对象导出到lua
- c++对象导出到lua
- c++对象导出到lua
- c接口导出到lua中使用
- CardinalSplineTo导出到Lua
- 将C++对象导出到lua,C++和lua的相互调用
- C++对象绑定到Lua
- 传Lua对象到Cpp
- C++对象绑定到Lua
- 导出C/C++API给Lua使用
- C++接口导出到lua组件
- cocos2d-x quick 导出c++到Lua
- 导出C++类到LUA脚本
- Mac下导出c++到Lua失败
- 从C/C++到Lua
- 第九周实验任务五--设计一元一次方程类,求形如ax+b=0的方程的解。
- Linux 平台相关代码的 C++ 解决方案
- asp.net使用listview分页显示数据
- 线性表的链式实现
- sqlite3的时间类型
- c++对象导出到lua
- A Simple SpringMVC2.5
- sqlite3插入时间实例
- linux职位要求
- 删除joomla 2.5 中的示例文章
- 最详细的Cydia使用教程------完全版。新补充Cydia1.1.1离线安装(升级)方法。
- linux最简单驱动
- 双向选择列表框
- C语言中字符串转换为数字和数字转换为字符串库函数