← Volver al listado de tecnologías

Capítulo 20: La C API - Extendiendo Lua

Por: Artiko
luac-apiffinative-modules

Capítulo 20: La C API - Extendiendo Lua

La verdadera potencia de Lua reside en su capacidad para integrarse con C/C++. La C API permite crear módulos nativos, exponer funcionalidades del sistema, y lograr rendimiento máximo donde Lua puro no es suficiente.

1. Fundamentos del Stack de Lua

El stack es la estructura central de comunicación entre C y Lua. Todos los valores se transfieren a través de él.

El Modelo de Stack

// mylib.c - Ejemplo básico del stack
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

// Función C que suma dos números
static int l_add(lua_State *L) {
    // Obtener argumentos del stack
    double a = luaL_checknumber(L, 1);  // Primer argumento
    double b = luaL_checknumber(L, 2);  // Segundo argumento

    // Calcular resultado
    double result = a + b;

    // Pushear resultado al stack
    lua_pushnumber(L, result);

    // Retornar número de valores devueltos
    return 1;
}

// Función con múltiples retornos
static int l_divmod(lua_State *L) {
    int a = luaL_checkinteger(L, 1);
    int b = luaL_checkinteger(L, 2);

    // Validar división por cero
    if (b == 0) {
        return luaL_error(L, "division by zero");
    }

    // Pushear dos resultados
    lua_pushinteger(L, a / b);  // Cociente
    lua_pushinteger(L, a % b);  // Resto

    return 2;  // Retornamos 2 valores
}

// Tabla de funciones
static const struct luaL_Reg mathlib[] = {
    {"add", l_add},
    {"divmod", l_divmod},
    {NULL, NULL}
};

// Función de inicialización
int luaopen_mymath(lua_State *L) {
    luaL_newlib(L, mathlib);
    return 1;
}

Manipulación del Stack

// stack_ops.c - Operaciones avanzadas del stack
#include <lua.h>
#include <lauxlib.h>

// Inspeccionar el stack
static void print_stack(lua_State *L) {
    int top = lua_gettop(L);
    printf("Stack size: %d\n", top);

    for (int i = 1; i <= top; i++) {
        int t = lua_type(L, i);
        switch (t) {
            case LUA_TSTRING:
                printf("%d: '%s'\n", i, lua_tostring(L, i));
                break;
            case LUA_TBOOLEAN:
                printf("%d: %s\n", i, lua_toboolean(L, i) ? "true" : "false");
                break;
            case LUA_TNUMBER:
                printf("%d: %g\n", i, lua_tonumber(L, i));
                break;
            default:
                printf("%d: %s\n", i, lua_typename(L, t));
                break;
        }
    }
}

// Función que trabaja con múltiples tipos
static int l_inspect(lua_State *L) {
    int n = lua_gettop(L);  // Número de argumentos

    printf("Received %d arguments:\n", n);
    print_stack(L);

    // Retornar el número de argumentos
    lua_pushinteger(L, n);
    return 1;
}

// Manipular índices absolutos y relativos
static int l_rotate_values(lua_State *L) {
    int n = lua_gettop(L);

    if (n < 2) {
        return luaL_error(L, "need at least 2 values");
    }

    // Rotar valores: (a,b,c) -> (c,a,b)
    lua_rotate(L, 1, -1);  // Rotar n-1 posiciones a la derecha

    return n;
}

2. Tipos de Datos y Conversiones

Trabajando con Tablas

// tables.c - Manipulación de tablas desde C
#include <lua.h>
#include <lauxlib.h>

// Crear una tabla con valores
static int l_create_config(lua_State *L) {
    lua_newtable(L);

    // config.name = "MyApp"
    lua_pushstring(L, "MyApp");
    lua_setfield(L, -2, "name");

    // config.version = "1.0.0"
    lua_pushstring(L, "1.0.0");
    lua_setfield(L, -2, "version");

    // config.debug = true
    lua_pushboolean(L, 1);
    lua_setfield(L, -2, "debug");

    // config.port = 8080
    lua_pushinteger(L, 8080);
    lua_setfield(L, -2, "port");

    return 1;
}

// Leer valores de una tabla
static int l_get_table_info(lua_State *L) {
    luaL_checktype(L, 1, LUA_TTABLE);

    // Obtener config.name
    lua_getfield(L, 1, "name");
    const char *name = lua_tostring(L, -1);
    lua_pop(L, 1);

    // Obtener config.port
    lua_getfield(L, 1, "port");
    int port = lua_tointeger(L, -1);
    lua_pop(L, 1);

    // Crear resultado
    lua_pushfstring(L, "%s running on port %d", name, port);
    return 1;
}

