LuaBind --最強大的Lua C++ Bind

LuaBind --最強大的Lua C++ Bind


1 介紹
LuaBind 是一個幫助你綁定C++和Lua的庫.她有能力暴露 C++ 函數和類到 Lua . 她也有
能力支持函數式的定義一個Lua類,而且使之繼承自C++或者Lua. Lua類可以覆寫從 C++ 基類
繼承來的虛函數. 她的目標平臺是Lua 5.0 ,不能支持Lua 4.0 .

她利用模板原編程技術實現.這意味着,你不需要額外的預處理過程去編譯你的工程(編譯器
會替你完成全部的工作).這還意味着,你也不需要(通常)知道你註冊的每一個函數的精確的簽名.
因爲,LuaBind庫會在編譯時生成所需的代碼.這樣做的不利點是,編譯時間會隨着需要註冊的
文件的數目增加而增加.因此建議你把所有的需要註冊的東西放到一個cpp文件裏面.

LuaBind 遵循 MIT 協議 發佈.

我們非常希望聽說有工程使用了LuaBind, 請告訴我們,如果你的工程使用了LuaBind.

主要的反饋渠道是 LuaBind郵件列表 .
在 irc.freenode.net還可以找到一個IRC頻道 #luabind .

2 功能

LuaBind支持:

* 重載自由函數
* C++類導入Lua
* 重載成員函數
* 操作符
* 屬性
* 枚舉
* Lua函數導入C++
* Lua類導入C++
* Lua類(單繼承)
* 從Lua或C++類繼承
* 覆寫C++類的虛函數
* 註冊類型間隱式的類型轉換
* 最好匹配式簽名匹配
* 返回值策略和參數策略

3 可移植性

LuaBind 已經通過下面的編譯器環境的測試:

Visual Studio 7.1
Visual Studio 7.0
Visual Studio 6.0 (sp 5)
Intel C++ 6.0 (Windows)
GCC 2.95.3 (cygwin)
GCC 3.0.4 (Debian/Linux)
GCC 3.1 (SunOS 5.8)
GCC 3.2 (cygwin)
GCC 3.3.1 (cygwin)
GCC 3.3 (Apple, MacOS X)
GCC 4.0 (Apple, MacOS X)


LuaBind被確認不能在 GCC 2.95.2 (SunOS 5.8) 下工作.
Metrowerks 8.3 (Windows) 可以編譯LuaBind,但是通不過常量測試.這就意味着常量
成員函數被視同非常量成員函數.
如果你測試了LuaBind和其他未列出的編譯器的兼容性,請告訴我們你的結果.

4 構建LuaBind

爲了抑制LuaBind的編譯時間最好是將其編譯爲一個庫. 這意味着你要不編譯並連接LuaBind
庫要不就添加其所有源碼到你的工程裏面.你必須確保LuaBind目錄在你的編譯器包含目錄中.
LuaBind需要Boost 1.32.0 或者 1.33.0 (只需要頭文件即可). LuaBind還需要Lua.

官方的構建LuaBind的方式是通過 Boost.Build V2 . 爲此,你需要設置兩個環境變量:
BOOST_ROOT 指向你的Boost安裝目錄
LUA_PATH     指向你的Lua目錄.編譯系統將假定包含文件和庫文件分別放在
$(LUA_PATH)/include/ 和 $(LUA_PATH)/lib/.

爲了向後兼容性,LuaBind在根目錄下還保留了一個makefile.這可以構建庫和測試程序.如果
你正在使用一個UNIX系統(或者 cygwin),他們將使得構建LuaBind靜態庫變得很簡單.如果
你正在使用 Visual Studio ,很簡單的包含 src 目錄下的文件到你的工程即可.

構建LuaBind的時候,你可以設定一些選項來使得庫更加符合你的需求.特別重要的是,你的應用
程序也必須使用和庫一樣的設定.可用的選項的介紹參見 Build options 章節.

如果你希望改變缺省的設置,推薦你通過修改命令行參數的方式來實現.(在Visual Studio
的工程設置項裏面).

5 基本使用

爲了使用LuaBind, 你必須包含 lua.h 和 LuaBind 的主要頭文件:
extern "C"
{
    #include "lua.h"
}

#include <luabind/luabind.hpp>

