Lua Binding

루아를 공부하면서 궁금했던 부분인 어떻게 Lua와 C++사이에서 데이터 교환이 이뤄지는가에 대한 인터넷 조사 결과를 정리해보았다. 

Lua Binding을 왜 해야하는가?
  • 루아와 C++의 차이 때문에 직접적으로 데이터를 교환할 수 없다
    • 동적 데이터 타입 시스템인 Lua와 정적 데이터 타입 시스템인 C++
    • 자동 메모리관리 하는 Lua와 수동 메모리 관리하는 C++

그래서 루아에서는 C++과 값을 교환하는데 추상적인 스택의 개념을 사용하고 있다.
즉,  Lua로부터 값을 요청할 때는 Lua를 호출하여 스택에 요청하는 값을 push하며,  루아로 값을 전달할때는 스택에 값을 push한 후 루아 함수 호출하는 방식으로 사용한다. 

루아 기본 API에서 C++과 데이터 교환을 위한 수단을 제공하고 있다.  복잡하게 살펴볼것도 없이 간단하게만 다음의 경우에 대해서만 파악해보자.

1. C++에서 루아 변수를 사용하는 방법
2. 루아에서 C++변수를 사용하는 방법
3. C++에서 루아 함수를 사용하는 방법
4. 루아에서 C++함수를 사용하는 방법


1. C++에서 루아 변수를 사용하는 방법
루아에서 선언한 변수를 C++에서 사용하는 것이기 때문에 Lua에서는 변수를 선언해야 한다.
-- sample.lua
lua_int = 3

// main.cpp
// sample2.lua를 읽어들여 실행한다.
luaL_dofile( L, "sample.lua" );
// lua_int 값을 찾기 위해 키를 Stack에 Push
lua_pushstring( L, "lua_int" );
// Global 테이블에서 stack에 넣어진 키에 해당하는 값을 꺼내 
// Stack의 맨 위에 push 한다.
lua_gettable( L, LUA_GLOBALSINDEX );
// stack의 가장 윗부분의 값을 가져온다.
int lua_int = luaL_checknumber( L, -1 );
// stack에서 1개 pop한다.
lua_pop( L, 1 );
2. 루아에서 C++변수를 사용하는 방법
// main.cpp
int cpp_int = 100;
// 테이블에서 사용할 키를 Stack에 push
lua_pushstring( L, "cpp_int" );
// 값을 Stack에 push
lua_pushnumber( L, cpp_int ); 
// Global 테이블에 "appoint“를 키(key)로하여 cpp_int를 값으로 추가
lua_settable( L, LUA_GLOBALSINDEX );
luaL_dofile( L, "sample.lua" );

-- sample.lua
print("cpp_int = ".. cpp_int) // 잘 쓴다......
3. C++에서 루아 함수를 사용하는 방법
-- sample.lua
function LuaFunc()
     print( "in LuaFunc" )
     return 3
end

// main.cpp
luaL_dofile( L, "sample.lua" ); // sample.lua를 읽어 들여 실행 한다.
// Global 테이블에서 키가 LuaFunc인 것을 찾아 Stack의 맨 위에push
lua_getfield( L, LUA_GLOBALSINDEX, "LuaFunc" );
// stack에 push된 함수를 호출,
// 인자는 0이며, 반환 값은 1개, 에러 처리는 하지 않음
lua_pcall( L, 0, 1, 0 );
int luaFuncResult = (int)luaL_checknumber( L, -1 );

4. 루아에서 C++함수를 사용하는 방법

// main.cpp
int cpp_func( lua_State *L )
{
    // 매개변수가 2개 필요하므로 Stack에서 두개를 가져온다.
    int arg1 = (int)luaL_checknumber( L, -2 );
    int arg2 = (int)luaL_checknumber( L, -1 );
    lua_pushnumber( L, arg1+arg2 ); // 결과값을 스택에 넣는다.
    return 1;                       // 결과 값의 개수를 return
}
// 루아 테이블에서 키로 사용할 이름을 넣는다.
lua_pushstring(L, "Func" );
// 값으로는 함수의 포인터를 넘겨준다.
lua_pushlightuserdata(L, cpp_func);
lua_pushcclosure( L, cpp_func, 1 );
// 전역테이블에 키,값을 저장한다.
lua_settable(L, LUA_GLOBALSINDEX); 
luaL_dofile( L, "sample.lua" ); // sample.lua를 읽어 들여 실행 한다.

-- sample.lua
a = Func( 1, 2 ) // 잘 쓴다..
Lua API만을 이용하여 루아와 C++간의 데이터 교환은 가능하지만 복잡하기 때문에 바인딩 코드를 작성하는 사람은 Lua의 가상 스택과 바인딩을 위해 제공하는 API에 대해서 정확하게 파악하고 있어야만 한다.

