윈도우 프로그래밍과 루아의 대통합 루아와 C/C++ 바인딩 하기
윈도우 프로그래밍과 루아의 대통합 루아와 C/C++ 바인딩 하기
스크립트 언어 중 게임 분야에서 특히 인기를 얻고 있는 것이 있으니 이것은 바로 루아다. 루아는 C/C++와 바인딩(통신)하거나 내장시키는 것이 자류로운 덕에, 국내외 여러 게임에 유행처럼 적용되고 있는 실정이다. 특집 5부에서는 이 루아의 특징에 대해 살펴보고 루아에서 C/C++ 모듈을 사용하는 방법에 대해 알아본다.
루아는 애초에 다른 언어들과의 접착(Glue) 언어로 설계된 덕에 C나 C++로 만들어진 모듈(dll이나 lib)과의 바인딩이 자유롭다. 또, 확장 언어와 스크립팅 언어를 지향하고 있는 루아는 충분히 콤팩트하고 빠른 개발을 할 수 있다는 장점 덕분에 게임 개발 분야에서 특히 인기를 얻고 있다. 루아가 적용된 게임 중 대표 격인 월드 오브 워크래프트는 사용자 인터페이스와 캐릭터 애니메이션 등에 루아를 사용하고 있다. 그 밖에도 원숭이 섬의 비밀과 MDK 2, 네버 윈터 나이트 등의 게임 등에도 루아가 적용되었다.
이처럼 게임 분야를 중심으로 하여 급속도로 사용자층이 늘고 있는 반면에 루아에 대한 국내 자료는 아직 턱없이 부족한 상황이다. 또, 지면상 이 글에서 소개되는 내용 또한 다양한 시각과 다양한 활용 방법들을 제시하기 어려우니 더 다양한 내용에 대해 알고자 한다면 루아 공식 홈페이지(http://www.lua.org)의 레퍼런스 매뉴얼 또는 관련서적을 참고하길 바란다.
접착 언어 루아
루아는 ANSI C로 구현된 스크립트 언어이다. 브라질의 Poni tifical Catholic 대학교의 PUC-Rio 팀에서 만들어진 루아의 최신 버전은 2006년 2월에 발표된 5.1 버전이다. 루아는 사용상의 제약이 거의 없는 MIT 라이센스를 가지고 있어 누구나 개발에 자유롭게 활용할 수 있다(5.0 이전 버전은 BSD 라이센스의 변형 라이센스로 공개되었다).
잘 알려진 것처럼 루아가 게임 개발 분야에서 큰 인기를 끌고 있는 이유는 많다. 먼저, 게임의 특성상 복잡해 질 수밖에 없는 GUI와 인공지능을 쉽게 관리할 수 있는 루아의 특성 때문이다. 특히 다른 스크립트 언어들과 가장 잘 구분되는 요소인 접착성 또한 게임 개발자들을 매료시키는 루아의 장점이다. 때문에 루아는 C나 C++와 쉽게 연동이 되고 서로간의 데이터 공유도 아주 쉽다. 루아 API(C언어로 제작)에서 이미 C/C++와의 상호작용에 필요한 대부분의 함수를 만들어 놓았기 때문에 굳이 클래스화 같은 작업도 할 필요가 없다. 루아 API만 가지고도 훌륭히 C와 C++로 작성된 모듈을 불러 쓰거나 그 반대의 기능도 얼마든지 구현할 수 있다.
물론 루아만 다른 컴파일 언어와 결합해서 사용할 수 있는 것은 아니다. 뿐만 아니라, 아직 이런 기능을 제공하지 않는 스크립트 언어들 또한 앞 다투어 해당 프로젝트를 진행 중이기도 하다. 그럼에도 불구하고 루아가 돋보이는 이유는 그 근본이 다르다는 데 있다. 굳이 따지자면 정통성 같은 것이다. 루아는 기존 스크립트 언어에 결합성을 부여하는 흉내를 내는 것이 아니라, 설계 단계부터 접착 언어로 사용될 것을 염두에 두고 개발된 스크립트 언어이기 때문이다. 그렇다보니 다른 스크립트 언어들보다 훨씬 자연스럽게 다른 언어들과의 연동이 가능해지는 것이다.
범용 언어로써의 루아
그럼, 루아는 다른 언어들과 함께 사용해야만 제 기능을 발휘할 수 있는 불완전한 언어일까? 물론 아니다. 루아는 범용언어라고 해도 부족함이 없는 기능을 제공한다. 일반적으로 프로그래밍이 가능한 언어가 되기 위해 필요한 변수와 반복, 분기문을 모두 지원하고 있다. 루아는 지역, 전역 변수를 제공하고 if ifelse와 같은 분기문을 제공한다. while, repeat, for 세 가지 타입의 반복문도 제공한다. 뿐만 아니라, 파일의 입출력과 문자열(string), 수학에 관한 표준 라이브러리도 모두 제공한다.
물론, 이런 특징은 대부분의 다른 스크립트 언어들도 지원한다. 사실 파이썬(python)과 같은 스크립트 언어는 루아보다 훨씬 더 많은 표준 라이브러리를 제공한다. 그런 면에서 따진다면 루아는 독립 언어로써 다른 스크립트 언어들 보다 큰 장점을 가진 것은 아니다.
반면에 시각을 조금만 조정하면 판도는 완전히 달라진다. 앞서 말한 것처럼 몇몇 특징과 파일 입출력 등의 꼭 필요한 기능을 갖추고 있으니, 루아 또한 범용 언어로써 크게 부족한 점은 없다고 볼 수 있다. 더불어 C와 C++의 모듈까지 사용할 수 있으니 루아의 기본 기능에 새로운 라이브러리들이 추가될수록 기능도 늘어난다. 필요한 기능은 무엇이든 추가해서 만들 수 있는 것이다. 이렇게 보면 루아 또한 분명 독립 언어로써의 강점을 가지고 있다고 할 수 있다.
루아 스크립트의 특징
루아를 C나 C++와 함께 사용하려면 먼저 루아 스크립트의 간단한 사용법에 대해 알아두어야 한다. 여기에서는 먼저 루아 스크립트를 설치하는 방법과 루아 문법의 특징들에 대해 알아보자.
루아 홈페이지에 가면 바이너리와 소스코드의 두 가지 배포판이 있다. 바이너리 압축 파일은 bin2c5.1.exe, lua5.1.dll, lua5 .1.exe, luac5.1.exe 네 개인데, 이 중에서 lua5.1.exe는 루아 인터프리터를 바로 실행할 수 있는 실행파일이다. lua5.1.dll은 루아 인터프리터를 마이크로소프트 비주얼 스튜디오와 같은 윈도우즈 통합개발환경(IDE)에서 로딩하여 사용할 수 있다. 명령어 프롬프트에서 :\lua5.1.exe를 실행하면, 루아 스크립트를 바로 실행할 수 있는 인터프리터가 실행된다. 따로 저장된 스크립트 파일을 실행하려면 :\lua5.1.exe file_name를 입력하면 된다.
<화면 1> 루아 소스 배포판의 디렉토리 화면
<화면 2> 루아 프로젝트 화면
소스코드 배포판은 앞서 말한 바이너리 파일을 모두 빌드 할 수 있는 소스와 비주얼 스튜디오 6.0 이후 현재의 8.0 버전까지의 모든 프로젝트를 제공한다. 프로젝트 환경 파일(6.0에서는 .dsw, 7.0 이후에는 .sln파일)을 실행하면 다섯 개의 프로젝트가 표시된다. 이 중에서 우리가 사용할 프로젝트는 lua5.1_dll나 lua5.1_lib다. 이 두 프로젝트는 모두 비슷한 방법으로 이 글에서 사용하는 예제 애플리케이션에 포함될 수 있다.
참고로, lua5.1_dll은 동적 라이브러리, lua5.1_lib은 정적 라이브러리다. 두 라이브러리의 내부 메커니즘은 다르지만, 사용하는 방법은 동적 라이브러리의 명시적 연결(Explicit Linking)만 사용하지 않으면 비슷하다. 어쨌든 중요한 것은 꼭 소스코드 배포판을 다운받고, 원하는 형태의 라이브러리(정적 또는 동적)를 빌드해야 한다는 점이다. 물론 바이너리 배포판에 포함된 dll 파일을 명시적 연결을 통해 사용할 수 있지만, 디버그 할 때 릴리즈 모드로 빌드 된 파일은 값 추적이 되지 않는다. 때문에, 어쩌면 만날지도 모르는 루아의 숨은 버그에 대해서 대처하기 힘들 수도 있으니 소스코드를 다운로드 받은 뒤에 직접 빌드하는 것이 좋다.
타입 지정이 필요 없는 루아의 변수
루아는 기본적으로 타입이 없는 변수(typeless)를 지원한다. 예를 들어 다음과 같은 코드를 실행하면 입력된 변수의 각 값에 따라 자동으로 타입을 인식한다.
a = “MicrosoftWare Magazine”
b = 200
c = 300
print(a)
print(b+c)
<화면 3> 예제 코드를 실행한 화면
<화면 3>은 앞서 입력한 코드의 실행 화면이다. 변수 a는 문자열로 인식 하였고, b와 c는 수치데이터로 인식되어 연산이 되는 것을 확인할 수 있다.
함수와 스코프(scope)
변수는 전역변수(Global Variable)와 지역변수(Local Variable)로 나뉜다. 이들은 C 언어의 전역변수와 지역변수의 개념과 동일하다. 단, 지역변수는 함수 안에서만 선언할 수 있고 ‘local’이라는 키워드를 함께 사용해야 한다. 만약 ‘local’ 키워드와 함께 선언하지 않으면 그 변수는 전역변수가 된다.
함수의 시작은 ‘function’이라는 키워드로 시작된다. 그 이후에 함수의 이름과 매개변수(parameter) 리스트가 이어진다. 함수의 마지막에는 ‘end’라는 키워드를 통해 함수가 종료했음을 인터프리터에게 알려준다. 함수의 매개변수는 간단히 function function_name (param1, param2...)와 같이 매개변수의 이름만을 써준다. 앞서 말했듯이 루아는 타입이 없는 변수를 지원하기 때문에 매개변수 역시 타입 없이 매개변수의 식별자(iden tifier) 역할을 하는 이름만을 입력한다. 또한, 함수의 리턴 값 역시 타입이 없기 때문에 리턴 타입을 적어줄 필요도 없다. 참고로 매개변수 리스트에 들어간 매개변수는 함수의 지역변수와 같이 함수 내에서만 유효한 스코프(Scope)를 가진다.
<리스트 1> 전역변수를 사용하는 함수 예제
function printText()
a = "Microsoftware Magazine"
b = 200
c = 300
print(a)
print(b+c)
end
printText()
print(a)
print(b)
print(c)
<화면 4> 전역변수를 사용한 함수의 실행 화면
<리스트 2>는 함수의 지역변수와 전역변수의 차이를 보여주는 예제이다. 출력된 화면을 보면, 코드의 printText() 함수가 실행되면서 ‘Microsoftware Magazine’과 500이 출력된 것을 볼 수 있다. 그 이후의 코드가 차례로 실행되면서 print(a)와 print(b)는 nil을 출력하고 print(c)는 300을 올바르게 출력한 것을 볼 수 있다. 여기서 nil은 C/C++의 NULL 값과 비슷한 개념으로 print(parameter)에서 매개변수에 유효하지 않은 값(invalid value)이 들어갔을 때 nil을 출력하게 된다. 다시 코드를 보면 printText() 함수의 변수 a, b, c 중에 a, b는 ‘local’ 키워드로 시작하여 지역변수로 선언되었고, c는 전역변수로 선언된 것을 알 수 있다. 따라서 a와 b는 지역변수로 출력하는 곳에서 유효하지 않은 스코프를 가졌기 때문에 nil 이 출력되었고, c는 전역변수이기 때문에 300이 올바르게 출력된 것이다.
<리스트 2> 전역변수와 지역변수의 차이를 보여주는 예제
function printText()
local a = "Microsoftware Magazine"
local b = 200
c = 300
print(a)
print(b+c)
end
printText()
print(a)
print(b)
print(c)
<화면 5> 전역변수와 지역변수가 출력된 화면
분기문과 반복문
프로그래밍을 짧게 정의하자면 세상에 필요한 로직을 분기문과 반복문을 사용하여 표현하는 것이라고 할 수 있다. 물론, 객체지향언어의 클래스 개념이 들어가고 이와 함께 수반되는 상속, 인터페이스와 더불어 디자인패턴과 같은 것까지 결합되는 요즘 프로그래밍 환경에서는 너무나 간략한 정의라고 할 수 있다. 하지만, 이 중 어떤 프로그래밍 기법도 분기문과 반복문을 사용하지 않고는 프로그래밍을 할 수 없다는 것에는 이견이 없을 것이다. 루아의 반복문과 분기문은 C나 C++와 크게 다르지 않다.
루아에서는 ‘if’, ‘elseif’, ‘else’의 키워드와 함께 분기문을 사용할 수 있다. 각 키워드의 조합이 모두 종료되는 마지막에는 하나의 분기문을 끝낸다는 의미로 ‘end’ 키워드를 입력해야 한다. 또한 ‘if’, ‘elseif’에 대해서는 조건식이 끝난 뒤에 ‘then’이라는 키워드를 넣어줘야 한다. 반면에 ‘else’에는 ‘then’을 넣지 않는데, 이것이 루아에서 분기문을 사용할 때 주의할 점이다(<리스트 3> 참조).
<리스트 3> 분기문 테스트 코드
function ControlTest(param)
if (param == 100) then
print("parameter is 100")
elseif (param < 100) then
print("parameter is smaller than 100")
else
print("parameter is bigger than 100")
end
end
ControlTest(99)
ControlTest(100)
ControlTest(101)
<화면 6> 분기문 테스크 코드 실행 화면
<리스트 4> While 반복문 예제
index = 0
while index < 10 do
print (index)
index = index + 1
end
<화면 7> while 반복문 실행 화면
<리스트 5> repeat 반복문 예제
index = 0
repeat
print (index)
index = index + 1
until index < 10
<화면8> repeat 반복문 실행 화면
루아에서는 while, repeat, for 세 가지의 반복문을 사용할 수 있다. while과 for문은 C나 C++와 비슷하고, repeat는 do-while과 비슷하다. 한 가지 다른 점은 repeat의 조건식이 거짓일 경우가 아니라, 조건식이 참 일 때 반복이 멈춘다는 것이다. 이 점만 유의하면 루아에서 반복문을 사용하는 것에 크게 어려움을 겪지 않을 것이다.
<화면 8>을 보면 0~9의 수가 모두 출력되지 않고 0만 출력되는데, 이는 앞서 설명한 것처럼 repeat 문의 반복이 참일 때 멈추는 탓이다. 때문에 0을 출력한 뒤에 until문의 index < 10을 검사할 때 바로 조건을 만족하기 때문에 반복문이 멈추는 것이다.
<리스트 6> for 반복문 예제
for index = 0,3
do
print (index)
end
<화면 9> for 반복문 실행 화면1
<리스트 6>의 for문에서 첫 번째 index = 0은 for문내에서 사용되는 지역변수 index의 초기화이다. 최신의 C++ 컴파일러에서 지원하는 for(int I = 0...) 시 I를 자동으로 선언해주는 것과 같다. 그 다음에 나오는 3은 index <= 3 이라는 조건식을 의미한다. 따라서 index가 4가 될 때까지 계속 반복한다. C나 C++와 다른 또 한 가지는 변수의 증가량에 대한 조건이 없다는 것이다. 이 조건은 입력해도 되고 하지 않아도 된다. 조건을 지정하지 않으면 자동적으로 index는 매 반복 시 1씩 증가하게 된다. <리스트 7>은 for 반복문을 이용해 2씩 증가한 값을 출력하도록 하는 예제이다.
<리스트 7> for 반복문에 index를 지정한 예제
for index = 0, 4, 2
do
print (index)
end
<화면 10> for 반복문에 index의 증가량을 입력한 코드의 실행 화면
C/C++에서 루아 스크립트 사용하기
C와 C++에서 루아 스크립트를 사용하려면 루아 소스코드를 다운 받은 후 lib나 dll을 빌드해야 한다. 루아 다운로드 페이지(http://luabinaries.luaforge.net/download.html)에서 lua5_1_1_Sources.zip 파일을 다운로드 받은 뒤에, 비주얼 스튜디오에 알맞은 프로젝트를 찾아 lib나 dll로 빌드한다. 물론, 바이너리 버전에도 릴리즈 모드로 빌드된 lua5.1.dll 파일이 존재하지만, 이렇게 하면 디버그 모드로 빌드하여 값을 추적하는 디버깅을 할 수 없다.
일일이 열거할 수는 없지만 실제로 루아는 몇 가지 버그가 있다(필자도 몇몇 버그를 경험했다). 독자는 반드시 소스코드를 직접 빌드하여 루아의 버그를 추적하고 대처할 수 있도록 만드는 것이 좋다. 꼭 버그 때문이 아니더라도 라이브러리를 빌드하는 과정에서 비주얼 스튜디오의 옵션을 조절하는 등의 세부적인 제어를 프로그래머가 직접 할 수 있다는 점에서도 소스코드를 빌드하는 편이 좋다.
<리스트 8> 루아 스크립트의 데이터와 함수를 사용하는 C 언어 코드
#ifdef __cplusplus
extern "C" {
#endif
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#ifdef __cplusplus
}
#endif
#include <iostream>
int main() {
// 새로운 lua_State 포인터 생성
lua_State *L = luaL_newstate();
// 루아 표준 함수 로딩
luaL_openlibs(L);
// 위에 생성한 lua_State에서 루아 스크립트 실행
luaL_dofile(L, "test.lua");
std::string name;
int capacity;
std::string price;
// 전역변수를 lua_State의 스택에 push
lua_getglobal(L, "name");
lua_getglobal(L, "capacity");
lua_getglobal(L, "price");
// lua_State의 스택에서 값 가져오기 (pop)
name = lua_tostring(L, 1);
capacity = lua_tointeger(L, 2);
price = lua_tostring(L, 3);
// 출력하기
std::cout << "name : " << name.c_str() << std::endl;
std::cout << "capacity : " << capacity << std::endl;
std::cout << "price : " << price.c_str() << std::endl;
// lua_State의 스택에 함수 push
lua_getglobal(L, "TestFunc");
// lua_State의 스택에 함수의 매개변수 push
lua_pushstring(L, "test function Parameter 1");
lua_pushnumber(L, 12345);
// 루아 스크립트의 함수 실행
lua_pcall(L, 2, 2, 0);
// 루아 스택에서 함수의 리턴 값 가져오기 (pop)
int returnInt = lua_tonumber(L, -1);
std::string returnString = lua_tostring(L, -2);
// 출력하기
std::cout << returnInt << std::endl;
std::cout << returnString.c_str() << std::endl;
// lua_State가 동적 할당한 메모리 해제
lua_close(L);
return 0;
}
<리스트 9> <리스트 8>을 실행할 때 입력되는 루아 스크립트 코드
name = "RAM"
capacity = 512
price = "$50"
function TestFunc(param1, param2)
print(param1)
print(param2)
return "returned value", 22
end
<화면 11> <리스트 9>의 루아 스크립트를 <리스트 8>로 실행한 화면
루아 스크립트의 데이터를 C와 C++에서 사용하기
lua_State는 루아 스크립트를 읽어서 실행하는 환경이라고 생각하면 된다. lua_State는 내부적으로 독립된 자료구조(스택)를 가지고 있다. 물론, 스크립트 언어를 실행하려면 스크립트 언어에 포함된 데이터에 대한 전반적인 사항들(symbol table, function table)의 정보를 가지고 있어야 한다. 이러한 데이터는 프로그램이 실행되는 동안 일관되고 안전하게 유지 되어야 한다. 바로 이런 역할을 하는 것이 lua_State이다. 따라서 C언에서 전역변수, C++의 멤버변수나 static 변수와 같이 변수의 값을 프로그램 종료까지 계속 유지할 수 있는 방법이 마련된 변수로 선언되어야 한다(<리스트 8>은 main 함수 하나로 이루어져 있기 때문에 편의상 main 함수의 지역변수로 선언하였다). 마지막 줄의 lua_close() 함수는 lua_State의 포인터가 동적 할당한 모든 메모리를 해제 시켜주는 함수로 프로그램 종료 시 반드시 입력해줘야 하는 함수이다.
루아는 작은 사이즈를 유지하기 위해 luaL_newState()로 리턴 되는 새로운 lua_State에는 어떤 표준 라이브러리 함수도 넣어두지 않는다. <리스트 8>에서는 흔히 보아온 print() 함수조차도 luaL_openlib() 함수를 호출하기 전까지는 사용할 수 없다. 따라서 표준 함수를 사용하려면 luaL_openlib() 함수를 반드시 호출해야 한다. 이제야 비로소 C와 C++에서 루아 스크립트의 데이터를 사용할 수 있는 준비가 끝났다.
루아 스크립트의 함수 사용하기
이제 루아 스크립트에 있는 함수를 불러서 사용하는 방법에 대해 알아보자. 루아 스크립트로 만들어진 함수를 얻어내는 것 역시 lua_getglobal() 함수를 사용한다. 사용 방법은 루아 스크립트의 전역변수를 사용하는 방법과 동일하다. 함수에 대한 정보를 얻어왔으니, 함수 실행에 필요한 매개변수를 넘겨줘야 한다. 함수에 매개변수를 넘겨주는 것 역시 lua_todatatype()함수를 이용한다. 함수의 리턴 값 역시 lua_todatatype()함수를 이용한다.
루아의 함수는 동시에 여러 개의 값을 리턴할 수 있다. 방법은 return value1, value2... 와 같이 입력해주면 된다. 각 리턴 값은 “,”로 구분된다.
루아 스크립트에서 C/C++ 모듈 사용하기
<리스트 10> 루아 스크립트에서 사용할 수 있는 dll 코드
#include "stdafx.h"
#include "testDll.h"
EXPORT void Init(lua_State* _luaState)
{
// 매개변수로 넘어온 lua_State에 함수 등록
lua_register(_luaState, "TestSum", TestSum);
lua_register(_luaState, "TestEcho", TestEcho);
lua_register(_luaState, "TestMessageBox", TestMessageBox);
}
int TestSum(lua_State* L)
{
// 스택에 있는 매개변수 가져오기(pop)
double a = lua_tonumber(L, 1);
double b = lua_tonumber(L, -2);
// 함수의 결과 값(리턴 값)을 스택에 push
lua_pushnumber(L, a + b);
return 1;
}
int TestEcho(lua_State* L)
{
// 스택에 있는 매개변수 가져오기(pop)
std::string temp = lua_tostring(L, 1);
std::cout << temp.c_str() << std::endl;
return 1;
}
int TestMessageBox(lua_State* L) {
// 스택에 있는 매개변수 가져오기(pop)
std::string caption = lua_tostring(L, 1);
std::string message = lua_tostring(L, 2);
// 윈도우 API의 MessageBox()함수를 이용하여 메시지 박스 띄우기
MessageBox(NULL, message.c_str(), caption.c_str(), MB_OK);
return 1;
}
<리스트 10>은 dll을 만드는 평범한 코드이다. dllMain 함수가 있는 dllMain.cpp와 함수의 프로토타입이 있는 헤더파일은 이달의 디스켓을 참조한다. EXPORT는 필자가 #define을 이용하여 dll의 명시적 연결(explicit linking)을 하기 위해 만들어 놓은 것이다. 헤더파일을 보면 이해가 될 테니 신경 쓰지 않아도 된다.
<리스트 10>을 보면 가장 먼저 나오는 init() 함수를 제외하고는 모두 int function_name (lua_State*)의 프로토타입을 가지고 있다. 이것은 루아 환경에서 콜백 함수를 사용하기 위해 지정해준 프로토타입이다. 콜백 함수는 함수포인터를 시스템으로 넘겨, 시스템이 일정 조건이 충족될 때 함수를 실행하게 하는 메커니즘이다.
<리스트 10>에서 init() 함수를 보면 lua_register() 함수가 세 번 불리는 것을 확인할 수 있다. lua_register() 함수를 보면 프로토타입은 void lua_register(lua_State*, const char*, (int)(*)(lua_State*))이다. lua_register() 함수가 하는 일은 세 번째 매개변수로 들어간 함수포인터를 두 번째 매개변수로 들어간 이름으로 루아 환경에서 사용도록 하는 것이다. 따라서 명시적 연결이든 암시적 연결이든 dll을 로딩한 후에 init() 함수를 실행하면 그 이후로는 루아 환경에서 dll에 있는 모든 함수를 사용할 수 있게 된다. 물론, init() 함수에서 dll에 포함된 모든 함수를 lua_register()를 통해 등록을 해줘야 한다.
그 이후에 나오는 세 개의 함수는 모두 int function_name (lua_State*)라는 프로토타입을 가지고 있다. 정확히 lua_ register()의 세 번째 매개변수의 함수포인터의 타입과 일치한다.
TestSum() 함수에는 lua_tonumber()와 lua_pushnu mber() 함수가 사용되었다. lua_tonumber()는 앞의 C/C++에서 루아스크립트 데이터를 사용하는 예제에서도 쓰인 스택에서 값을 가져오는(pop) 함수이다. 이렇듯 루아는 C/C++와의 스택을 통해서 모든 통신을 한다는 점을 주지하자.
다시 본론으로 돌아와 루아 스크립트에서 TestSum(1,2)와 같은 코드를 입력하였을 때, 1과 2는 모두 스택에 저장(push)된다. C/C++의 모듈(dll)에서는 이 값을 가져오기 위해 lua_ todatatype() 함수를 이용하는 것이다. lua_tonumber()의 두 번째 매개 변수는 스택의 인덱스 값을 의미한다. 함수가 매개변수를 받았으니, 결과 값도 리턴 해줘야 한다. TestSum()을 보면 마지막 줄에 lua_pushnumber()라는 함수가 있다. 바로 이 함수가 스택에 값을 넣어주는 함수이다. 따라서 루아 스크립트에서 a = TestSum(1, 2)와 같은 문장이 실행될 때, a에는 TestSum() 함수의 마지막 줄의 lua_pushnumber()가 실행되어 그 값이 a에 대입되는 것이다. TestSum() 함수를 실행하면 3이 리턴 되는 것을 확인할 수 있다.
<리스트 11> 루아 스크립트에서 dll 모듈을 불러 사용하게 하는 메인함수 코드
#ifdef __cplusplus
extern "C" {
#endif
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#ifdef __cplusplus
}
#endif
#include <windows.h>
int main()
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);
// dll 명시적 연결 부분
// 디버그 모드와 릴리즈 모드에서 각각에 맞는 dll을 로딩하기 위해 pre
// processor 사용
#ifdef _DEBUG
HINSTANCE hLib = LoadLibrary((LPCSTR)(TEXT("testDlld.dll")));
#else
HINSTANCE hLib = LoadLibrary((LPCSTR)(TEXT("testDll.dll")));
#endif
// 루아 환경에서 dll의 함수를 사용할 수 있게 하기 위해
// dll의 init() 함수를 함수
void (*pfInit) (lua_State*);
pfInit = (void (*) (lua_State*)) GetProcAddress(hLib, "Init");
(*pfInit)(L);
// 루아 스크립트 실행
luaL_dofile(L, "test.lua");
// 동적 할당된 메모리 해제
lua_close(L);
return 0;
}
<리스트 12> 위의 메인 함수에서 사용되는 루아 스크립트
-- dll 의 TestSum() 함수 호출
a = TestSum(1,2)
print(a)
-- dll 의 TestEcho() 함수 호출
TestEcho("Test Echo Test")
-- dll 의 TestMessageBox() 함수 호출
TestMessageBox("hello", "This is a messagebox from Lua")
<화면 12> 위의 메인 함수를 실행한 화면
<리스트 10>의 코드는 메인함수 코드이다. lua_State라는 타입의 포인터 변수를 초기화하고, luaL_openlibs()를 실행하는 것은 앞에서 본 C/C++에서 루아 스크립트를 사용하는 것과 똑같다. luaL_dofile() 함수를 실행함으로써 루아 스크립트를 실행하면서 함께 dll 모듈을 불러 사용하면서 프로그램은 종료하게 된다.
지면 관계상 짧게 설명하기 위하여 루아의 간단한 문법과 핵심 기능인 C/C++ 언어와의 바인딩에 관한 짧은 예제 두 개로 설명을 하였으나 충분치 않을 것으로 생각한다. 하지만, 두 예제를 가지고 응용을 한다면 전체 애플리케이션에서 설정 부분을 외부파일로 뺄 때나 dll로 만들어진 모듈과 다른 C/C++ 모듈을 혼합해서 사용할 때 루아가 효과적으로 사용된다는 점은 알았을 거라 생각한다. 또한 두 예제를 다른 참고 자료와 응용하면 루아를 좀 더 효과적으로 사용할 수 있을 것이다.
루아 참고 자료
아직 국내에 루아와 관련된 자료들이 많지 않기 때문에 루아를 사용을 어려워하는 독자들이 많은 듯하다. 끝으로 루아와 관련된 자료들을 제공하는 사이트와 커뮤니티 등을 소개하니 참고하길 바란다.
● 루아 공식홈페이지 - http://www.lua.org
루아의 공식홈페이지로써 루아의 배포판과 참고 매뉴얼 등을 다운받을 수 있다. 루아와 관련된 최신 정보를 확인할 수 있는 사이트다.
● 루아 레드위키 - http://www.redwiki.net/wiki/wiki.php/Lua
루아의 소개부터 루아와 다른 언어와의 바인딩 기법 등 여러 가지 정보를 한글로 제공하는 유일한 사이트이다.
● 루아 사용자 모임 - http://lua-users.org
루아 사용자 모임에서는 루아 튜터리얼과 샘플코드, 자잘한 버그나 하위 호환성과 관련된 패치 등을 다운받을 수 있다.
● C#과 루아
- http://www.gamedev.net/reference/articles/article2275.asp
- http://einfall.blogspot.com/2006/05/scripting-with-lua-in-c.html
C#에서 dl 을 불러 쓸 수 있다는 점을 착안하여, lua의 dll을 C# 에서 랩업(wrap up) 클래스화하여 사용하는 방법을 소개하고 있다.
● 루아를 이용한 민첩하고 효과적인 게임 개발
Paul Schuytema, Mark Manyen 저, 류광 역, 사이텍 미디어, 2005
루아를 게임 개발에 이용하는 방법을 예제를 통해 자세하게 설명하고 있는 책이다.
참고 자료
1. 루아 공식 홈페이지, http://www.lua.org
2. Programming Windows 5th Edition, Charles Petzold저, 김선우 역, 한빛미디어, 2004
3. Windows API 정복(개정판), 김상형, 한빛미디어, 2006
4. 루아를 이용한 민첩하고 효과적인 게임 개발, Paul Schuytema, Mark Manyen 저, 류광 역, 사이텍 미디어, 2005
5. Programming in Lua, Roberto Lerusalimschy, Lua.org, 2006
6. Game Scripting Mastery, Alex Varanese, Andre LaMothe, Primer Press, 2003
7. The Joy Of C, Lawrence H. Miller, Alesander E. Quilici 저, 민용식 외 7인 역, 교보문고, 1998
'프로그래밍 언어 > Lua' 카테고리의 다른 글
5. 루아 제어문편 (0) | 2009.09.02 |
---|---|
4. 루아 연산자편 (0) | 2009.09.02 |
3. 루아 변수편 (0) | 2009.08.31 |
2. 루아로 찍어보자 "Hello, World!" (2) | 2009.08.31 |
1. 루아 시작하기 (0) | 2009.08.29 |