這些頭文件提供了註冊函數和類的功能. 如果你只是想獲得函數或者類的支持,你可以分開
包含 luabind/function.hpp 和 luabind/class.hpp:
#include <luabind/function.hpp>
#include <luabind/class.hpp>
你需要去做的第一件事是 調用 luabind::open(lua_State*), 由此註冊可以在Lua創建類
的函數並初始化 LuaBind需要使用的 狀態機全局結構. 如果你不調用這個函數, 你會在後面
觸發一個 斷言 .  不沒有一個對應的關閉函數.因爲,一旦一個類被註冊到Lua,真沒有什麼好
的方法去移除它.部分原因是任何剩餘的類實例都將依賴其類. 當狀態機被關閉的時候,所有
的一切都將被清理乾淨.

LuaBind 的頭文件不會直接包含 Lua.h , 而是透過 <luabind/lua_include.hpp> . 如果你
出於某種原因需要包含其他的Lua頭文件,你可以修改此文件.

5.1 Hello World
新建一個控制檯DLL工程, 名字是 luabind_test.
#include <iostream>
#include <luabind/luabind.hpp>
#include <luabind/lua_include.hpp>

extern "C"
{
    #include "lua.h"
    #include "lauxlib.h"
}

void greet()
{
    std::cout << "hello world!/n";
}

extern "C" int luaopen_luabind_test(lua_State* L)
{
    using namespace luabind;

    open(L);

    module(L)
        [
            def("greet", &greet)
        ];

    return 0;
}

把生成的DLL和lua.exe/lua51.dll放在同一個目錄下.
Lua 5.1.2  Copyright (C) 1994-2007 Lua.org, PUC-Rio
> require "luabind_test"
> greet()
Hello world!
>

6 作用域

註冊到Lua裏面的所有東西要不註冊於一個名空間下(Lua table)要不註冊於全局作用域(lua module).
所有註冊的東西必須放在一個作用域裏面.爲了定義一個模塊, luabind::module 類必須被使用.
使用方式如下:
module(L)
[
    // declarations
];
這將會註冊所有的函數或者類到 Lua 全局作用域. 如果你想要爲你的模塊設定一個名空間(類似標準模塊),
你可以給構造函數設定一個名字,例如:
module(L, "my_library")
[
    // declarations
];
這裏所有的申明都將被放置在 my_libary 表.

如果你想要嵌套名空間,你可以用 luabind::namespace_ 類. 它和 luabind::module 類似,除了構造器
沒有lua_State* 輸入參數.用例如下:
module(L, "my_library")
[
    // declarations

    namespace_("detail")
    [
        // library-private declarations
    ]
];

你可能會想到,下面兩個聲明是等價的:
module(L)
[
    namespace_("my_library")
    [
        // declarations
    ]

];
module(L, "my_library")
[
    // declarations
];
每一個聲明必須用逗號分隔,例如:
module(L)
[
    def("f", &f),
    def("g", &g),
    class_<A>("A")
        .def(constructor<int, int>),
    def("h", &h)
];

更多實際的例子請參閱  綁定函數到Lua 和 綁定類到Lua 章節.

請注意, (如果你對性能有很高的需求)把你的函數放到表裏面將增加查找函數的時間.

7 綁定函數到Lua

爲了綁定函數到Lua,你可以使用函數 luabind::def(). 它的聲明如下:
template<class F, class policies>
void def(const char* name, F f, const Policies&);
* name 是該函數在Lua裏面的名字
* F 是該函數的指針
* 策略參數是用來描述怎樣處理該函數參數和返回值的.這是一個可選參數,參見 策略 章節.

下面的例子演示註冊函數 float std::sin(float):
module(L)
[
    def("sin", &std::sin)
];

7.1 重載函數

如果你有同名函數需要註冊到Lua, 你必須顯示的給定函數的簽名.
這可以讓C++知道你指定的是哪一個函數. 例如, 如果你有兩個函數,
int f(const char*) 和 void f(int).
module(L)
[
    def("f", (int(*)(const char*)) &f),
    def("f", (void(*)(int)) &f)
];

7.2 簽名匹配

LuaBind 將會生成代碼來檢查Lua棧的內容是否匹配你的函數的簽名. 它會隱式的在
派生類之間進行類型轉換,並且它會按照儘量少進行隱式類型轉換的原則經行匹配.在
一個函數調用中,如果函數是重載過的,並且重載函數的參數匹配分不出好壞的話
(都經行同樣次數的隱式類型轉換),那麼將產生一個二義性錯誤.這將生成一個運行時
錯誤,程序掛起在產生二義性調用的地方.一個簡單的例子是,註冊兩個函數,一個函數
接受一個int參數,另外一個函數接受一個float參數. 因爲Lua將不區別浮點數和整形數,
所以他們都是匹配的.