// Iterar sobre tabla
static int l_sum_values(lua_State *L) {
    luaL_checktype(L, 1, LUA_TTABLE);

    double sum = 0;

    lua_pushnil(L);  // Primera key
    while (lua_next(L, 1) != 0) {
        // key en -2, value en -1
        if (lua_isnumber(L, -1)) {
            sum += lua_tonumber(L, -1);
        }
        lua_pop(L, 1);  // Remover value, mantener key
    }

    lua_pushnumber(L, sum);
    return 1;
}

// Crear array
static int l_create_array(lua_State *L) {
    int n = luaL_checkinteger(L, 1);

    lua_createtable(L, n, 0);  // Pre-allocar espacio

    for (int i = 1; i <= n; i++) {
        lua_pushinteger(L, i * i);  // Valor: i²
        lua_rawseti(L, -2, i);      // array[i] = i²
    }

    return 1;
}

Trabajando con Userdata

// userdata.c - Objetos C en Lua
#include <lua.h>
#include <lauxlib.h>
#include <stdlib.h>
#include <string.h>

// Estructura de nuestro objeto
typedef struct {
    char *data;
    size_t size;
    size_t capacity;
} Buffer;

#define BUFFER_METATABLE "MyLib.Buffer"

// Constructor
static int l_buffer_new(lua_State *L) {
    size_t capacity = luaL_optinteger(L, 1, 256);

    // Crear userdata
    Buffer *buf = (Buffer *)lua_newuserdata(L, sizeof(Buffer));

    // Inicializar
    buf->data = (char *)malloc(capacity);
    buf->size = 0;
    buf->capacity = capacity;

    // Asignar metatable
    luaL_getmetatable(L, BUFFER_METATABLE);
    lua_setmetatable(L, -2);

    return 1;
}

// Método: append
static int l_buffer_append(lua_State *L) {
    Buffer *buf = (Buffer *)luaL_checkudata(L, 1, BUFFER_METATABLE);
    size_t len;
    const char *str = luaL_checklstring(L, 2, &len);

    // Verificar espacio
    if (buf->size + len > buf->capacity) {
        // Expandir buffer
        buf->capacity = (buf->size + len) * 2;
        buf->data = (char *)realloc(buf->data, buf->capacity);
    }

    // Copiar datos
    memcpy(buf->data + buf->size, str, len);
    buf->size += len;

    // Retornar self para chaining
    lua_pushvalue(L, 1);
    return 1;
}

// Método: get
static int l_buffer_get(lua_State *L) {
    Buffer *buf = (Buffer *)luaL_checkudata(L, 1, BUFFER_METATABLE);
    lua_pushlstring(L, buf->data, buf->size);
    return 1;
}

// Método: size
static int l_buffer_size(lua_State *L) {
    Buffer *buf = (Buffer *)luaL_checkudata(L, 1, BUFFER_METATABLE);
    lua_pushinteger(L, buf->size);
    return 1;
}

// Método: clear
static int l_buffer_clear(lua_State *L) {
    Buffer *buf = (Buffer *)luaL_checkudata(L, 1, BUFFER_METATABLE);
    buf->size = 0;
    lua_pushvalue(L, 1);
    return 1;
}

// Metamétodo: __gc (destructor)
static int l_buffer_gc(lua_State *L) {
    Buffer *buf = (Buffer *)luaL_checkudata(L, 1, BUFFER_METATABLE);
    if (buf->data) {
        free(buf->data);
        buf->data = NULL;
    }
    return 0;
}

// Metamétodo: __tostring
static int l_buffer_tostring(lua_State *L) {
    Buffer *buf = (Buffer *)luaL_checkudata(L, 1, BUFFER_METATABLE);
    lua_pushfstring(L, "Buffer(%d/%d)", buf->size, buf->capacity);
    return 1;
}

// Metamétodo: __len
static int l_buffer_len(lua_State *L) {
    Buffer *buf = (Buffer *)luaL_checkudata(L, 1, BUFFER_METATABLE);
    lua_pushinteger(L, buf->size);
    return 1;
}

// Tabla de métodos
static const struct luaL_Reg buffer_methods[] = {
    {"append", l_buffer_append},
    {"get", l_buffer_get},
    {"size", l_buffer_size},
    {"clear", l_buffer_clear},
    {NULL, NULL}
};

// Tabla de metamétodos
static const struct luaL_Reg buffer_meta[] = {
    {"__gc", l_buffer_gc},
    {"__tostring", l_buffer_tostring},
    {"__len", l_buffer_len},
    {NULL, NULL}
};

