본문 바로가기

3.구현/Lua

6. Lua의 C 바인딩

Lua와 C을 바인딩하는 내용을 살펴보겠다. 바인딩는 Lua에서 C에 있는 값이나 함수를 사용거나 C에서 Lua에 있는 값이나 함수를 가져다 사용하겠다는 의미이다. 추후에 C++과 바인딩도 살펴보도록 하겠다. C++바인딩을 간략히 말하면 C++의 객체지향적 개념을 lua의 테이블에 도입하여 사용하겠다는 의미이다.

일단 C와 바인딩이 쉽고 Lua에서도 C를 위한 API를 제공하기 때문에 먼저 C 바인딩을 살펴보고 주소 API에 대해서 간략하게 살펴보기로 하겠다.

작성자: http://ospace.tistory.com/,2013.04.15 (ospace114@empal.com)

C에서 Lua바인딩 위한 기본 설정

기본적으로 Lua 라이브러리가 설치되어 있어야 한다. 이는 Lua.org에서 download 항목에서 다운로드 할 수 있다. 이미 빌드된 바이너리를 사용해도 되고 소스코드를 직접 빌드해도 된다.

MS Windows환경이라면 설치파일이 있으니 받아서 설치하면 된다.

스택 이해하기

C와 lua 간의 연동에 중요한 요소는 가상 스택이다. 이를 통하여 lua와 c간에 값을 주고 받는다. Lua 값은 일반적인 c와는 조금 틀리다. 대표적인 lua 자료형으로 nil, number, string 등이 있다.

lua의 스택을 액세스 방법은 2가지가 있다. 절대/상대 접근이 있다. 양수 값은 절대 접근 음수 값은 상대 접근으로 구분한다.

절대 접근 방법은 스택 base에서 시작하여 양수 1, 2, 3으로 순서를 부여하는 방법이다. 상대 접근은 스택 top에서 시작하여 -1, -2, -3으로 순서를 부여한다. 당연히 값의 범위는 스택 base에서 top내에서만 가능하다.

lua_checkstack: stack size 확인하기 위한 lua c api.

LUA_MINSTACK 20으로 정의됨.

허용 가능한 스택 index 범위는

(index < 0 && abs(index) <= top) || (index > 0 && index <= stackspace )

글로벌 테이블의 의사인덱스: LUA_GLOBALSINDEX

Lua에서 C함수 호출

  1. C함수를 정의함
  2. C함수를 lua에 등록
  3. lua에서 등록된 함수 호출

C함수 정의

lua에 등록가능한 C함수는 시그니처가 있다.

int (*cfunction)(lua_State*);

위와 같은 형태이다. 리턴되는 int형은 lua로 반환되는 리턴값 개수를 표시한다.lua_State형 인자를 통해서 lua와 값을 스택을 통해서 주고 받는다.

간단한 프로그램을 작성해보자. lua에서 문자를 받아서 출력하는 c 함수이다.

int lua_hello(lua_State* L) {
    const char* name = luaL_checkstring(L, 1);
    printf("hello %s\n", name);
}

함수명은 임의로 정하면 된다. luaL_checkstring()를 통해서 lua에서 넘어온 값을 획득한다. 양수 1은 스택 base에서 첫번째 값으로 lua에서 넘어온 처음 인자에 해당하는 값이다. 음수(-1)가 된다면 lua에서 넘어온 마지막 인자 값에 해당한다. 인자가 한 개만 넘어온다면 같은 값을 가리키게 된다.

C함수 등록

이젠 앞에서 정의한 함수를 등록해보자.
이때 사용하는 lua c api는 다음과 같다. c함수 명과 등록될 lua 함수 명과는 다를 수 있다.

lua_register(lua, "function_name", function_name);

앞의 예제에 적용해보자.

int main() {
    lua_State *lua = luaL_newstate();
    luaL_openlibs(lua);
    lua_register(lua, "hello", lua_hello);
    luaL_dofile(lua, "hello.lua");
    lua_close(lua);
    return 0;
}

lua_hello()함수를 "hello" lua함수로 등록하여 "hello.lua"를 호출해서 실행하는 코드이다.

별다른 어려움은 없을 것이다. 그러면, 실제 lua 에서 호출하는 예제를 살펴보다.

Lua 스크립트 에서 C함수 호출

아래는 앞의 hello.lua 스크립트 내용이다. 앞에서 lua_register()에 의해 등록된 "hello" 함수 명을 사용했다.

hello("ospace")

호출은 간단하다. 실행한 결과는 다음과 같다.

hello ospace

C에서 lua 함수 호출

앞의 내용과 반대로 lua에 있는 함수를 c에서 호출보자. lua 함수 호출는 2단계로 나눠진다.

  1. lua함수를 스택에 불러옴
  2. 스택에 있는 함수 실행

lua함수를 스택에 불러옴

case1)

lua_pushstring("function_lua");
lua_gettable(lua, LUA_GLOBALSINDEX);

case2) 한줄로 줄이면,

lua_getfield(lua, LUA_GLOBALSINDEX, "function_lua");

case3) 더 줄이면

lua_getglobal(lua, "function_lua");

lua_getglobal은 매크로로서 lua_getfield 함수를 호출한다. 최근 5.2에서 LUA_GLOBALSINDEX를 사용할 수 없게 되었다. 그렇기 때문에 lua_gettable()과 lua_getfield()를 사용할 수 없게 되었다.