因爲所有的重載是被測試過的,這將總是找到最好的匹配(不是第一個匹配).這樣意味着,
LuaBind可以處理簽名的區別只是const和非const的重載函數.

例如,如果如下的函數和類被註冊:
struct A
{
    void f();
    void f() const;
};

const A* create_a();所有權轉移
                                    爲了正確處理所有權轉移問題,create_a()將用來適配返回值策略. 
                                    參見 策略 章節.                                
-Linker Lin 4/5/08 6:32 PM
 

struct B: A {};
struct C: B {};

void g(A*);
void g(B*);

執行以下 Lua 代碼即結果:
a1 = create_a()
a1:f() -- 常量版本被調用

a2 = A()
a2:f() -- 非常量版本被調用

a = A()
b = B()
c = C()

g(a) -- calls g(A*)
g(b) -- calls g(B*)
g(c) -- calls g(B*)

7.3 調用Lua函數

爲了調用一個Lua函數, 你可以或者用 call_function() 或者用 一個對象(object).
template<class Ret>
Ret call_function(lua_State* L, const char* name, ...)
template<class Ret>
Ret call_function(object const& obj, ...)

call_function()函數有兩個重載版本.一個是根據函數的名字來調用函數,
另一個是調用一個可以作爲函數調用的Lua值.

使用函數名來調用的版本只能調用Lua全局函數. "..."代表傳遞給Lua函數的
可變個數的參數. 這使得你可以指定調用的策略.你可以通過 operator[] 來實現
這個功鞥.你可以同過方括號來指定策略,例如:
int ret = call_function<int>(
    L
  , "a_lua_function"
  , new complex_class()
)[ adopt(_1) ];

如果你想通過引用方式傳遞參數,你必須用Boost.Ref來包裝一下.
例如:
int ret = call_function(L, "fun", boost::ref(val));

如果你想給一個函數調用指定自己的錯誤捕獲處理函數(error handler),可以參閱
pcall errorfunc 章節的 set_pcall_callback .

7.4 使用Lua協程

爲了使用Lua協程,你必須調用 lua_resume(),這就意味着你不能用先前介紹的函數
call_function()來開始一個協程.你必須用這個:
template<class Ret>
Ret resume_function(lua_State* L, const char* name, ...)
template<class Ret>
Ret resume_function(object const& obj, ...)
和:
template<class Ret>
Ret resume(lua_State* L, ...)

第一次開始一個協程的時候,你必須給它一個入口函數. 當一個協程返回(yield)的時候,
resume_fucntion()調用的返回值是 lua_yield()的第一個傳入參數.當你想要繼續一個
協程的時候,你只需要調用 resume() 在你的 lua_State() 上,因爲它已經在執行一個函數
(即先前出入的入口函數),所以你不需要再次傳入函數.resume()的傳入參數將作爲Lua側的
yield()調用的返回值.

爲了暫停(yielding)C++函數,(不支持在C++側和Lua側傳送數據塊),你可以使用 yield 策略.

接受 object 參數的resume_function()的重載版本要求對象必須是一個協程對象.(thread)

lua_State* thread = lua_newthread(L);
object fun = get_global(thread)["my_thread_fun"];
resume_function(fun);


8 綁定類到Lua

爲了註冊一個類,你可以用 class_ 類. 它的名字和C++關鍵字類似是爲了比較直觀.它有一個重載
過的成員函數 def() .這個函數被用來註冊類的成員函數,操作符,構造器,枚舉和屬性.它將返回 this
指針,從而方便你直接註冊更多的成員.

讓我們開始一個簡單的例子.考慮下面的C++類:
class testclass
{
public:
    testclass(const std::string& s): m_string(s) {}
    void print_string() { std::cout << m_string << "/n"; }

private:
    std::string m_string;
};

爲了註冊這個類到Lua環境,可以像下面這樣寫(假設你使用了名空間):
module(L)
[
    class_<testclass>("testclass")
        .def(constructor<const std::string&>())
        .def("print_string", &testclass::print_string)
];

這將註冊 testclass 類以及接受一個string參數的構造器以及一個成員叫print_string()的函數.
Lua 5.0  Copyright (C) 1994-2003 Tecgraf, PUC-Rio
> a = testclass('a string')
> a:print_string()
a string

還可以註冊自由函數作爲成員函數.對這個自由函數的要求是,它必須接受該類的一個指針或常量指針或
引用或常量引用作爲函數的第一個參數.該函數的剩下的參數將在Lua側可見,而對象指針將被賦值給第一個
參數.如果我們有如下的C++代碼:
struct A
{
    int a;
};