// Inicialización
int luaopen_buffer(lua_State *L) {
    // Crear metatable
    luaL_newmetatable(L, BUFFER_METATABLE);

    // metatable.__index = metatable (para métodos)
    lua_pushvalue(L, -1);
    lua_setfield(L, -2, "__index");

    // Registrar metamétodos
    luaL_setfuncs(L, buffer_meta, 0);

    // Registrar métodos
    luaL_setfuncs(L, buffer_methods, 0);

    // Crear tabla del módulo
    lua_newtable(L);
    lua_pushcfunction(L, l_buffer_new);
    lua_setfield(L, -2, "new");

    return 1;
}

3. Referencias y el Registry

Sistema de Referencias

// refs.c - Sistema de referencias
#include <lua.h>
#include <lauxlib.h>

// Almacenar callback en registry
static int callback_ref = LUA_NOREF;

static int l_set_callback(lua_State *L) {
    luaL_checktype(L, 1, LUA_TFUNCTION);

    // Liberar referencia anterior
    if (callback_ref != LUA_NOREF) {
        luaL_unref(L, LUA_REGISTRYINDEX, callback_ref);
    }

    // Guardar nueva referencia
    lua_pushvalue(L, 1);
    callback_ref = luaL_ref(L, LUA_REGISTRYINDEX);

    return 0;
}

static int l_trigger_callback(lua_State *L) {
    if (callback_ref == LUA_NOREF) {
        return luaL_error(L, "no callback set");
    }

    // Recuperar función del registry
    lua_rawgeti(L, LUA_REGISTRYINDEX, callback_ref);

    // Preparar argumentos
    lua_pushstring(L, "event");
    lua_pushinteger(L, 42);

    // Llamar: callback("event", 42)
    if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
        return lua_error(L);
    }

    return 1;
}

// Múltiples callbacks con tabla
typedef struct {
    lua_State *L;
    int callbacks_ref;  // Referencia a tabla de callbacks
} EventSystem;

static int l_event_new(lua_State *L) {
    EventSystem *es = (EventSystem *)lua_newuserdata(L, sizeof(EventSystem));
    es->L = L;

    // Crear tabla para callbacks
    lua_newtable(L);
    es->callbacks_ref = luaL_ref(L, LUA_REGISTRYINDEX);

    // Asignar metatable (omitido por brevedad)

    return 1;
}

static int l_event_on(lua_State *L) {
    EventSystem *es = (EventSystem *)luaL_checkudata(L, 1, "EventSystem");
    const char *event = luaL_checkstring(L, 2);
    luaL_checktype(L, 3, LUA_TFUNCTION);

    // Obtener tabla de callbacks
    lua_rawgeti(L, LUA_REGISTRYINDEX, es->callbacks_ref);

    // callbacks[event] = function
    lua_pushvalue(L, 3);
    lua_setfield(L, -2, event);

    lua_pop(L, 1);
    return 0;
}

static int l_event_emit(lua_State *L) {
    EventSystem *es = (EventSystem *)luaL_checkudata(L, 1, "EventSystem");
    const char *event = luaL_checkstring(L, 2);

    // Obtener tabla de callbacks
    lua_rawgeti(L, LUA_REGISTRYINDEX, es->callbacks_ref);

    // Obtener callback
    lua_getfield(L, -1, event);

    if (!lua_isfunction(L, -1)) {
        lua_pop(L, 2);
        return 0;
    }

    // Copiar argumentos adicionales
    int nargs = lua_gettop(L) - 3;
    for (int i = 0; i < nargs; i++) {
        lua_pushvalue(L, 3 + i);
    }

    // Llamar callback
    if (lua_pcall(L, nargs, 0, 0) != LUA_OK) {
        return lua_error(L);
    }

    lua_pop(L, 1);  // Pop tabla de callbacks
    return 0;
}

4. Manejo de Errores

Errores y Protección

// errors.c - Manejo robusto de errores
#include <lua.h>
#include <lauxlib.h>
#include <setjmp.h>

// Errores simples
static int l_validate_age(lua_State *L) {
    int age = luaL_checkinteger(L, 1);

    if (age < 0) {
        return luaL_error(L, "age cannot be negative");
    }

    if (age > 150) {
        return luaL_error(L, "age %d is unrealistic", age);
    }

    lua_pushboolean(L, 1);
    return 1;
}

