轉自:http://www.cnblogs.com/ringofthec/archive/2010/10/26/luabindobj.html
這些東西是平時遇到的, 覺得有一定的價值, 所以記錄下來, 以後遇到類似的問題可以查閱, 同時分享出來也能方便需要的人, 轉載請註明來自RingOfTheC[[email protected]]
雖然有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))