int plus(A* o, int v) { return o->a + v; }

你可以註冊 plus() 作爲A的一個成員函數,如下:
class_<A>("A")
    .def("plus", &plus)

plus() 現在能夠被作爲A的一個接受一個int參數的成員函數來調用.如果對象指針(this指針)是const,
這個函數也將表現的像一個常量成員函數那樣(它可以通過常量對象來調用).

8.1 重載成員函數

當綁定超過一個以上的重載過的成員函數的時候,或只是綁定其中的一個的時候,你必須消除你傳遞給 def() 的
成員函數指針的歧義.爲此,你可以用普通C風格的類型轉換來轉型匹配正確的重載函數. 爲此,你必須知道怎麼去
描述C++成員函數的類型.這裏有一個簡短的教程(更多信息請查閱你的C++參考書):
成員函數指着的語法如下:
return-value (class-name::*)(arg1-type, arg2-type, ...)
例如:
struct A
{
    void f(int);
    void f(int, int);
};
class_<A>()
    .def("f", (void(A::*)(int))&A::f)

A的第一個成員函數f(int)被綁定了,而第二個沒喲被綁定.

8.2 屬性

很容易註冊類的全局數據成員.考慮如下的類:
struct A
{
    int a;
};

這個類可以這樣註冊:
module(L)
[
    class_<A>("A")
        .def_readwrite("a", &A::a)
];

這使得成員變量 A::a 獲得了讀寫訪問權. 還可以註冊一個只讀的屬性:
module(L)
[
    class_<A>("A")
        .def_readonly("a", &A::a)
];

當綁定成員是一個非原始數據類型的時候,自動生成的 getter 函數將會返回一個它引用.
這就允許你可以鏈式使用 . 操作符.例如,當有一個結構體包含另外一個結構體的時候.如下:
struct A { int m; };
struct B { A a; };

當綁定B到Lua的時候,下面的表達式應該可以工作:
b = B()
b.a.m = 1
assert(b.a.m == 1)

這要求 a 屬性必須返回一個A的引用, 而不是一個拷貝. 這樣,LuaBind將會自動使用依賴策略來
確保返回值依賴於它所在的對象.所以,如果返回的引用的生命長於該對象的所有的引用(這裏是b).
它將保持對象是激活的,從而避免出現懸掛指針.

你還可以註冊 getter 或者 setter 函數來使得它們看上去像一個 public 的成員.考慮下面的類:
class A
{
public:
    void set_a(int x) { a = x; }
    int get_a() const { return a; }

private:
    int a;
};

可以這樣註冊成一個公共數據成員:
class_<A>("A")
    .property("a", &A::get_a, &A::set_a)

這樣 set_a() 和 get_a() 將取代簡單的數據成員操作.如果你想使之只讀,你只需要省略最後一個參數.
請注意, get 函數必須是 const 的,否則不能通過編譯.

8.3 枚舉

如果你的類包含枚舉,你可以註冊它們到Lua. 注意,它們不是類型安全的,所有的枚舉在Lua側都是整型的,
並且所有接受枚舉參數的函數都將接受任何整型.你可以像這樣註冊它們:

module(L)
[
    class_<A>("A")
        .enum_("constants")
        [
            value("my_enum", 4),
            value("my_2nd_enum", 7),
            value("another_enum", 6)
        ]
];

在Lua側,他們可以像數據成員那樣被操作,除了它們是隻讀的而且屬於類本身而不是類的實例.
Lua 5.0  Copyright (C) 1994-2003 Tecgraf, PUC-Rio
> print(A.my_enum)
4
> print(A.another_enum)
6

8.4 操作符

爲了綁定操作符,你需要包含頭文件 <luabind/operator.hpp>.
註冊你的類的操作符的機制非常的簡單.你通過一個全局名字 luabind::self 來引用類自己,然後你就
可以在def()調用裏面直接用操作符表達式. 類如下:
struct vec
{
    vec operator+(int s);
};

可以這樣註冊:
module(L)
[
    class_<vec>("vec")
        .def(self + int())
];

不管你的 + 操作符是定義在類裏面還是自由函數都可以工作.
如果你的操作符是常量的(const)(或者,是一個自由函數, 接受一個類的常量的引用)你必須用
const_self 替代 self. 如下:
module(L)
[
    class_<vec>("vec")
        .def(const_self + int())
];

支持如下操作符:
+    -    *    /    ==    <    <=

這意味着,沒有"就地操作符"(in-place)(++ --). 相等操作符(==)有些敏銳;如果引用是相等的就不會
被調用. 這意味着, 相等操作符的效率非常好.