위의 3가지 중에 하나를 선택하면 된다. 성공이면 다음 실제 함수를 실행해보자.

쓰레드 환경은 항상 의사 인덱스 LUA_GLOBALSINDEX에 있다. 구동 중인 C함수 환경은 의사 인덱스 LUA_ENVIRONINDEX에 있다.

스택에 있는 함수 실행

스택에는 실제 함수는 없다. 함수를 접근하기 위한 참조 정보만 있을 뿐이다. 이를 정보를 이용해 실제 루아 함수 실행코드로 접근할 수 있게 해주는 lua c api가 lua_call()이다.

void lua_call(lua_State *L, int nargs, int nresults);

nargs는 함수 실행에 사용되는 인자 개수이고, nresults는 함수 실행결과 반환값 개수이다. 해당 모든 값은 스택을 통해서 관리하기 위한 정보이다.

그럼 실제 적용한 예제를 보자.

lua_State* lua = lua_newstate();
//...(중략)
lua_getglobal(lua, "print");
if (!lua_isfunction(lua, -1)) {
    fprintf(stderr, "print is not function\n");
    return 1;
}
lua_pushstring(lua, "hello lua");
lua_call(lua, 1, 0);
lua_close(lua);

코드가 좀더 추가되었다. 중간에 lua_isfunction()은 방금 전에 스택에 추가된 값이 함수인지 판별하는 lua c api이다. 위의 예제에서는 제거해도 무방하지만, 이름이 겹치거나 잘못 사용할 수도 있기 때문에 사용해도 좋다.

그리고 lua_pushstring()에 의해 lua로 넘겨질 인자 값을 저장하고 있다. lua_call()에 의해서 넘겨지는 인자 개수는 1개이고 lua에서 반환되는 값은 0개로 지정하고 있다. 만약에 반환 값이 있다면, lua_call() 다음에 다시 스택에 있는 값을 꺼내서 사용하면 될 것이다.

리턴이 있는 LUA 함수 실행

먼저 간단한 스크립트 함수를 정의해보자.

function test1()
return 1, "abc"
end

이제 c함수에서 위의 스크립트를 호출하고 반환값을 획득해보자. 일단 반환되는 값의 개수는 가변적인 경우를 보겠다.

test1() 함수에서 반환되는 값의 개수는 lua_gettop()를 사용해서 획득할 수 있다.

int top = lua_gettop(l);
lua_getglobal(l, "test1");
if (!lua_isfunction(l, -1)) exit(1);

int r = lua_pcall(l, 0, LUA_MULTRET, 0);
if (r) {
    fprintf(stderr, "call error: %s\n", lua_tostring(l, -1));
    exit(1);
}

int ret_cnt = lua_gettop(l) - top;

int ret_val1 = 0;
std::string ret_val2;

if (1<ret_cnt) {
    ret_val2 = luaL_checkstring(l, -1);
    lua_pop(l, 1);
}

if (0<ret_cnt) {
    ret_val1 = (int)luaL_checknumber(l, -1);
    lua_pop(l, 1);
}

luaL_checkstring(), luaL_checknumber() 함수에 의해서 스택에 있는 반환값을 획득한다. 그리고 lua_pop() 스택에서 값을 추출하면 다시 스택에 다른 반환값을 획득할 수 있다. 주의 할 것은 마지막에 반환되는 값이 스택 맨 위에 저장된다.

예를 들어 "return 1"과 "return 1, 'a'"가 있다면 전자는 스택에 "1"값 한개만 맨 위에 저장되지만, 후자는 먼저 스택에 "1"값을 넣고 다음에 "a" 값을 넣는다. 맨 위에는 "a"가 저장된다. 즉, 반환 값의 맨 위의 값은 다른 타입이된다. 그렇기 때문에 리턴되는 값 개수가 가변적인 경우 값을 가져오는 순서가 달라지는 점을 주의해야 한다.

결론

lua와 c간에 인터페이스는 간단하다. 단 c 함수 시그니처와 주고 받는 값이 스택을 통해서 이뤄진다는 부분이다.

사실 스택을 이용하여 값을 주고 받는 방식이 익숙하지 않으면 처음에 이해하는데 쉽지 않다. 본인도 실제로 부딕치면서 조금 더 이해할 수 있었다. 즉, 스택을 통해 값을 처리하는 방식에 익숙해져야만 한다.

사실 c 함수와 lua 함수간에 인터페이스는 극히 일부분일 뿐이다. lua에는 메소드에 의한 호출과 테이블 및 메타 테이블이라는 개념이 있다. 이러한 모든 것도 스택을 통해서 이뤄진다. 당연히 추가적인 lua c api를 습득해야 한다.

부족하지만, 이상으로 c와 lua간에 바인딩을 살펴보았다.

모드 즐프하기를.ospace

참조

[1] http://lua-users.org/wiki/CppBindingWithLunar

반응형

'3.구현 > Lua' 카테고리의 다른 글

Lua에서 객체지향적 코딩 하기  (0) 2012.02.15
lua 동향(2011/10)  (0) 2011.10.26
5. Lua 메타테이블(metatable)  (0) 2008.02.28
Lua 5.x와 Lua 4.0 호환성  (0) 2008.01.21
4. Lua의 조건문 반복문  (0) 2008.01.21