← Volver al listado de tecnologías
Capítulo 20: La C API - Extendiendo Lua
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:
- Stack: Estructura central de comunicación
- Userdata: Exponer objetos C a Lua
- Referencias: Mantener valores Lua desde C
- Errores: Manejo robusto con
luaL_errorylua_pcall - Metatables: Comportamiento personalizado para userdata
Con estos fundamentos, puedes crear módulos nativos potentes que extiendan Lua con cualquier funcionalidad de C/C++.