// Errores con argumentos
static int l_divide(lua_State *L) {
    double a = luaL_checknumber(L, 1);
    double b = luaL_checknumber(L, 2);

    luaL_argcheck(L, b != 0, 2, "division by zero");

    lua_pushnumber(L, a / b);
    return 1;
}

// Llamadas protegidas desde C
static int risky_function(lua_State *L) {
    // Simular operación que puede fallar
    if (rand() % 2 == 0) {
        return luaL_error(L, "random failure");
    }
    lua_pushstring(L, "success");
    return 1;
}

static int l_try_risky(lua_State *L) {
    lua_pushcfunction(L, risky_function);

    int result = lua_pcall(L, 0, 1, 0);

    if (result != LUA_OK) {
        // Error: mensaje está en el stack
        const char *error = lua_tostring(L, -1);
        lua_pop(L, 1);

        // Retornar nil, error
        lua_pushnil(L);
        lua_pushstring(L, error);
        return 2;
    }

    // Éxito: resultado en el stack
    return 1;
}

// Error handler personalizado
static int error_handler(lua_State *L) {
    const char *msg = lua_tostring(L, -1);

    // Agregar traceback
    luaL_traceback(L, L, msg, 1);

    return 1;
}

static int l_safe_call(lua_State *L) {
    luaL_checktype(L, 1, LUA_TFUNCTION);

    // Push error handler
    lua_pushcfunction(L, error_handler);
    lua_insert(L, 1);

    // Mover argumentos
    int nargs = lua_gettop(L) - 2;

    // pcall con error handler
    int result = lua_pcall(L, nargs, LUA_MULTRET, 1);

    // Remover error handler
    lua_remove(L, 1);

    if (result != LUA_OK) {
        // Error con traceback está en el stack
        return lua_error(L);
    }

    return lua_gettop(L);
}

5. Módulo Completo: Sistema de Archivos

// filesystem.c - Módulo completo de ejemplo
#include <lua.h>
#include <lauxlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

// Verificar si existe un archivo
static int l_exists(lua_State *L) {
    const char *path = luaL_checkstring(L, 1);
    struct stat st;
    lua_pushboolean(L, stat(path, &st) == 0);
    return 1;
}

// Obtener tipo de archivo
static int l_type(lua_State *L) {
    const char *path = luaL_checkstring(L, 1);
    struct stat st;

    if (stat(path, &st) != 0) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }

    if (S_ISREG(st.st_mode)) {
        lua_pushstring(L, "file");
    } else if (S_ISDIR(st.st_mode)) {
        lua_pushstring(L, "directory");
    } else if (S_ISLNK(st.st_mode)) {
        lua_pushstring(L, "link");
    } else {
        lua_pushstring(L, "other");
    }

    return 1;
}

// Listar directorio
static int l_list(lua_State *L) {
    const char *path = luaL_checkstring(L, 1);

    DIR *dir = opendir(path);
    if (!dir) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }

    lua_newtable(L);
    int index = 1;

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        // Omitir . y ..
        if (strcmp(entry->d_name, ".") == 0 ||
            strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        lua_pushstring(L, entry->d_name);
        lua_rawseti(L, -2, index++);
    }

    closedir(dir);
    return 1;
}

// Crear directorio
static int l_mkdir(lua_State *L) {
    const char *path = luaL_checkstring(L, 1);
    mode_t mode = luaL_optinteger(L, 2, 0755);

    if (mkdir(path, mode) != 0) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }

    lua_pushboolean(L, 1);
    return 1;
}

// Eliminar archivo/directorio
static int l_remove(lua_State *L) {
    const char *path = luaL_checkstring(L, 1);

    if (remove(path) != 0) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }

    lua_pushboolean(L, 1);
    return 1;
}

// Renombrar
static int l_rename(lua_State *L) {
    const char *old = luaL_checkstring(L, 1);
    const char *new = luaL_checkstring(L, 2);

    if (rename(old, new) != 0) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }

    lua_pushboolean(L, 1);
    return 1;
}

// Obtener información
static int l_stat(lua_State *L) {
    const char *path = luaL_checkstring(L, 1);
    struct stat st;

    if (stat(path, &st) != 0) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }

    lua_createtable(L, 0, 6);

    lua_pushinteger(L, st.st_size);
    lua_setfield(L, -2, "size");

    lua_pushinteger(L, st.st_mtime);
    lua_setfield(L, -2, "mtime");

    lua_pushinteger(L, st.st_atime);
    lua_setfield(L, -2, "atime");

    lua_pushinteger(L, st.st_mode);
    lua_setfield(L, -2, "mode");

    lua_pushboolean(L, S_ISDIR(st.st_mode));
    lua_setfield(L, -2, "is_directory");

    lua_pushboolean(L, S_ISREG(st.st_mode));
    lua_setfield(L, -2, "is_file");

    return 1;
}

