Lua - C Integration
C Integration
Time: ~35 minutes
Learn how to integrate Lua with C/C++ applications. This tutorial covers the complete Lua C API, embedding Lua as a scripting engine, extending Lua with C functions, and building real-world integrated systems.
Learning Objectives
- Understand the Lua C API and stack-based operations
- Embed Lua interpreter in C/C++ applications
- Call Lua functions from C and C functions from Lua
- Create C extensions and libraries for Lua
- Handle memory management and error handling in C/Lua integration
- Build real-world integrated applications
Prerequisites
- Basic C programming knowledge
- Understanding of Lua fundamentals
- Familiarity with compilation and linking
The Lua C API Basics
Understanding the Lua Stack
The Lua C API uses a virtual stack to exchange values between C and Lua. Understanding this stack is crucial for successful integration.
// Basic C program embedding Lua
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
int main() {
// Create new Lua state
lua_State *L = luaL_newstate();
// Load standard libraries
luaL_openlibs(L);
// Execute simple Lua code
luaL_dostring(L, "print('Hello from Lua!')");
// Clean up
lua_close(L);
return 0;
}
Compilation: gcc -o embed embed.c -llua -lm
Expected Output: Hello from Lua!
Stack Operations
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
void demonstrate_stack(lua_State *L) {
// Push values onto stack
lua_pushinteger(L, 42); // Stack: [42]
lua_pushstring(L, "hello"); // Stack: [42, "hello"]
lua_pushboolean(L, 1); // Stack: [42, "hello", true]
printf("Stack size: %d\n", lua_gettop(L));
// Access values (negative indices count from top)
printf("Top (-1): %s\n", lua_tostring(L, -1)); // "true"
printf("Middle (-2): %s\n", lua_tostring(L, -2)); // "hello"
printf("Bottom (-3): %lld\n", lua_tointeger(L, -3)); // 42
// Pop values
lua_pop(L, 3); // Remove all 3 values
printf("Stack size after pop: %d\n", lua_gettop(L));
}
int main() {
lua_State *L = luaL_newstate();
demonstrate_stack(L);
lua_close(L);
return 0;
}
Expected Output:
Stack size: 3
Top (-1): 1
Middle (-2): hello
Bottom (-3): 42
Stack size after pop: 0
Calling Lua Functions from C
Loading and Executing Lua Scripts
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
// Create a simple Lua script file first
// File: math_functions.lua
/*
function add(a, b)
return a + b
end
function factorial(n)
if n <= 1 then
return 1
else
return n * factorial(n - 1)
end
end
-- Global variable
pi = 3.14159
*/
int call_lua_function(lua_State *L) {
// Load and execute Lua file
if (luaL_dofile(L, "math_functions.lua") != LUA_OK) {
fprintf(stderr, "Error: %s\n", lua_tostring(L, -1));
return -1;
}
// Call add function
lua_getglobal(L, "add"); // Push function onto stack
lua_pushinteger(L, 10); // Push first argument
lua_pushinteger(L, 15); // Push second argument
// Call function with 2 arguments, expecting 1 result
if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
fprintf(stderr, "Error calling add: %s\n", lua_tostring(L, -1));
return -1;
}
// Get result
long long result = lua_tointeger(L, -1);
lua_pop(L, 1); // Remove result from stack
printf("add(10, 15) = %lld\n", result);
// Call factorial function
lua_getglobal(L, "factorial");
lua_pushinteger(L, 5);
if (lua_pcall(L, 1, 1, 0) != LUA_OK) {
fprintf(stderr, "Error calling factorial: %s\n", lua_tostring(L, -1));
return -1;
}
result = lua_tointeger(L, -1);
lua_pop(L, 1);
printf("factorial(5) = %lld\n", result);
// Access global variable
lua_getglobal(L, "pi");
double pi_value = lua_tonumber(L, -1);
lua_pop(L, 1);
printf("pi = %.5f\n", pi_value);
return 0;
}
int main() {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
call_lua_function(L);
lua_close(L);
return 0;
}
Expected Output:
add(10, 15) = 25
factorial(5) = 120
pi = 3.14159
Calling C Functions from Lua
Creating C Functions for Lua
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
#include <math.h>
// C function that can be called from Lua
// All Lua-callable C functions must have this signature
static int c_power(lua_State *L) {
// Check argument count
int argc = lua_gettop(L);
if (argc != 2) {
return luaL_error(L, "power() expects exactly 2 arguments");
}
// Get arguments (luaL_checknumber ensures they're numbers)
double base = luaL_checknumber(L, 1);
double exponent = luaL_checknumber(L, 2);
// Calculate result
double result = pow(base, exponent);
// Push result onto stack
lua_pushnumber(L, result);
// Return number of results (1 in this case)
return 1;
}
// C function with multiple return values
static int c_divmod(lua_State *L) {
double dividend = luaL_checknumber(L, 1);
double divisor = luaL_checknumber(L, 2);
if (divisor == 0) {
return luaL_error(L, "division by zero");
}
// Push quotient and remainder
lua_pushnumber(L, floor(dividend / divisor));
lua_pushnumber(L, fmod(dividend, divisor));
// Return 2 values
return 2;
}
// C function that works with strings
static int c_reverse_string(lua_State *L) {
size_t len;
const char *input = luaL_checklstring(L, 1, &len);
// Create reversed string
char *reversed = malloc(len + 1);
for (size_t i = 0; i < len; i++) {
reversed[i] = input[len - 1 - i];
}
reversed[len] = '\0';
lua_pushstring(L, reversed);
free(reversed);
return 1;
}
int main() {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
// Register C functions in Lua global namespace
lua_pushcfunction(L, c_power);
lua_setglobal(L, "power");
lua_pushcfunction(L, c_divmod);
lua_setglobal(L, "divmod");
lua_pushcfunction(L, c_reverse_string);
lua_setglobal(L, "reverse");
// Test the functions
const char *test_script =
"print('power(2, 8) =', power(2, 8))\n"
"local q, r = divmod(17, 5)\n"
"print('divmod(17, 5) =', q, r)\n"
"print('reverse(\"hello\") =', reverse('hello'))\n"
"\n"
"-- Error handling example\n"
"local success, result = pcall(power, 2)\n"
"if not success then\n"
" print('Error:', result)\n"
"end\n";
if (luaL_dostring(L, test_script) != LUA_OK) {
fprintf(stderr, "Error: %s\n", lua_tostring(L, -1));
}
lua_close(L);
return 0;
}
Expected Output:
power(2, 8) = 256.0
divmod(17, 5) = 3.0 2.0
reverse("hello") = olleh
Error: power() expects exactly 2 arguments
Creating C Libraries for Lua
Building a Math Extension Library
// mathext.c - A C extension library for Lua
#include <lua.h>
#include <lauxlib.h>
#include <math.h>
// Library functions
static int mathext_gcd(lua_State *L) {
lua_Integer a = luaL_checkinteger(L, 1);
lua_Integer b = luaL_checkinteger(L, 2);
// Euclidean algorithm
while (b != 0) {
lua_Integer temp = b;
b = a % b;
a = temp;
}
lua_pushinteger(L, a);
return 1;
}
static int mathext_lcm(lua_State *L) {
lua_Integer a = luaL_checkinteger(L, 1);
lua_Integer b = luaL_checkinteger(L, 2);
// Call our gcd function
lua_pushcfunction(L, mathext_gcd);
lua_pushinteger(L, a);
lua_pushinteger(L, b);
lua_call(L, 2, 1);
lua_Integer gcd = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_pushinteger(L, (a * b) / gcd);
return 1;
}
static int mathext_isprime(lua_State *L) {
lua_Integer n = luaL_checkinteger(L, 1);
if (n < 2) {
lua_pushboolean(L, 0);
return 1;
}
for (lua_Integer i = 2; i * i <= n; i++) {
if (n % i == 0) {
lua_pushboolean(L, 0);
return 1;
}
}
lua_pushboolean(L, 1);
return 1;
}
// Function registry
static const luaL_Reg mathext_functions[] = {
{"gcd", mathext_gcd},
{"lcm", mathext_lcm},
{"isprime", mathext_isprime},
{NULL, NULL} // Sentinel
};
// Library initialization function
// This is called when the library is loaded
int luaopen_mathext(lua_State *L) {
// Create new table for our library
luaL_newlib(L, mathext_functions);
// Add version information
lua_pushstring(L, "1.0");
lua_setfield(L, -2, "_VERSION");
return 1; // Return the library table
}
// For static linking, we can also create a simple test
#ifdef MATHEXT_STATIC_TEST
int main() {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
// Register our library
luaL_requiref(L, "mathext", luaopen_mathext, 1);
lua_pop(L, 1); // Remove library from stack
// Test the library
const char *test_script =
"local mathext = require('mathext')\n"
"print('mathext version:', mathext._VERSION)\n"
"print('gcd(48, 18) =', mathext.gcd(48, 18))\n"
"print('lcm(12, 15) =', mathext.lcm(12, 15))\n"
"print('isprime(17) =', mathext.isprime(17))\n"
"print('isprime(15) =', mathext.isprime(15))\n";
if (luaL_dostring(L, test_script) != LUA_OK) {
fprintf(stderr, "Error: %s\n", lua_tostring(L, -1));
}
lua_close(L);
return 0;
}
#endif
Building and Using the Library
# Compile as shared library (Linux/macOS)
gcc -shared -fPIC -o mathext.so mathext.c -llua
# Compile with static test
gcc -DMATHEXT_STATIC_TEST -o mathext_test mathext.c -llua -lm
Expected Output:
mathext version: 1.0
gcd(48, 18) = 6
lcm(12, 15) = 60
isprime(17) = true
isprime(15) = false
Real-World Application: Configuration System
Lua-Powered Application Configuration
// config_system.c - Application with Lua configuration
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
#include <string.h>
typedef struct {
char server_name[256];
int port;
int max_connections;
double timeout;
int debug_enabled;
} AppConfig;
// C function to provide system information to Lua
static int get_system_info(lua_State *L) {
lua_newtable(L);
lua_pushstring(L, "Linux");
lua_setfield(L, -2, "os");
lua_pushinteger(L, 8);
lua_setfield(L, -2, "cpu_cores");
lua_pushinteger(L, 16384);
lua_setfield(L, -2, "memory_mb");
return 1;
}
int load_config(const char *config_file, AppConfig *config) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
// Register system info function
lua_pushcfunction(L, get_system_info);
lua_setglobal(L, "get_system_info");
// Load and execute config file
if (luaL_dofile(L, config_file) != LUA_OK) {
fprintf(stderr, "Config error: %s\n", lua_tostring(L, -1));
lua_close(L);
return -1;
}
// Extract configuration values
lua_getglobal(L, "server_name");
if (lua_isstring(L, -1)) {
strncpy(config->server_name, lua_tostring(L, -1), 255);
config->server_name[255] = '\0';
}
lua_pop(L, 1);
lua_getglobal(L, "port");
config->port = lua_isinteger(L, -1) ? lua_tointeger(L, -1) : 8080;
lua_pop(L, 1);
lua_getglobal(L, "max_connections");
config->max_connections = lua_isinteger(L, -1) ? lua_tointeger(L, -1) : 100;
lua_pop(L, 1);
lua_getglobal(L, "timeout");
config->timeout = lua_isnumber(L, -1) ? lua_tonumber(L, -1) : 30.0;
lua_pop(L, 1);
lua_getglobal(L, "debug_enabled");
config->debug_enabled = lua_isboolean(L, -1) ? lua_toboolean(L, -1) : 0;
lua_pop(L, 1);
lua_close(L);
return 0;
}
int main() {
// First, create a sample config file
FILE *config_file = fopen("app_config.lua", "w");
fprintf(config_file,
"-- Application Configuration\n"
"local system = get_system_info()\n"
"\n"
"server_name = \"MyApp Server\"\n"
"port = 8080\n"
"\n"
"-- Dynamic configuration based on system\n"
"if system.cpu_cores >= 8 then\n"
" max_connections = 500\n"
"else\n"
" max_connections = 200\n"
"end\n"
"\n"
"-- Environment-based settings\n"
"local env = os.getenv('APP_ENV') or 'development'\n"
"if env == 'production' then\n"
" debug_enabled = false\n"
" timeout = 60.0\n"
"else\n"
" debug_enabled = true\n"
" timeout = 10.0\n"
"end\n"
"\n"
"print('Configuration loaded for', env, 'environment')\n"
);
fclose(config_file);
// Load configuration
AppConfig config = {0};
if (load_config("app_config.lua", &config) == 0) {
printf("Configuration loaded successfully:\n");
printf(" Server Name: %s\n", config.server_name);
printf(" Port: %d\n", config.port);
printf(" Max Connections: %d\n", config.max_connections);
printf(" Timeout: %.1f seconds\n", config.timeout);
printf(" Debug Enabled: %s\n", config.debug_enabled ? "Yes" : "No");
}
return 0;
}
Expected Output:
Configuration loaded for development environment
Configuration loaded successfully:
Server Name: MyApp Server
Port: 8080
Max Connections: 500
Timeout: 10.0 seconds
Debug Enabled: Yes
Memory Management and Error Handling
Safe Memory Practices
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
// Custom memory allocator for tracking
static void* debug_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
int *total_memory = (int*)ud;
if (nsize == 0) {
// Free memory
if (ptr) {
*total_memory -= osize;
free(ptr);
printf("Freed %zu bytes, total: %d\n", osize, *total_memory);
}
return NULL;
} else {
// Allocate or reallocate
void *new_ptr = realloc(ptr, nsize);
if (new_ptr) {
*total_memory += (nsize - osize);
printf("Allocated %zu bytes, total: %d\n", nsize, *total_memory);
}
return new_ptr;
}
}
// Error handling example
static int protected_divide(lua_State *L) {
double a = luaL_checknumber(L, 1);
double b = luaL_checknumber(L, 2);
if (b == 0.0) {
// Return nil and error message
lua_pushnil(L);
lua_pushstring(L, "division by zero");
return 2;
}
lua_pushnumber(L, a / b);
return 1;
}
int main() {
int total_memory = 0;
// Create Lua state with custom allocator
lua_State *L = lua_newstate(debug_alloc, &total_memory);
if (!L) {
fprintf(stderr, "Failed to create Lua state\n");
return 1;
}
luaL_openlibs(L);
// Register protected function
lua_pushcfunction(L, protected_divide);
lua_setglobal(L, "safe_divide");
// Test error handling
const char *test_script =
"local result, err = safe_divide(10, 2)\n"
"if result then\n"
" print('10 / 2 =', result)\n"
"else\n"
" print('Error:', err)\n"
"end\n"
"\n"
"local result2, err2 = safe_divide(10, 0)\n"
"if result2 then\n"
" print('10 / 0 =', result2)\n"
"else\n"
" print('Error:', err2)\n"
"end\n";
printf("Executing Lua script...\n");
if (luaL_dostring(L, test_script) != LUA_OK) {
fprintf(stderr, "Script error: %s\n", lua_tostring(L, -1));
}
printf("Closing Lua state...\n");
lua_close(L);
printf("Final memory usage: %d bytes\n", total_memory);
return 0;
}
Expected Output:
Allocated 32 bytes, total: 32
Allocated 64 bytes, total: 96
... (more allocation messages)
Executing Lua script...
10 / 2 = 5.0
Error: division by zero
Closing Lua state...
Freed 64 bytes, total: 32
Freed 32 bytes, total: 0
Final memory usage: 0 bytes
Best Practices
Integration Guidelines
- Stack Management: Always balance pushes and pops
- Error Handling: Use
lua_pcall()
for protected calls - Memory Management: Properly close Lua states
- Type Checking: Use
luaL_check*()
functions for safety - Performance: Pre-compile frequently used scripts
Common Pitfalls
Stack Overflow: Pushing too many values without cleaning up
Memory Leaks: Not calling lua_close()
Type Confusion: Not checking Lua value types in C
Error Propagation: Not handling Lua errors in C code
Checks for Understanding
- What is the purpose of the Lua stack in the C API?
- How do you call a Lua function from C code safely?
- What signature must C functions have to be callable from Lua?
- How do you handle errors when integrating C and Lua?
Show Answers
- The stack is used to exchange values between C and Lua, providing a consistent interface for all data types.
- Use
lua_pcall()
instead oflua_call()
to catch and handle errors gracefully. - C functions must have signature
int function_name(lua_State *L)
and return the number of results. - Use
lua_pcall()
, check return codes, and useluaL_error()
to report errors from C to Lua.
Exercises
- Create a C function that accepts a table and returns the sum of all numeric values.
- Build a simple plugin system where Lua scripts can extend C application functionality.
- Implement a configuration system that validates settings using both C and Lua code.