[lua] luabind by 신동호

원본 링크: http://www.rasterbar.com/products/luabind/docs.html



1. Introduction

Luabind 는 c++ 과 Lua 를 연결할 수 있도록 도와주는 라이브러리로, c++ 에서 작성된 함수/클래스를 루아에서 접근할 수 있도록 만들어 주며 Lua 에 정의된 classes 를 사용하거나 lua 에서 정의된 classes 혹은 c++ classes 로부터 상속받아 구현할 수 있는 기능도 지원한다. Lua 5.0 이상에서는 c++ class 를 상속받아 lua classs 를 구현할 수 있다. 

Luabind 는 template meta programinig 을 사용하여 구현되었다. 이 말이 의미하는 것은, luabind 의 bind 내용을 컴파일 하기 위해서 다른 컴파일러가 필요 없다는 의미이며 당신이 등록하고자 하는 각 함수의 정확한 signature 를 알 필요가 없다는 의미이기도 하다. Luabind 라이브러리가 컴파일 타임에 signature 를 포함하여 코드를 생성해 줄 것이다. 이런 방식의 유일한 문제는 컴파일 타임이 늘어난다는 것인데, 이 시간을 줄이려면 등록하려는 코드를 모두 하나의 cpp file 에 넣는 것이 좋다.

Luabind 는 MIT 라이센스를 따른다.

2. Features

luabind 는 아래 내용을 지원한다.

* 함수 오버로드
* lua 에서 c++ 클래스 사용하기
* 맴버 함수 오버로드
* 연산자
* 프로퍼티
* enum
* c++ 에서 lua 함수/클래스 사용하기
* lua 혹은 c++ 클래스 상속
* c++ 클래스의 가상 함수 오버라이드
* 등록된 타입들의 함축된(implicit는 explicit-명시적인 케스팅-의 반대말입니다.) 케스팅
* 최적화된 시그니쳐 매칭
* 리턴/파라메터에 대한 정책(policy-morden cpp 책의 단위전략-unit policy-참조) 지원

3. Portability

4. Building luabind

5. Basic usage

luabind 를 사용하기 위해 아래와 같이 lua.h 와 luabind 의 메인 헤더 파일을 코드에 포함시킨다.

extern "C"
{
    #include "lua.h" // lua 는 원시 c 로 작성되어 c++ 에서 사용하려면 extern 으로 감싸야 합니다.
}
#include "luabind/luabind.hpp"

이제 luabind::open(lua_State*) 를 호출하여 lua 에 바인드할 객체를 생성하고 생성된 lua_State* 에 원하는 함수/클래스를 등록할 수 있다. 이와 같은 방법은 언제든지 등록된 class 를 제공하거나 다른 set 의 lua_State* 를 사용할 수 있는 편의가 있다.

luabind 는 lua.h 를 바로 include 하고 있지않지만, "luabind/lua_include.hpp" 에는 등록이 되어있다. 만약 이 파일을 사용한다면 추가로 필요한 헤더 파일은 이 파일을 수정하여 사용하면 된다.

5.1 Hello world

#include <iostream>
#include "luabind/luabind.hpp"

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

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

    open(L);

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

    return 0;
}

결과

Lua 5.0  Copyright (C) 1994-2003 Tecgraf, PUC-Rio
> loadlib('hello_world.dll', 'init')()
> greet()
Hello world!
>

6. Scopes

Lua 에 등록된 모든 것들은 특정 namespace 혹은 전역 으로 등록된다.

module(L)
[
    // 정의
];

위와 같이 정의하는 경우 모든 함수/클래스들이 루아에 전역 namespace 로 등록된다. 만약 특정 namespace 를 주려고 한다면 아래와 같이 module 생성 부분에 이름을 넣어 주면 된다.

module(L, "my_library")
[
    // 정의
];

이 모듈 안에 정의한 모든 함수/클래스는 my_library 테이블에 들어간다.

만약, 중첩된 namespace 를 사용하고 싶다면 아래와 같이 luabind::namespace_ 클래스를 사용하면 된다.

module(L, "my_library")
[
    // 정의

    namespace_("detail")
    [
        // 정의
    ]
];

또한, 아래 두 가지 예제는 동일한 의미를 가진다.

1)
module(L)
[
    namespace_("my_library")
    [
        // 정의
    ]
];

2)
module(L, "my_library")
[
    // 정의
];

각 정의는 반듯이 콤마로 분리되어야 한다. 아래를 보자.

