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

  1. What is the purpose of the Lua stack in the C API?
  2. How do you call a Lua function from C code safely?
  3. What signature must C functions have to be callable from Lua?
  4. How do you handle errors when integrating C and Lua?
Show Answers
  1. The stack is used to exchange values between C and Lua, providing a consistent interface for all data types.
  2. Use lua_pcall() instead of lua_call() to catch and handle errors gracefully.
  3. C functions must have signature int function_name(lua_State *L) and return the number of results.
  4. Use lua_pcall(), check return codes, and use luaL_error() to report errors from C to Lua.

Exercises

  1. Create a C function that accepts a table and returns the sum of all numeric values.
  2. Build a simple plugin system where Lua scripts can extend C application functionality.
  3. Implement a configuration system that validates settings using both C and Lua code.