c++對象導出到lua

轉自: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))


發佈了11 篇原創文章 · 獲贊 6 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章