Lua와 C++ 간 상호작용 메커니즘 심층 분석

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 등)

데이터 저장 방식에 따라 두 가지 카테고리로 나뉩니다:

  1. 직접 저장: number, boolean, nil, light userdata는 스택 요소 내부에 직접 저장
  2. 참조 저장: 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* 함수군을 사용합니다.

태그: Lua C++ Stack Embedding api

6월 1일 18:56에 게시됨