module(L)
[
    def("f", &f),
    def("g", &g),
    class_<A>("A")
        .def(constructor<int, int>),
    def("h", &h)
];

7. Binding functions to Lua

luabind::def() 함수를 사용하여 함수를 바인딩 할 수 있다. def 는 다음과 같이 정의되어 있다.

template <class F, class polices>
void def(const char* name, F f, const Polices&);

* name 은 Lua 에 등록할 이름을 말한다.
* F 는 등록하고자 하는 함수의 포인터를 의미한다.
* Policies 는 어떻게 파라메터와 반환 값이 함수에서 처리되는지에 대한 정책으로 반듯이 사용할 필요는 없다.

다음 예제는 float std::sin(float) 함수를 등록한다.

module(L)
[
    def("sin", &std::sin)
];

7.1 Overloaded functions

만약, 함수를 오버로딩 하고자 한다면 명시적(위에서 언급한 implicit 의 반대말 explicit 입니다.) 인 signature 를 적어주어야 한다. 다음 예제를 보자.

module(L)
[
    def("f", (int(*)(const char*))&f),
    def("f", (void(*)(int)&f)
];

7.2 Signature matching

luabind 는 당신이 정의한 함수의 signature 가 Lua stack 과 매칭되는지 확인하여 코드를 생성한다. 이 과정에서 상속받은 클래스 사이의 암묵적인(implicit) 타입 변환이 이루어질 수 있으며, 최소한의 암묵적 변환을 수행할 수 있는 함수와 매칭될 것이다. 오버로드로 정의된 함수가 호출되었는데 매칭되는 함수가 없는 모호한 상태가 된다면, 실행 시간에 런타임 에러가 발생할 것이다. 간단한 예로, int 를 파라메터로 받는 함수와 float 를 파라메터로 받는 두개의 함수를 등록한 경우, Lua 는 이 둘을 구분하지 못하게 된다.

모든 오버로드 테스트가 완료되었을 때, lua 는 항상 최선의 매치를 찾을 것이다.(먼저 발견된 매치가 아니다) 이 말은 const 함수와 그렇지 않은 함수를 구별할 수 있다는 의미이다.

다음 예제를 살펴보자.

struct A
{
    void f();
    void f() const;
};

const A* create_a();

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

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

아래는 lua 코드이다.

a1 = create_a()
a1:f() -- the const version is called, 포인터로 접근했으므로 내부를 변경할 수 없는 void f() 가 호출된 듯

a2 = A()
a2:f() -- the non-const version is called

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

g(a) -- calls g(A*)
g(b) -- calls g(B*)
g(c) -- calls g(B*) C 는 A 를 상속받은 B 를 상속하였지만 g(A*) 를 호출하기 위해서는 두번의 케스팅이 필요하고 g(B*) 의 경우 한번의 케스팅으로 접근할 수 있다. 위에서 언급한 최소한의 케스팅 구문을 찾는 다는 말이 이것을 의미하는 듯 하네요.

7.3 Calling Lua functions

7.4 Using Lua threads

8. Binding classes to Lua

클래스를 등록하려면 class_ 를 사용한다. 이 이름은 c++ 키워드인 class 와 닮아서 보다 직관적이다. class_ 는 def() 를 오버로딩한 맴버 함수를 가지고 있으며, def() 를 사용하여 클래스에 맴버 함수, 연산자, 생성자, enum, property 를 등록할 수 있다. class_ 는 등록한 class 의 this 포인터를 반환하는데, 이 포인터에 다른 맴버들을 직접 연결시키면 된다.

간단한 예제를 살펴보자. 아래는 등록할 클래스이다.

struct Test
{
    Test(const std::string& str) : str_(str) {}
   
    void print() { std::cout << str_ << std::endl; }
private:
    std::string str_;
};

다음은 Lua 에 등록하는 부분이다.

module(L)
[
    class_<Test>("Test")
        .def(constructor<const std::string&>())
        .def("print_string", &Test::print)
];

다음은 실행 결과이다.

Lua 5.0  Copyright (C) 1994-2003 Tecgraf, PUC-Rio
> a = testclass('a string')
> a:print_string()
a string

전역 함수를 맴버 함수로 등록하는 것도 가능하다. 이렇게 등록하고자 하는 함수의 요구 조건은 함수의 첫 번째 파라메터가 클래스의 포인터(pointer, const pointer, reference, const reference) 를 가지는 것 뿐이다. 이렇게 등록된 함수는 클래스 포인터를 가지는 첫 번째 파라메터를 제외한 나머지 파라메터만 lua 에서 볼 수 있다. 예제를 보자.

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

아래와 같이 클래스 등록에 plus 를 등록할 수 있다.

class_<A>("A")
    .def("plus", &plus)

이제 plus() 는 A 의 맴버 함수가 되었다. // lua 에서 int A::plus(int v) 와 같은 맴버로 접근할 수 있습니다.

8.1 Overloaded member functions

// 여기서부터는 위에서 언급한 내용이 반복되니 예제 위주로 설명토록 하겠습니다.

struct A
{
    void f(int);
    void f(int, int);
};

module(L)
[
    class_<A>("A")
        .def("f", (void(A::*)(int))&A::f) // A 클래스의 void f(int) 만 bind 하였습니다.
];

8.2 Properties

struct A
{
    int a;
};

module(L)
[
    class_<A>("A")
        .def_readwrite("a", &A::a) // A.a 에 대한 접근 권한, def_readonly 로 하면 읽기만 할 수 있습니다.
];
// 이후 구조적으로 명확한 경우 module(L) 구문은 생략합니다.

bind 한 클래스가 원시 형태가 아닌 일반 클래스의 타입을 맵버 변수로 가지고 있다면, 자동으로 getter 가 등록되어 클래스의 참조를 반환하게 되며 . 연산자를 사용하여 접근할 수 있다.

struct A { int m; };
struct B { A a; };

B 를 바인딩 하면 다음과 같이 접근할 수 있다. // 해보지 않고는 모르겠지만 A 또한 바인딩 되었다는 가정에서만 동작할 것으로 보입니다.

b = B()
b.a.m = 1

다음은 lua 에서 특정 맴버 변수를 setter, getter 를 사용하여 접근할 수 있도록 구현한 예제입니다.

struct A
{
    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)

8.3 Enums

lua 의 enums 는 모두 일반 정수로 인식된다. 

module(L)

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

사용 예,

 Lua 5.0  Copyright (C) 1994-2003 Tecgraf, PUC-Rio
> print(A.my_enum)
4
> print(A.another_enum)
6

8.4 Operators

operators 를 bind 하기 위해서는 "luabind/operator.hpp" 를 include 해야 한다.

operator 를 등록하는 과정은 간단하다. 추가할 operator 의 정의를 적고, 자기 자신의 type 이 들어갈 자리에 luabind::self 를 적어준다.

struct vec
{
    vec operator+(int s);
};

module(L)
[
    class_<vec>("vec")
        .def(self + int()) // const 의 경우 const_self 를 사용한다.
];

주의할 점은 이 연산자가 vec 내부에 있거나 외부에 있거나 luabind 가 신경쓰지 않고 결합시킨다는 점이다.

lua 는 다음과 같은 operators 를 제공한다.

+ - * / == < <=

Lua 는 !=, >, >= 와 같은 연산자를 지원하지 않는다.

// 이하 복잡한 내용은 생략..-_-

8.5 Nested scopes and static functions

클래스에 내부 클래스를 정의하는 것도 가능하다. 이 방법은 중첩 클래스나 정적 함수를 렙핑하려고 할 때 유용하게 사용할 수 있다.

class_<foo>("foo")
    .def(constructor<>()
    .scope
    [
        class_<inner>("nested"),
        def("f", &f)
    ];

이 예제에서 f 는 foo 클래스의 정적 맵버 함수처럼 동작한며 nested 는 foo 의 내부 클래스가 된다.

또한, 같은 문법을 사용하여 클래스에 namespace 를 추가하는 것도 가능하다.

8.6 Derived classes

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

위 구조는 다음과 같이 등록할 수 있다.

module(L)
[
    class_<A>("A"),
    class_<B, A>("B")
];

사실, class_<B, A>("B") 는 class_<B, bases<A> >("B") 와 같이 써야 하지만 상속 받는 객체가 하나뿐이라면 생략할 수 있다. 만약 B 가 C 도 상속받는다면

module(L)
[
    class_<B, bases<A, C> >("B")
];

와 같이 등록해 주어야 한다.

NOTE: 만약 각 객체들의 상속 관계를 정의하지 않는다면, luabind 는 type 들 간의 암묵적인 형 변환을 할 수 없을 것이다.

8.7 Smart pointers

8.8 Splitting class registerations

9. Object

9.1 Iterators

9.2 Related functions

10. Defining classes in Lua

10.1 Deriving in lua

10.1.1 OBJECT IDENTITY

10.2 Overloading operators

10.3 Finalizers

10.4 Slicing

11 Exceptions

당신이 등록한 어떤 함수가 예외를 발생시킨다면 luabind 에 의해 감지되어 예외에 대한 문자열을 변환하고 lua_error() 를 발생시킬 것이다. 예외가 발생하여 std::exception 혹은 const char* 문자열이 Lua 스텍에 쌓였다면, std::exception::what() 을 사용하여 그 문자열을 반환받을 수 있다.

사용자가 정의한 함수로부터 발생한 예외는 luabind 가 감지할 수 있다. 하지만 lua 자체가 발생한 예외가 있다면 그 부분은 lua 가 순수 c 로 되어 있어 스텍으로 부터 예외를 알아낼 수 없을 것이다.

lua 코드가 호출한 어떤 함수들은 luabind::error 를 발생시킨다. 이 예외는 lua 가 실행 타임에 에러를 발생했다는 것을 알려준다. 이 에러 메시지는 lua 스텍의 가장 위에 있다. ...(중략-중요한 내용 같은데 해석이 어렵네요)...

try
{
    lua_state L = lua_open();
}
catch (luabind::error& e)
{
    lua_State* L = e.state();
}

12. Policies

때로는 luabind 가 어떻게 파라메터와 반환 값을 pass 할 것인지 제어할 필요가 있다. 이러한 제어 방법을 우리는 정책(policy) 라고 부른다.

12.1 adopt

12.1.1 MOTIVATION
언어 영역을 넘어 소유권을 변경할 때 사용한다.

12.1.2   Defined in
#include <luabind/adopt_policy.hpp>

12.1.3   Synopsis
adopt(index)

12.1.4   Parameters
Parameter Purpose
index The index which should transfer ownership, _N or result

12.1.5   Example

X* create()
{
    return new X;
}

...

module(L)
[
    def("create", &create, adopt(result))
];

12.2   dependency

12.2.1   Motivation
값들 사이에 의존적인 생존 타임을 만들기 위해 사용한다. 예를 들어 어떤 클래스의 내부 참조를 반환할 때 필요하다.

12.2.2   Defined in
#include <luabind/dependency_policy.hpp>

12.2.3   Synopsis
dependency(nurse_index, patient_index)

12.2.4   Parameters
Parameter Purpose
nurse_index The index which will keep the patient alive.
patient_index The index which will be kept alive.

12.2.5   Example

struct X
{
    B member;
    B& get() { return member; }
};

module(L)
[
    class_<X>("X")
        .def("get", &X::get, dependency(result, _1))
];

12.3   out_value

12.3.1   Motivation
This policy makes it possible to wrap functions that take non-const references or pointer to non-const as it's parameters with the intention to write return values to them. Since it's impossible to pass references to primitive types in lua, this policy will add another return value with the value after the call. If the function already has one return value, one instance of this policy will add another return value (read about multiple return values in the lua manual).

12.3.2   Defined in
#include <luabind/out_value_policy.hpp>

12.3.3   Synopsis
out_value(index, policies = none)

12.3.4   Parameters
Parameter Purpose
index The index of the parameter to be used as an out parameter.
policies The policies used internally to convert the out parameter to/from Lua. _1 means to C++, _2 means from C++.

12.3.5   Example

void f1(float& val) { val = val + 10.f; }
void f2(float* val) { *val = *val + 10.f; }

module(L)
[
    def("f", &f, out_value(_1))
];

Lua 5.0  Copyright (C) 1994-2003 Tecgraf, PUC-Rio
> print(f1(10))
20
> print(f2(10))
20

12.4   pure_out_value
12.4.1   Motivation
This works exactly like out_value, except that it will pass a default constructed object instead of converting an argument from Lua. This means that the parameter will be removed from the lua signature.

12.4.2   Defined in
#include <luabind/out_value_policy.hpp>

12.4.3   Synopsis
pure_out_value(index, policies = none)

12.4.4   Parameters
Parameter Purpose
index The index of the parameter to be used as an out parameter.
policies The policies used internally to convert the out parameter to Lua. _1 is used as the internal index.

12.4.5   Example
Note that no values are passed to the calls to f1 and f2.

void f1(float& val) { val = 10.f; }
void f2(float* val) { *val = 10.f; }

module(L)
[
    def("f", &f, pure_out_value(_1))
];

Lua 5.0  Copyright (C) 1994-2003 Tecgraf, PUC-Rio
> print(f1())
10
> print(f2())
10

12.5   return_reference_to
12.5.1   Motivation
It is very common to return references to arguments or the this-pointer to allow for chaining in C++.

struct A
{
    float val;

    A& set(float v)
    {
        val = v;
        return *this;
    }
};

When luabind generates code for this, it will create a new object for the return-value, pointing to the self-object. This isn't a problem, but could be a bit inefficient. When using the return_reference_to-policy we have the ability to tell luabind that the return-value is already on the lua stack.

12.5.2   Defined in
#include <luabind/return_reference_to_policy.hpp>

12.5.3   Synopsis
return_reference_to(index)

12.5.4   Parameters
Parameter Purpose
index The argument index to return a reference to, any argument but not result.

12.5.5   Example
struct A
{
    float val;

    A& set(float v)
    {
        val = v;
        return *this;
    }
};

module(L)
[
    class_<A>("A")
        .def(constructor<>())
        .def("set", &A::set, return_reference_to(_1))
];

Warning

This policy ignores all type information and should be used only it situations where the parameter type is a perfect match to the return-type (such as in the example).

12.6   copy
12.6.1   Motivation
This will make a copy of the parameter. This is the default behavior when passing parameters by-value. Note that this can only be used when passing from C++ to Lua. This policy requires that the parameter type has an accessible copy constructor.

12.6.2   Defined in
#include <luabind/copy_policy.hpp>

12.6.3   Synopsis
copy(index)

12.6.4   Parameters
Parameter Purpose
index The index to copy. result when used while wrapping C++ functions. _N when passing arguments to Lua.

12.6.5   Example
X* get()
{
    static X instance;
    return &instance;
}

...

module(L)
[
    def("create", &create, copy(result))
];

12.7   discard_result
12.7.1   Motivation
This is a very simple policy which makes it possible to throw away the value returned by a C++ function, instead of converting it to Lua.

12.7.2   Defined in
#include <luabind/discard_result_policy.hpp>

12.7.3   Synopsis
discard_result

12.7.4   Example
struct X
{
    X& set(T n)
    {
        ...
        return *this;
    }
};

...

module(L)
[
    class_<X>("X")
        .def("set", &simple::set, discard_result)
];

12.8   return_stl_iterator
12.8.1   Motivation
This policy converts an STL container to a generator function that can be used in lua to iterate over the container. It works on any container that defines begin() and end() member functions (they have to return iterators).

12.8.2   Defined in
#include <luabind/iterator_policy.hpp>

12.8.3   Synopsis
return_stl_iterator

12.8.4   Example
struct X
{
    std::vector<std::string> names;
};

...

module(L)
[
    class_<A>("A")
        .def_readwrite("names", &X::names, return_stl_iterator)
];

...

> a = A()
> for name in a.names do
>  print(name)
> end

12.9   raw
12.9.1   Motivation
This converter policy will pass through the lua_State* unmodified. This can be useful for example when binding functions that need to return a luabind::object. The parameter will be removed from the function signature, decreasing the function arity by one.

12.9.2   Defined in
#include <luabind/raw_policy.hpp>

12.9.3   Synopsis
raw(index)

12.9.4   Parameters
Parameter Purpose
index The index of the lua_State* parameter.

12.9.5   Example
void greet(lua_State* L)
{
    lua_pushstring(L, "hello");
}

...

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

> print(greet())
hello

12.10   yield
12.10.1   Motivation
Makes a C++ function yield when returning.

12.10.2   Defined in
#include <luabind/yield_policy.hpp>

12.10.3   Synopsis
yield

12.10.4   Example
void do_thing_that_takes_time()
{
    ...
}

...

module(L)
[
    def("do_thing_that_takes_time", &do_thing_that_takes_time, yield)
];

 

13. Splitting up the registeration

module 등록은 분리될 수 있다.

a.cpp:

luabind::scope register_a()
{
    return
        class_<a>("a")
            .def("f", &a::f)
            ;
}
b.cpp:

luabind::scope register_b()
{
    return
        class_<b>("b")
            .def("g", &b::g)
            ;
}
module_ab.cpp:

luabind::scope register_a();
luabind::scope register_b();

void register_module(lua_State* L)
{
    module("b", L)
    [
        register_a(),
        register_b()
    ];
}

 

 

 

 

 

 

 

 

 

 

 


 


트랙백

이 글과 관련된 글 쓰기 (트랙백 보내기)
TrackbackURL : http://aronze.egloos.com/tb/501614 [도움말]

덧글

덧글 입력 영역