Lua不支持操作符包括: !=,>和<=.這是爲什麼你只能註冊上面那些操作符. 當你調用這些操作符的時候,
Lua會把調用轉換到支持的操作符上.(譯註:例如:==和!=有邏輯非得關係) -Linker Lin 4/6/08 11:09 PM 

在上面的示例中,操作數的類型是 int().如果操作數的類型是複雜類型,就不是那麼簡單了,你需要用 other<>
來包裝下.例如:
爲了註冊如下的類,我們不想用一個string的實例來註冊這個操作符.
struct vec
{
    vec operator+(std::string);
};

取而代之的是,我們用 other<> 包裝下,如下:
module(L)
[
    class_<vec>("vec")
        .def(self + other<std::string>())
];

註冊一個應用程序操作符(函數調用):
module(L)
[
    class_<vec>("vec")
        .def( self(int()) )
];

這裏有個特殊的操作符.在Lua裏,它叫做 __tostring,它不是一個真正的操作符.它是被用來轉換一個對象到
string的標準Lua方法.如果你註冊之,可以通過Lua的標準函數 tostring() 來轉換你的對象到一個string.

爲了在C++裏實現這個操作符,你需要爲 std::ostream 提供 operator<< .像這樣:
class number {};
std::ostream& operator<<(std::ostream&, number&);

...

module(L)
[
    class_<number>("number")
        .def(tostring(self))
];


8.5 嵌套作用域和靜態函數

