Lua 스택 구조 이해
Lua와 C++의 통신은 가상 스택을 기반으로 동작합니다. 이 스택은 후입선출(LIFO) 방식으로 동작하며, Lua에서는 다음과 같은 특성을 가집니다:
- 양수 인덱스: 1번이 항상 스택 바닥(bottom)
- 음수 인덱스: -1번이 항상 스택 꼭대기(top)
Lua 스택은 TValue 구조체 배열로 구성되며, 모든 데이터 타입이 {값, 타입} 형식으로 저장됩니다:
TValue stack[max_stack_len]
TValue는 다음과 같은 구성 요소를 포함합니다:
- p: 포인터(light userdata)
- n: 숫자형 값(int, float)
- b: 불리언 값
- gc: 가비지 컬렉션 대상 객체(string, table, closure 등)
데이터 저장 방식에 따라 두 가지 카테고리로 나뉩니다:
- 직접 저장: number, boolean, nil, light userdata는 스택 요소 내부에 직접 저장
- 참조 저장: string, table, closure, userdata, thread는 포인터만 저장되고 실제 데이터는 GC 관리 대상
스택 조작 API
Lua는 스택을 다루기 위한 다양한 함수를 제공합니다:
int lua_gettop(lua_State *L); // 스택 상단 인덱스 반환
void lua_settop(lua_State *L, int idx); // 스택 상단 설정
void lua_pushvalue(lua_State *L, int idx);// 지정 인덱스 값 복사 후 푸시
void lua_remove(lua_State *L, int idx); // 지정 인덱스 값 제거
void lua_insert(lua_State *L, int idx); // 스택 상단 요소를 지정 위치 삽입
void lua_replace(lua_State *L, int idx); // 스택 상단 요소로 지정 위치 교체
특히 lua_settop(0)은 스택을 완전히 비우는 데 사용됩니다.
C++에서 Lua 호출하기
Lua 파일의 변수, 테이블, 함수 등을 C++에서 읽어오는 예제입니다:
예시 Lua 파일 (config.lua):
message = "Hello from Lua"
person = {name = "John", age = 30}
function multiply(x, y)
return x * y
end
C++ 구현 코드:
#include <iostream>
#include <string>
extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}
int main() {
lua_State* state = luaL_newstate();
// Lua 파일 로드 및 실행
if (luaL_loadfile(state, "config.lua") || lua_pcall(state, 0, 0, 0)) {
std::cerr << "Error: " << lua_tostring(state, -1) << std::endl;
lua_close(state);
return 1;
}
// 전역 변수 읽기
lua_getglobal(state, "message");
std::cout << "Message: " << lua_tostring(state, -1) << std::endl;
lua_pop(state, 1);
// 테이블 값 읽기
lua_getglobal(state, "person");
lua_getfield(state, -1, "name");
std::cout << "Name: " << lua_tostring(state, -1) << std::endl;
lua_pop(state, 2);
// 함수 호출
lua_getglobal(state, "multiply");
lua_pushnumber(state, 5);
lua_pushnumber(state, 3);
if (lua_pcall(state, 2, 1, 0) == 0) {
std::cout << "Result: " << lua_tonumber(state, -1) << std::endl;
}
lua_pop(state, 1);
lua_close(state);
return 0;
}
테이블 값 수정:
// 테이블 필드 수정
lua_getglobal(state, "person");
lua_pushstring(state, "Jane");
lua_setfield(state, -2, "name");
lua_pop(state, 1);
새 테이블 생성:
// 새로운 테이블 생성
lua_newtable(state);
lua_pushstring(state, "New Value");
lua_setfield(state, -2, "key");
lua_setglobal(state, "new_table");
Lua에서 C++ 함수 호출하기
정적 라이브러리 방식
Lua 모듈로 등록할 C++ 함수 예제:
#include <lua.hpp>
// Lua 함수 시그니처 준수
static int calculate_stats(lua_State* L) {
int count = lua_gettop(L);
double total = 0.0;
for (int i = 1; i <= count; i++) {
total += lua_tonumber(L, i);
}
lua_pushnumber(L, total / count); // 평균
lua_pushnumber(L, total); // 합계
return 2; // 반환 값 개수
}
static int display_message(lua_State* L) {
const char* msg = lua_tostring(L, 1);
std::cout << "Lua says: " << msg << std::endl;
return 0;
}
int main() {
lua_State* L = luaL_newstate();
luaL_openlibs(L);
// 함수 등록
lua_pushcfunction(L, calculate_stats);
lua_setglobal(L, "stats");
lua_pushcfunction(L, display_message);
lua_setglobal(L, "show");
// Lua 스크립트 실행
luaL_dostring(L, R"(
avg, sum = stats(10, 20, 30)
print("Average:", avg, "Sum:", sum)
show("Hello C++!")
)");
lua_close(L);
return 0;
}
동적 라이브러리(DLL) 방식
모듈 헤더 파일 (math_module.h):
#pragma once
extern "C" {
#include "lua.h"
#include "lauxlib.h"
}
#ifdef MATH_MODULE_EXPORTS
#define MODULE_API __declspec(dllexport)
#else
#define MODULE_API __declspec(dllimport)
#endif
extern "C" MODULE_API int luaopen_math_module(lua_State* L);
구현 파일 (math_module.cpp):
#include "math_module.h"
#include <cmath>
static int power_operation(lua_State* L) {
double base = lua_tonumber(L, 1);
double exp = lua_tonumber(L, 2);
lua_pushnumber(L, std::pow(base, exp));
return 1;
}
static int sqrt_operation(lua_State* L) {
double value = lua_tonumber(L, 1);
lua_pushnumber(L, std::sqrt(value));
return 1;
}
static const luaL_Reg math_functions[] = {
{"power", power_operation},
{"square_root", sqrt_operation},
{nullptr, nullptr}
};
extern "C" MODULE_API int luaopen_math_module(lua_State* L) {
luaL_newlib(L, math_functions);
return 1;
}
Lua에서 사용:
local math_lib = require "math_module"
print(math_lib.power(2, 3)) -- 8
print(math_lib.square_root(16)) -- 4
데이터 타입 매핑
| C++ 타입 | Lua TValue 구조 |
|---|---|
| void* | {value=포인터, tt=t_lightuserdata} |
| int/double | {value=숫자, tt=t_number} |
| char[] | {value=gco, tt=t_string} | bool | {value=0/1, tt=t_boolean} |
| nullptr | {value=0, tt=t_nil} |
Lua에서 C++로의 데이터 변환은 lua_to* 함수군을 통해 이루어지며, C++에서 Lua로의 변환은 lua_push* 함수군을 사용합니다.