// Directorio actual
static int l_getcwd(lua_State *L) {
    char buf[1024];
    if (getcwd(buf, sizeof(buf)) == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }
    lua_pushstring(L, buf);
    return 1;
}

// Cambiar directorio
static int l_chdir(lua_State *L) {
    const char *path = luaL_checkstring(L, 1);

    if (chdir(path) != 0) {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        return 2;
    }

    lua_pushboolean(L, 1);
    return 1;
}

// Tabla de funciones
static const struct luaL_Reg fslib[] = {
    {"exists", l_exists},
    {"type", l_type},
    {"list", l_list},
    {"mkdir", l_mkdir},
    {"remove", l_remove},
    {"rename", l_rename},
    {"stat", l_stat},
    {"getcwd", l_getcwd},
    {"chdir", l_chdir},
    {NULL, NULL}
};

// Inicialización
int luaopen_filesystem(lua_State *L) {
    luaL_newlib(L, fslib);

    // Agregar constantes
    lua_pushstring(L, "/");
    lua_setfield(L, -2, "sep");

    return 1;
}

6. Compilación y Uso

Makefile para el Módulo

# Makefile
CC = gcc
CFLAGS = -O2 -Wall -fPIC
LDFLAGS = -shared

# Detectar SO
UNAME := $(shell uname)
ifeq ($(UNAME), Darwin)
    EXT = so
    LDFLAGS += -undefined dynamic_lookup
else
    EXT = so
endif

# Rutas Lua (ajustar según instalación)
LUA_INCDIR = /usr/include/lua5.4
LUA_LIBDIR = /usr/lib

all: filesystem.$(EXT) buffer.$(EXT)

filesystem.$(EXT): filesystem.c
	$(CC) $(CFLAGS) -I$(LUA_INCDIR) $(LDFLAGS) -o $@ $<

buffer.$(EXT): userdata.c
	$(CC) $(CFLAGS) -I$(LUA_INCDIR) $(LDFLAGS) -o $@ $<

clean:
	rm -f *.so

install: all
	cp *.$(EXT) $(LUA_LIBDIR)/lua/5.4/

.PHONY: all clean install

Uso desde Lua

-- test_modules.lua
local fs = require("filesystem")
local buffer = require("buffer")

-- Filesystem
print("Current dir:", fs.getcwd())

local files, err = fs.list(".")
if files then
    for _, file in ipairs(files) do
        local info = fs.stat(file)
        if info then
            print(string.format("%s: %d bytes", file, info.size))
        end
    end
end

-- Buffer
local buf = buffer.new(256)
buf:append("Hello ")
buf:append("World!")

print(buf:get())  -- "Hello World!"
print(#buf)       -- 12 (usando __len)
print(buf)        -- Buffer(12/256) (usando __tostring)

buf:clear():append("New content")
print(buf:get())  -- "New content"

Mejores Prácticas

1. Validación de Argumentos

// Siempre validar entradas
static int l_safe_function(lua_State *L) {
    // Verificar número de argumentos
    int n = lua_gettop(L);
    if (n < 2) {
        return luaL_error(L, "expected at least 2 arguments, got %d", n);
    }

    // Validar tipos
    const char *name = luaL_checkstring(L, 1);
    int age = luaL_checkinteger(L, 2);

    // Validar rangos
    luaL_argcheck(L, age >= 0 && age <= 150, 2, "age out of range");

    // ... lógica
    return 0;
}

2. Gestión de Memoria

// SIEMPRE liberar recursos en __gc
static int l_resource_gc(lua_State *L) {
    Resource *res = (Resource *)luaL_checkudata(L, 1, "Resource");

    if (res->handle) {
        close_resource(res->handle);
        res->handle = NULL;
    }

    if (res->buffer) {
        free(res->buffer);
        res->buffer = NULL;
    }

    return 0;
}

3. Thread Safety

// Para módulos multi-thread
#include <pthread.h>

static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

static int l_thread_safe_op(lua_State *L) {
    pthread_mutex_lock(&lock);

    // Operación crítica
    // ...

    pthread_mutex_unlock(&lock);
    return 0;
}

Conclusión

La C API de Lua es la puerta a rendimiento extremo y funcionalidades del sistema. Puntos clave:

Con estos fundamentos, puedes crear módulos nativos potentes que extiendan Lua con cualquier funcionalidad de C/C++.