可以添加嵌套的作用域到一個類.當你需要包裝一個嵌套類或者一個靜態函數的時候就會很有用.
class_<foo>("foo")
    .def(constructor<>()
    .scope
    [
        class_<inner>("nested"),
        def("f", &f)
    ];

在上面的例子裏, f 將表現的像一個類 foo 的靜態函數,而 類 nested 將表現的像類 foo 的嵌套類.

還可以用同樣的語法添加名空間到類裏面.


8.6 繼承類

如果你想要註冊一個繼承自其它類的類到Lua, 你可以指定一個模板參數 bases<> 給 class_ 的構造器.
如下的繼承關係:
struct A {};
struct B : A {};

可以這樣註冊:
module(L)
[
    class_<A>("A"),
    class_<B, A>("B")
];

如果你使用了多繼承,你可以指定多於一個的基類.如果 B 還繼承了類 C , 它可以這樣註冊:
module(L)
[
    class_<B, bases<A, C> >("B")
];

注意,你可以省去 bases<> 當你用的是單繼承的時候.

注意

如果你不指定類的繼承關係, LuaBind 將不能在相關的繼承類型間進行隱式類型轉換.


8.7 智能指針

當你註冊一個類的時候,你可以告訴 LuaBind 所有的該類的實例應該被某種智能指針持有.(例如: boost::shared_ptr)
你可通過把一個 持有器類型模板參數 給 class_ 類的構造器來實現該功能.例如:
module(L)
[
    class_<A, boost::shared_ptr<A> >("A")
];

你還必須爲你的智能指針提供兩個函數.一個返回常量版本的智能指針類型(這裏是: boost:shared_ptr< const A >).
另一個函數要可以從智能指針萃取流指針(raw pointer). 之所以需要第一個函數是因爲,LuaBind 允許
非常量 -> 轉換在傳遞Lua值到C++的時候.之所以需要第二個函數是因爲,當Lua調用一個被智能指針持有
的類的成員函數的時候,this 指針必須是一個流指針.還有一個原因是,從Lua轉換到C++的時候,需要實現
智能指針到普通指針的轉換.看上去像這樣:
namespace luabind {

    template<class T>
    T* get_pointer(boost::shared_ptr<T>& p)
    {
        return p.get();
    }

    template<class A>
    boost::shared_ptr<const A>*
    get_const_holder(boost::shared_ptr<A>*)
    {
        return 0;
    }
}

第二個函數只在編譯時用於映射 boost::shared_ptr<A>到其常量版本 boost::shared_ptr<const A>.
它從來不會被調用,所以返回值是無所謂的(返回值的類型纔是關鍵).

這個轉換將這樣工作(假定 B 是A的基類):
從Lua到C++
Source Target
holder_type<A> A*
holder_type<A> B*
holder_type<A> A const*
holder_type<A> B const*
holder_type<A> holder_type<A>
holder_type<A> holder_type<A const>
holder_type<A const> A const*
holder_type<A const> B const*
holder_type<A const> holder_type<A const

從C++到Lua
Source Target
holder_type<A> holder_type<A>
holder_type<A const> holder_type<A const>
holder_type<A> const& holder_type<A>
holder_type<A const> const& holder_type<A const>

當使用持有器類型的時候,知道指針是不是合法(例如:非空)是很有用的.例如,當使用 std::auto_ptr 的時候,
持有器通過一個參數傳遞給函數的時候將會變得無效. 爲了這個目的,所有的對象實例都有一個成員叫: __ok.
struct X {};
void f(std::auto_ptr<X>);

module(L)
[
class_<X, std::auto_ptr<X> >("X")
.def(constructor<>()),

def("f", &f)
];
 5.0  Copyright (C) 1994-2003 Tecgraf, PUC-Rio
> a = X()
> f(a)
> print a.__ok
false

當註冊一個繼承樹的時候,所有的實例被智能指針持有的地方,所有的類必須包含持有器類型.例如:
module(L)
[
class_<base, boost::shared_ptr<base> >("base")
.def(constructor<>()),
class_<derived, base, boost::shared_ptr<base> >("base")
.def(constructor<>())
];

在內部, LuaBind 將會做必要的轉換於萃取自持有器的流指針之上.

8.8 拆分類註冊

在某些情況下,可能需要分開註冊一個類在不同的編譯單元. 部分原因可能是節約重編譯時間,而某些編譯器的
限制可能要求不得不分開註冊一個類.其實很簡單.考慮下面的示例代碼:
void register_part1(class_<X>& x)
{
x.def(/*...*/);
}

void register_part2(class_<X>& x)
{
x.def(/*...*/);
}

void register_(lua_State* L)
{
class_<X> x("x");

register_part1(x);
register_part2(x);

module(L) [ x ];
}

這裏,類X被分兩步註冊.兩個函數 register_part register_part2 可能被放到不同的編譯單元裏.
 

關於分開註冊一個模塊的信息請參閱: 分開註冊 章節.



9 對象

因爲函數必須能夠接受Lua值作爲參數,我們必須包裝之. 這個包裝被稱作
luabind::object. 如果你註冊的函數
接受一個對象,那它就可以匹配任何Lua值.爲了使用它,你需要包含頭文件: <luabind/object.hpp>.

摘要

class object
{
public:
template<class T>
object(lua_State*, T const& value);
object(from_stack const&);
object(object const&);
object();

~object();

lua_State* interpreter() const;
void push() const;
bool is_valid() const;
operator safe_bool_type () const;

template<class Key>
implementation-defined operator[](Key const&);

template<class T>
object& operator=(T const&);
object& operator=(object const&);

bool operator==(object const&) const;
bool operator<(object const&) const;
bool operator<=(object const&) const;
bool operator>(object const&) const;
bool operator>=(object const&) const;
bool operator!=(object const&) const;

template <class T>
implementation-defined operator[](T const& key) const

void swap(object&);

implementation-defined operator()();

template<class A0>
implementation-defined operator()(A0 const& a0);

template<class A0, class A1>
implementation-defined operator()(A0 const& a0, A1 const& a1);

/* ... */
};

當你需要一個Lua對象的時候,你可以通過=操作符給它賦一個新值.當你這麼做的時候,default_policy
會被用來轉換C++值到Lua. 如果你的 luabind::object  是一個table,你可以通過 []操作符或者迭代器
來訪問它的成員.[]操作符的返回值是一個代理對象,這個對象可以用於讀寫表裏的值(通過=操作符).

注意,沒有辦法知道一個Lua對象是否可以索引化訪問(  lua_gettable 不會失敗,要不成功,要不崩潰 ).
這意味着,如果你在一個不可以索引化訪問的東西上進行索引,你就只能靠自己了.Lua將會調用它的 panic()
函數.

還有一些自由函數可以用來索引一張table,參閱 相關函數 章節.

那個接受 from_stack 對象作爲參數的構造器是用來初始化一個關聯Lua棧值的對象的. from_stack 類型
有如下的構造器:
from_stack(lua_State* L, int index);

index參數就是原始的Lua棧的索引,負值是從棧頂開始索引的.你可以這樣用:
object o(from_stack(L, -1));

這將會創建一個 object的實例 o,並拷貝Lua棧頂的對象的值.

interpreter() 函數返回保存object實例的Lua狀態機.如果你想要直接用Lua函數操作object對象的實例,你
可以通過調用 push() 來把它壓入Lua棧.

==操作符將會在操作數上調用 lua_equal()並返回它的結果.

is_valid() 函數會告訴你object的實例是否已經初始化過了.通過默認構造器來初始化的實例是非法的.要使之
合法,你可以給其賦一個值.如果你想使一個 object 不合法,最簡單的辦法就是給它賦一個非法的 object.

operator safe_bool_type() 和 to is_valid() 是等價的.這意味着,下面的代碼片段是等價的:

應用程序操作符() 將會像對待一個函數那樣來調用綁定的值. 你可以給它任何數量的參數
(目前,
default_policy 將被用於轉換 ).返回的對象將代表函數的返回值(當前只支持一個返回值).該操作符
可能會拋出
luabind::error ,如果函數調用失敗.如果你想指定一個特殊的函數調用策略,你可以通過在函數
調用時使用 []操作符來指定策略.像這樣:
my_function_object(
2
, 8
, new my_complex_structure(6)
) [ adopt(_3) ];

這告訴 LuaBind 讓 Lua 接受所有權和負責傳入給lua函數的指針.

重要的是當Lua狀態機關閉的時候,所有的 object 的實例都會被析構.object實例會持有Lua狀態機的指針,並在
自己析構的時候釋放它的Lua對象.

這裏有一個函數怎樣使用 table 的例子:
void my_function(object const& table)
{
if (type(table) == LUA_TTABLE)
{
table["time"] = std::clock();
table["name"] = std::rand() < 500 ? "unusual" : "usual";

std::cout << object_cast<std::string>(table[5]) << "/n";
}
}


如果函數接受一個object作爲參數,那麼任何Lua值都將匹配這個參數.這就是爲什麼,我們必須保證入參是一個table

的原因.

std::ostream& operator<<(std::ostream&, object const&);


流操作符可以把object實例藉由 boost::lexical_cast  轉換到string或者方便打印輸出.這將會使用Lua的string

轉換函數.如果你用 tostring 去轉換一個C++對象,對應類型的流操作符將會被使用.

 

9.1 迭代器

 

有兩種迭代器. 普通迭代器將會使用對象的原方法(如果存在)來獲取值. 普通迭代器被稱爲 luabind::iterator. 另一個

迭代器被稱爲 luabind::raw_iterator ,它將忽略原方法而直接給出表裏的真實內容. 它們具有相同的接口,  都實現了

ForwardIterator 概念.大部分標準迭代器都有如下的成員和構造器:

class iterator
{
iterator();
iterator(object const&);

object key() const;

standard iterator members
};

 

接受一個 luabind::object 的構造器實際上是一個用於操作 object 的模板.通過傳入一個 object 給構造器來構造出

一個指向 object 裏的第一個元素的迭代器.

缺省的構造器將會初始化迭代器爲一個指向最後一個元素的後面位置的迭代器.這可以用來測試是否抵達了序列的末端.


迭代器的值類型是一個支持和 luabind::object 相同的操作的代理類型.這意味着,大部分情況下你可以當它就是一個原始
的 object 實例. 它們之間的不同之處在於,任何對代理的賦值操作都會導致值被插入到表中迭代器所指的位置.

key() 成員返回迭代器用於索引表的鍵.

一個迭代器的例子如下:
for (iterator i(globals(L)["a"]), end; i != end; ++i)
{
*i = 1;
}

end 迭代器是一個缺省的指向序列末尾的迭代器.在這個例子裏,我們簡單的迭代了表 a 裏面所有的實體,並將之賦值爲 1.


9.2 相關函數

這裏介紹些用於 對象 和 表 操作的函數.
int type(object const&);

這個函數將會返回lua類型索引.例如: . LUA_TNIL, LUA_TNUMBER 等.
template<class T, class K>
void settable(object const& o, K const& key, T const& value);
template<class K>
object gettable(object const& o, K const& key);
template<class T, class K>
void rawset(object const& o, K const& key, T const& value);
template<class K>
object rawget(object const& o, K const& key);

這些函數是用來索引 table 用的. settable 和 gettable 函數分別翻譯調用到 lua_settable lua_gettable 函數.
這意味着,你可以在對象上使用索引操作符.

rawset 和 rawget 將會翻譯調用到 lua_rawset 和 lua_rawget. 所以他們可以繞開任何原方法而給你表裏實體的
真實值.
template<class T>
T object_cast<T>(object const&);
template<class T, class Policies>
T object_cast<T>(object const&, Policies);

template<class T>
boost::optional<T> object_cast_nothrow<T>(object const&);
template<class T, class Policies>
boost::optional<T> object_cast_nothrow<T>(object const&, Policies);

object_cast 函數轉型對象的值到C++值.你可以給這個從lua到C++的轉換提供一個轉換策略.如果轉型失敗,
cast_failed 異常將被拋出. 如果你已經定義了
LUABIND_NO_ERROR_CHECKING  (參閱 編譯選項)宏,就不會
進行任何檢查,如果轉型非法,應用程序將會徹底崩潰. 不拋出異常的版本會返回一個沒有初始化的
boost::optional<T> 對象,由此來指出轉型不能進行.   

上面的函數的簽名確實是模板化的 object 參數,但是這裏你應該只傳遞 object 對象.

object globals(lua_State*);
object registry(lua_State*);

 

這些函數分別返回全局環境表和Lua註冊表.

object newtable(lua_State*);

 

這個函數創建一個新的 table 並以一個 object 來返回它.




10  在Lua裏定義類

 

 

作爲一個附加功能,LuaBind還提供了一個 Lua側OO系統來綁定C++函數和對象.
class 'lua_testclass'

function lua_testclass:__init(name)-- 譯註:這個風格類似Python的OO語法
self.name = name
end

function lua_testclass:print()
print(self.name)
end

a = lua_testclass('example')
a:print()

在Lua類之間可以使用繼承:

class 'derived' (lua_testclass)

function derived:__init() super('derived name')
end

function derived:print()
print('Derived:print() -> ')
lua_testclass.print(self)-- 譯註:注意這裏 : 和 . 的區別
end

 

這裏的 super 關鍵字用來初始化基類.用戶必須在構造器裏面第一個調用 super.

正如你在這個例子裏看到的,你可以調用基類的成員函數.你可以找到所有的基類成員,但是你必須把 this指針(self)

做爲函數的第一個參數.

 

10.1 在Lua裏繼承

你還可以從Lua側繼承一個C++類,並用Lua函數來覆寫虛函數.爲了實現這個,我們必須爲C++基類創建一個封裝類.

當我們實例化一個Lua類的時候,這個封裝類將持有Lua對象.

class base
{
public:
base(const char* s)
{ std::cout << s << "/n"; }

virtual void f(int a)
{ std::cout << "f(" << a << ")/n"; }
};

struct base_wrapper : base, luabind::wrap_base
{
base_wrapper(const char* s)
: base(s)
{}

virtual void f(int a)
{
call<void>("f", a);
}

static void default_f(base* ptr, int a)
{
return ptr->base::f(a);
}
};

...

module(L)
[
class_<base, base_wrapper>("base")
.def(constructor<const char*>())
.def("f", &base::f, &base_wrapper::default_f)
];

 


重要

因爲MSVC6.5不支持成員函數的顯示模板參數化,作爲成員函數 call()的替代, 你可以調用自由函數 call_member()並把 this指針作爲第一個參數傳入該函數.

 

注意,如果你同時綁定 base 類 和 base類封裝,你必須把基類和基類的封裝一起作爲模板參數提供給 class_

(就像上面的例子中所做的一樣).你指定它們的順序並不重要.你必須還要從wrapper註冊靜態版本的和虛函數版

本的封裝函數,這是讓LuaBind實現動態和靜態分派函數調用的必須.


重要


極其重要的是靜態(缺省)函數的簽名必須和虛函數一致.



 

 

 

 


Linker Lin翻譯此文檔只爲提供更多信息,轉載請保留 原文鏈接.


發表於 @ 2008年04月06日 06:46:00|評論(6 <script type=text/javascript>AddFeedbackCountStack("2254725")</script> )|

評論

#iambic 發表於2008-04-09 23:44:09  IP: 222.94.3.*
Cool!
#lidelong 發表於2008-12-08 21:24:45  IP: 165.246.43.*
LUABIND很好,能夠讓兩個語言邦定的工作變得很簡單
不過從很多人的反應來看,LUABIND有很嚴重的效率問題,關於這個問題,Linker怎麼看呢?
#lidelong 發表於2008-12-08 21:25:27  IP: 165.246.43.*
還有感謝下Linker的翻譯~
#linkerlin 發表於2008-12-09 20:05:13  IP: 222.66.145.*
@lidelong 效率問題確實存在.如果確實有問題,建議部分使用手動的方式.預先優化是罪惡的根源.
#fullsail 發表於2008-12-19 01:14:10  IP: 119.123.200.*
不錯,和tolua++比較,Linker感覺什麼更好一點?
#linkerlin 發表於2008-12-19 10:25:24  IP: 58.246.72.*
tolua++的自動化是很好的.
luabind更加自主可控一些,就是寫起來麻煩的多.
對含有'/0'的std::string的處理,tolua++有問題.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章