바인딩할때마다 복잡하게 API를 사용하지 않고 좀더 편하게 할 수 있는 방법은 당연히 있다.
사용하기 편하도록 라이브러리를 배포하고 있는데 간단하게 몇가지만 정리해보면..


크게 LuaBind와 LuaTinker에 대해서 앞서 API를 써서 작성한 코드들이 어떻게 작성되는지에 대해 초점을 맞춰 비교해 보겠다. 

LuaBind
1. C++에서 루아 변수를 사용하는 방법
-- sample.lua
-- Lua쪽 변수에 값을 할당한다.
lua_int = cpp_int

// main.cpp
luaL_dofile(L, "sample.lua");
// global 테이블에서 lua_int 키를 찾아서 값을 가져온다.
size_t lua_int = luabind::object_cast(luabind::globals(L)["lua_int"]); 
2. 루아에서 C++변수를 사용하는 방법
// main.cpp
int cpp_int = 100;
// Global 테이블에 cpp_int 추가
luabind::globals(L)["cpp_int"] = cpp_int;
luaL_dofile(L, "sample.lua");

-- sample.lua
print("Get cpp cpp_int :", cpp_int) // 잘쓴다...
3. C++에서 루아 함수를 사용하는 방법
-- sample.lua
function LuaFunc()
     print( "In Lua LuaFunc()" )
     return 3
end

// main.cpp
luaL_dofile(L, "sample.lua");
// 반환값이int 이고 이름이 LuaFunc인 루아함수를 실행
int luaFuncResult = luabind::call_function(L,"LuaFunc"); 

4. 루아에서 C++함수를 사용하는 방법
// main.cpp
int func( int a, int b )
{
    return a + b;
}
// c함수를 lua로 연결 
luabind::module(L)
[ 
    luabind::def("Func", &func)
];
luaL_dofile(L, "sample.lua");

-- sample.lua
print( Func( 2, 2 ) )
LuaTinker
1. C++에서 루아 변수를 사용하는 방법
-- sample.lua
-- Lua쪽 변수에 값을 할당한다.
lua_int = cpp_int

// main.cpp
// sample1.lua 파일을 로드/실행 한다.
lua_tinker::dofile(L, "sample.lua");
// sample1.lua의 변수를 호출한다.
int lua_int = lua_tinker::get(L, "lua_int");
2. 루아에서 C++변수를 사용하는 방법
// main.cpp
int cpp_int = 100;
// LuaTinker를 이용해서 cpp_int를 Lua에 전달
lua_tinker::set(L, "cpp_int", cpp_int);
lua_tinker::dofile(L, "sample.lua");

-- sample.lua
print( cpp_int )
3. C++에서 루아 함수를 사용하는 방법
-- sample.lua
function LuaFunc()
     print( "In Lua LuaFunc()" )
     return 3
end

// main.cpp
// sample.lua 파일을 로드/실행한다.
lua_tinker::dofile(L, "sample.lua");
// sample1.lua의 LuaFunc함수를 실행, 반환값은 int
int luaFuncResult = lua_tinker::call(L, "LuaFunc");
4. 루아에서 C++함수를 사용하는 방법
// main.cpp
int cpp_func(int arg1, int arg2)
{
  return arg1 + arg2;
}
// Func 이름으로 Lua에서 사용할수있도록 함수를 등록
lua_tinker::def(L, "Func", cpp_func );

-- sample.lua
print( Func( 2, 2 ) )

LuaBind와 LuaTinker를 사용하면 Lua API나 스택에 대해서 세심하게 신경쓰지 않아도 Lua와 C++ 사이의 값교환이 가능함을 알 수 있다. 물론 LuaBind와 LuaTinker는 위에서 작성한 것이외에도 클래스, 상속관계... 여러가지 많은 기능을 제공한다.


LuaBind의 느린 빌드 속도를 줄이기 위해서 Pre-Compiled Header를 사용하는 방법에 대해서도 조사해보았다.

  • LuaBind 라이브러리를 include하는 코드를 미리 컴파일된 헤더로 만드는 경우 최초 미리 컴파일 헤더를 만들때를 제외하면 빌드 속도가 향상된다.
  • 하지만 리빌드를 할 경우에는 여전히 오랜 시간이 걸린다.
  • GPGStudy 게시판의 글을 보면 LuaBind와 미리컴파일된 헤더를 사용하다 빌드 시간이 오래 걸림으로 LuaTinker를 만들게 된듯한 글이 보임


Lua Binding 관련해서 볼만한 Game Programming gems 부분들..

  • 5권 1.10 루아를 게임에 통합
  • 6권 - 4.2 C/C++ 객체와 루아의 바인딩