Lo que diferencia a Lua de la mayoría de lenguajes de scripting es que fue diseñado desde el primer día para vivir dentro de otra aplicación. Python, Ruby o JavaScript tienen su propia ejecutable y su propio ciclo de vida; Lua es una biblioteca. La función main la pone la aplicación anfitriona, y Lua se limita a ejecutar los scripts que esa aplicación le encarga. La C API es el puente entre los dos mundos.
La pila: el mecanismo de comunicación
La C API de Lua usa una pila (stack) para pasar valores entre C y Lua. Cuando Lua llama a una función C, los argumentos están en la pila con índices positivos (1 = primero) o negativos (-1 = último). La función C empuja sus resultados a la pila y devuelve el número de valores que ha empujado.
Este diseño es explícito pero predecible, y permite que la VM gestione la memoria sin necesidad de referencias externas ni garbage collector manual en el lado de C.
Embeber Lua en una aplicación C
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
int main(void) {
/* Crear el estado de la VM */
lua_State *L = luaL_newstate();
if (!L) { fprintf(stderr, "Error creando lua_Staten"); return 1; }
/* Cargar la biblioteca estándar (io, math, string, table...) */
luaL_openlibs(L);
/* Ejecutar un fichero .lua */
int resultado = luaL_dofile(L, "script.lua");
if (resultado != LUA_OK) {
/* El mensaje de error está en la cima de la pila */
fprintf(stderr, "Error Lua: %sn", lua_tostring(L, -1));
lua_pop(L, 1); /* limpiar el error de la pila */
}
/* Evaluar un fragmento de código inline */
luaL_dostring(L, "print('Hola desde C')");
/* Cerrar la VM y liberar toda la memoria */
lua_close(L);
return 0;
}
Para compilar, hay que enlazar con la biblioteca de Lua:
gcc main.c -o programa -llua5.4 -lm # O con pkg-config si está disponible: gcc main.c -o programa $(pkg-config --cflags --libs lua5.4)
Llamar funciones Lua desde C
lua_pcall llama a una función Lua de forma protegida: si la función lanza un error, lua_pcall lo captura y lo deja en la pila en lugar de abortar el proceso:
/* Ejecutar la función Lua: resultado = suma(3, 4) */
lua_getglobal(L, "suma"); /* empujar la función */
lua_pushinteger(L, 3); /* argumento 1 */
lua_pushinteger(L, 4); /* argumento 2 */
/* Llamar: 2 argumentos, 1 resultado esperado */
if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
fprintf(stderr, "Error: %sn", lua_tostring(L, -1));
lua_pop(L, 1);
} else {
lua_Integer res = lua_tointeger(L, -1);
printf("Resultado: %lldn", (long long)res);
lua_pop(L, 1); /* limpiar el resultado de la pila */
}
Registrar funciones C en Lua
Para que un script Lua pueda llamar a una función C, hay que registrarla en el estado de la VM. La firma de toda función C llamable desde Lua es int nombre(lua_State *L):
/* Función C que suma dos números y devuelve el resultado */
static int c_suma(lua_State *L) {
/* Comprobar que hay exactamente 2 argumentos numéricos */
luaL_checkinteger(L, 1);
luaL_checkinteger(L, 2);
lua_Integer a = lua_tointeger(L, 1);
lua_Integer b = lua_tointeger(L, 2);
lua_pushinteger(L, a + b);
return 1; /* número de valores que devolvemos */
}
/* Función C que devuelve dos valores (módulo y cociente) */
static int c_divmod(lua_State *L) {
lua_Integer a = luaL_checkinteger(L, 1);
lua_Integer b = luaL_checkinteger(L, 2);
if (b == 0) luaL_error(L, "división por cero");
lua_pushinteger(L, a / b); /* cociente */
lua_pushinteger(L, a %% b); /* resto */
return 2; /* devolvemos 2 valores */
}
/* Registrar ambas funciones como globales de Lua */
lua_register(L, "suma", c_suma);
lua_register(L, "divmod", c_divmod);
/* Ahora desde Lua se puede escribir: */
/* print(suma(10, 3)) --> 13 */
/* local q, r = divmod(10, 3) --> 3, 1 */
Crear un módulo C para Lua
La forma más limpia de distribuir extensiones C para Lua es como módulo dinámico (.so en Linux, .dll en Windows). El módulo implementa la función luaopen_nombre que inicializa y devuelve la tabla del módulo:
#include <lua.h>
#include <lauxlib.h>
static int mi_funcion(lua_State *L) {
const char *s = luaL_checkstring(L, 1);
lua_pushfstring(L, "Procesado: %s", s);
return 1;
}
/* Tabla de funciones del módulo */
static const luaL_Reg funciones[] = {
{"procesar", mi_funcion},
{NULL, NULL} /* centinela */
};
/* Punto de entrada que llama require("mimodulo") */
int luaopen_mimodulo(lua_State *L) {
luaL_newlib(L, funciones);
return 1;
}
# Compilar como biblioteca compartida
gcc -shared -fPIC mimodulo.c -o mimodulo.so -llua5.4
# Desde Lua:
# local m = require("mimodulo")
# print(m.procesar("hola")) --> Procesado: hola
Gestionar tipos y errores
La API ofrece funciones de comprobación de tipo que lanzan errores descriptivos automáticamente (luaL_check*) y variantes que simplemente devuelven un valor por defecto si el tipo es incorrecto (lua_to*). En producción conviene usar las variantes luaL_check* para dar mensajes de error útiles al desarrollador Lua:
static int configurar(lua_State *L) {
/* Obtener argumentos con comprobación estricta */
const char *host = luaL_checkstring(L, 1);
lua_Integer puerto = luaL_checkinteger(L, 2);
/* Tercer argumento opcional: timeout con valor por defecto 30 */
lua_Integer timeout = luaL_optinteger(L, 3, 30);
printf("Conectando a %s:%lld (timeout: %llds)n",
host, (long long)puerto, (long long)timeout);
return 0;
}
La C API es la razón por la que Lua aparece embebido en tantos sitios distintos: desde motores de juego como los que usan LÖVE 2D y Defold hasta el proxy inverso de OpenResty. Si has trabajado con la C API de Python, la de Lua te resultará más sencilla: menos magia, más control explícito de la pila.
Imagen: Pexels / Pixabay
