Embedding Lua in C — Beginner’s Tutorial

In this article you will learn how to embed the Lua language inside a C program. This is simpler than it seems, and it opens up many interesting possibilities.


What is “embedding Lua in C”?

Imagine you have a C program and you want to allow users to write scripts to customize its behavior — without needing to recompile the program. Lua is perfect for this.

By embedding Lua, your C program can: - Execute Lua code in real-time - Read results calculated by Lua - Expose C functions for the Lua code to use


Prerequisites

Before starting, you need to know: - The basics of C (variables, functions, printf) - Have gcc installed on your system

You don’t need to know Lua. The examples are simple enough to understand from context.


What is this useful for in practice?

Safe Evaluation

It is possible to execute user-provided Lua code without worrying about malicious code injection. This is extremely useful for building plugins, extensions, and no-code platforms (as is the case with OUI).

Creating No-Code Platforms

You can create a system where your users write scripts in Lua to automate tasks or create plugins — without needing to touch the C code of your program.

Creating Runtimes

Just as it was done in VibeScript and Darwin, you can create runtimes with embedded libraries for the most diverse use cases.


Installation

In this tutorial we use LuaCEmbed, a library that greatly simplifies working with Lua in C.

Step 1: Download the library with a single command:

curl -L https://github.com/OUIsolutions/LuaCEmbed/releases/download/0.13.0/LuaCEmbedOne.c -o LuaCEmbedOne.c

Step 2: Create a main.c file and include the library at the top:

#include "LuaCEmbedOne.c"  // includes the entire library

int main() {
    // your code here
}

The library is single-file, so you don’t need to install anything — just include it.


Compilation

On Linux:

gcc main.c -o program.out

On Windows with MinGW:

i686-w64-mingw32-gcc main.c -o program.exe -lws2_32

On Windows with MSVC:

cl.exe main.c /Fe:program.exe

Basic Example

Let’s start with the simplest example possible: execute a calculation in Lua and read the result back in C.

#include "LuaCEmbedOne.c"

int main(int argc, char *argv[]) {
    // 1. Create a new Lua "virtual machine"
    LuaCEmbed *l = newLuaCEmbedEvaluation();

    // 2. Execute Lua code: define the variable 'r' as 30
    LuaCEmbed_evaluate(l, "r = 30");

    // 3. Evaluate a Lua expression and read the result as an integer (long)
    long calc = LuaCEmbed_get_evaluation_long(l, "r + 20");
    printf("result: %ld\n", calc);  // prints: 50

    // 4. Check if any error occurred during execution
    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }

    // 5. Free the memory used by the virtual machine
    LuaCEmbed_free(l);
    return 0;
}

Output:

result: 50

Pattern you will repeat in all examples: 1. newLuaCEmbedEvaluation() — creates the instance 2. LuaCEmbed_evaluate() — executes Lua code 3. Reads the result with the get_evaluation_* functions 4. Checks for errors with LuaCEmbed_has_errors 5. LuaCEmbed_free() — frees memory


Reading Values from Lua

String

#include "LuaCEmbedOne.c"

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_evaluate(l, "r = 'hello world'");

    // Read the variable 'r' as text
    char *result = LuaCEmbed_get_evaluation_string(l, "r");
    printf("result: %s\n", result);

    LuaCEmbed_free(l);
    return 0;
}

Output:

result: hello world

Integer number (long)

#include "LuaCEmbedOne.c"

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_evaluate(l, "r = 20 + 30");
    long result = LuaCEmbed_get_evaluation_long(l, "r");
    printf("result: %ld\n", result);
    LuaCEmbed_free(l);
    return 0;
}

Output:

result: 50

Decimal number (double)

#include "LuaCEmbedOne.c"

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_evaluate(l, "r = 20 + 30");
    double result = LuaCEmbed_get_evaluation_double(l, "r");
    printf("result: %lf\n", result);
    LuaCEmbed_free(l);
    return 0;
}

Output:

result: 50.000000

Boolean

#include "LuaCEmbedOne.c"

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_evaluate(l, "r = true");
    bool result = LuaCEmbed_get_evaluation_bool(l, "r");
    printf("result: %d\n", result);  // 1 = true, 0 = false
    LuaCEmbed_free(l);
    return 0;
}

Output:

result: 1

Discovering the type of a variable

If you don’t know what type the variable has, you can check before reading:

#include "LuaCEmbedOne.c"

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_evaluate(l, "r = 'hello world'");
    int r_type = LuaCEmbed_get_evaluation_type(l, "r");
    const char *type_name = LuaCembed_convert_arg_code(r_type);
    printf("type: %s\n", type_name);
    LuaCEmbed_free(l);
    return 0;
}

Output:

type: string

Native Lua Functions

By default, LuaCEmbed does not expose the native Lua functions (such as print, io, os, etc.) to the evaluated code. This is intentional — it ensures that third-party code cannot access files or execute commands on your system.

If you trust the code you are going to execute and need these functions:

#include "LuaCEmbedOne.c"

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();

    // WARNING: only use this if you trust the code that will be executed!
    // This gives access to print, io, os, and other native Lua libs.
    LuaCEmbed_load_native_libs(l);

    LuaCEmbed_evaluate(l, "print('hello from lua')");

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }
    LuaCEmbed_free(l);
    return 0;
}

Output:

hello from lua

Executing a .lua file

Instead of passing code as a string, you can load a .lua file from the disk:

#include "LuaCEmbedOne.c"

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_load_native_libs(l);

    // Loads and executes the "script.lua" file
    LuaCEmbed_evaluete_file(l, "script.lua");

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }
    LuaCEmbed_free(l);
    return 0;
}

Callbacks — Exposing C Functions to Lua

Callbacks are the way to create C functions that can be called from Lua code. This allows you to define exactly what the user can or cannot do.

Simple callback (no arguments, no return)

#include "LuaCEmbedOne.c"

// This C function will be callable from Lua
LuaCEmbedResponse *hello(LuaCEmbed *args) {
    printf("my first callback\n");
    return NULL;  // NULL means: returns nothing (nil in Lua)
}

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();

    // Registers the C function with the name "hello" in Lua
    LuaCEmbed_add_callback(l, "hello", hello);

    // Now Lua code can call hello()
    LuaCEmbed_evaluate(l, "hello()");

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }
    LuaCEmbed_free(l);
    return 0;
}

Output:

my first callback

Receiving arguments in the callback

Arguments passed by Lua are available inside the C function. They are indexed starting from 0 (C standard), even though Lua usually uses indices starting from 1.

#include "LuaCEmbedOne.c"

LuaCEmbedResponse *test_func(LuaCEmbed *args) {
    // How many arguments were passed?
    long size = LuaCEmbed_get_total_args(args);
    if (size == 0) {
        printf("no arguments provided\n");
        return NULL;
    }

    // Reads the first argument (index 0)
    int index = 0;
    int arg_type = LuaCEmbed_get_arg_type(args, index);

    // Acts according to the received type
    if (arg_type == LUA_CEMBED_NUMBER) {
        printf("number: %lf\n", LuaCEmbed_get_double_arg(args, index));
    } else if (arg_type == LUA_CEMBED_STRING) {
        printf("string: %s\n", LuaCEmbed_get_str_arg(args, index));
    } else if (arg_type == LUA_CEMBED_BOOL) {
        printf("bool: %d\n", LuaCEmbed_get_bool_arg(args, index));
    } else {
        // For other types (like table), shows the type name
        const char *type_name = LuaCembed_convert_arg_code(arg_type);
        printf("type: %s\n", type_name);
    }

    return NULL;
}

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_add_callback(l, "test", test_func);

    // Tests with different types of arguments
    LuaCEmbed_evaluate(l, "test()");          // no argument
    LuaCEmbed_evaluate(l, "test(10)");        // number
    LuaCEmbed_evaluate(l, "test('hello')");   // string
    LuaCEmbed_evaluate(l, "test(true)");      // boolean
    LuaCEmbed_evaluate(l, "test({a=30})");    // table

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }
    LuaCEmbed_free(l);
    return 0;
}

Output:

no arguments provided
number: 10.000000
string: hello
bool: 1
type: table

Returning values from a callback

A callback can return a value to Lua using the LuaCEmbed_send_* functions:

#include "LuaCEmbedOne.c"

LuaCEmbedResponse *add(LuaCEmbed *args) {
    double a = LuaCEmbed_get_double_arg(args, 0);
    double b = LuaCEmbed_get_double_arg(args, 1);

    // If there was an error reading the arguments (e.g. wrong type), propagates the error
    if (LuaCEmbed_has_errors(args)) {
        return LuaCEmbed_send_error(LuaCEmbed_get_error_message(args));
    }

    return LuaCEmbed_send_double(a + b);  // returns the sum to Lua
}

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_add_callback(l, "add", add);

    double result = LuaCEmbed_get_evaluation_double(l, "add(10, 20)");
    printf("result: %lf\n", result);

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }
    LuaCEmbed_free(l);
    return 0;
}

Output:

result: 30.000000

Available return functions:

Function Returns
LuaCEmbed_send_double(double) decimal number
LuaCEmbed_send_long(long) integer number
LuaCEmbed_send_str(char*) text
LuaCEmbed_send_bool(bool) true/false
LuaCEmbed_send_table(LuaCEmbedTable*) table
LuaCEmbed_send_error(char*) propagates an error to Lua
return NULL nil (no return)

Tables

Tables are the main structured data type of Lua — they work as arrays, dictionaries, and objects all at once.

Reading properties of a received table

#include "LuaCEmbedOne.c"

LuaCEmbedResponse *show_table(LuaCEmbed *args) {
    // Reads the first argument as a table
    LuaCEmbedTable *t = LuaCEmbed_get_arg_table(args, 0);
    if (LuaCEmbed_has_errors(args)) {
        return LuaCEmbed_send_error(LuaCEmbed_get_error_message(args));
    }

    // Reads the properties by name
    char *name = LuaCembedTable_get_string_prop(t, "name");
    long age   = LuaCembedTable_get_long_prop(t, "age");

    printf("name: %s\n", name);
    printf("age: %ld\n", age);
    return NULL;
}

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_add_callback(l, "show_table", show_table);

    LuaCEmbed_evaluate(l, "show_table({ name = 'Mateus', age = 27 })");

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }
    LuaCEmbed_free(l);
    return 0;
}

Output:

name: Mateus
age: 27

Size of a table

#include "LuaCEmbedOne.c"

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_evaluate(l, "r = {1, 2, 3}");
    long size = LuaCEmbed_get_evaluation_table_size(l, "r");
    printf("size: %ld\n", size);  // 3
    LuaCEmbed_free(l);
    return 0;
}

Output:

size: 3

Iterating over the elements of a table

#include "LuaCEmbedOne.c"

LuaCEmbedResponse *show_table(LuaCEmbed *args) {
    LuaCEmbedTable *t = LuaCEmbed_get_arg_table(args, 0);
    if (LuaCEmbed_has_errors(args)) {
        return LuaCEmbed_send_error(LuaCEmbed_get_error_message(args));
    }

    long size = LuaCEmbedTable_get_listable_size(t);
    for (int i = 0; i < size; i++) {
        printf("index: %d\n", i);

        // Checks if the element has an associated key (string)
        const char *key = "no key";
        if (LuaCembedTable_has_key_at_index(t, i)) {
            key = LuaCembedTable_get_key_by_index(t, i);
        }
        printf("key: %s\n", key);

        // Reads the type and the value of the element
        int type = LuaCEmbedTable_get_type_by_index(t, i);
        printf("type: %s\n", LuaCembed_convert_arg_code(type));

        if (type == LUA_CEMBED_NUMBER) {
            printf("value: %lf\n", LuaCEmbedTable_get_double_by_index(t, i));
        } else if (type == LUA_CEMBED_STRING) {
            printf("value: %s\n", LuaCEmbedTable_get_string_by_index(t, i));
        } else if (type == LUA_CEMBED_BOOL) {
            printf("value: %d\n", LuaCEmbedTable_get_bool_by_index(t, i));
        }
        printf("--\n");
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_add_callback(l, "show_table", show_table);

    LuaCEmbed_evaluate(l, "show_table({10, 'hello', true, x=42})");

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }
    LuaCEmbed_free(l);
    return 0;
}

Reading sub-tables (tables inside tables)

#include "LuaCEmbedOne.c"

LuaCEmbedResponse *show_people(LuaCEmbed *args) {
    LuaCEmbedTable *t = LuaCEmbed_get_arg_table(args, 0);
    if (LuaCEmbed_has_errors(args)) {
        return LuaCEmbed_send_error(LuaCEmbed_get_error_message(args));
    }

    long size = LuaCEmbedTable_get_listable_size(t);
    for (int i = 0; i < size; i++) {
        // Each element is a table with "name" and "age"
        LuaCEmbedTable *item = LuaCEmbedTable_get_sub_table_by_index(t, i);
        char *name = LuaCembedTable_get_string_prop(item, "name");
        long age   = LuaCembedTable_get_long_prop(item, "age");
        printf("name: %s, age: %ld\n", name, age);
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_add_callback(l, "show_people", show_people);

    LuaCEmbed_evaluate(l, "show_people({{name='Alice', age=30}, {name='Bob', age=25}})");

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }
    LuaCEmbed_free(l);
    return 0;
}

Output:

name: Alice, age: 30
name: Bob, age: 25

Creating and returning a table from C to Lua

#include "LuaCEmbedOne.c"

LuaCEmbedResponse *create_table(LuaCEmbed *args) {
    // Creates a new empty table
    LuaCEmbedTable *t = LuaCembed_new_anonymous_table(args);

    // Sets the properties
    LuaCEmbedTable_set_string_prop(t, "name",    "Mateus");
    LuaCEmbedTable_set_long_prop  (t, "age",     27);
    LuaCEmbedTable_set_double_prop(t, "height",  1.82);
    LuaCEmbedTable_set_bool_prop  (t, "married", false);

    // Returns the table to Lua
    return LuaCEmbed_send_table(t);
}

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_add_callback(l, "create_table", create_table);
    LuaCEmbed_load_native_libs(l);

    LuaCEmbed_evaluate(l, "p = create_table(); print(p.name, p.age, p.height, p.married)");

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }
    LuaCEmbed_free(l);
    return 0;
}

Output:

Mateus  27  1.82    false

Adding methods to a table

You can create tables that behave like objects, with methods callable by Lua:

#include "LuaCEmbedOne.c"

// The method receives the table itself as 'self' (like in object-oriented programming)
LuaCEmbedResponse *describe(LuaCEmbedTable *self, LuaCEmbed *args) {
    char *name = LuaCembedTable_get_string_prop(self, "name");
    long age   = LuaCembedTable_get_long_prop(self, "age");
    printf("name: %s, age: %ld\n", name, age);
    return NULL;
}

LuaCEmbedResponse *create_table(LuaCEmbed *args) {
    LuaCEmbedTable *t = LuaCembed_new_anonymous_table(args);
    LuaCEmbedTable_set_string_prop(t, "name", "Mateus");
    LuaCEmbedTable_set_long_prop  (t, "age",  27);
    LuaCEmbedTable_set_method     (t, "describe", describe);
    return LuaCEmbed_send_table(t);
}

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_add_callback(l, "create_table", create_table);

    LuaCEmbed_evaluate(l, "local p = create_table(); p:describe()");

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }
    LuaCEmbed_free(l);
    return 0;
}

Output:

name: Mateus, age: 27

Metamethods

Metamethods allow intercepting special Lua operations, such as property access (__index) and destruction by the garbage collector (__gc):

#include "LuaCEmbedOne.c"

// Called when Lua accesses a property of the table
LuaCEmbedResponse *index_callback(LuaCEmbedTable *self, LuaCEmbed *args) {
    int value_type = LuaCEmbed_get_arg_type(args, 1);
    if (value_type == LUA_CEMBED_STRING) {
        printf("accessing index: %s\n", LuaCEmbed_get_str_arg(args, 1));
    }
    return NULL;
}

// Called when Lua's garbage collector destroys the table
LuaCEmbedResponse *gc_callback(LuaCEmbedTable *self, LuaCEmbed *args) {
    printf("table destroyed by GC\n");
    return NULL;
}

LuaCEmbedResponse *create_table(LuaCEmbed *args) {
    LuaCEmbedTable *t = LuaCembed_new_anonymous_table(args);
    LuaCEmbedTable_set_method(t, "__index", index_callback);
    LuaCEmbedTable_set_method(t, "__gc",    gc_callback);
    return LuaCEmbed_send_table(t);
}

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();
    LuaCEmbed_add_callback(l, "create_table", create_table);

    LuaCEmbed_evaluate(l, "local t = create_table(); local x = t.foo");

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }
    LuaCEmbed_free(l);
    return 0;
}

Output:

accessing index: foo
table destroyed by GC

Creating a Library (.so/.dll)

Besides executing Lua from C, you can also do the reverse: compile C as a dynamic library and import it directly from Lua with require.

#include "LuaCEmbedOne.c"

LuaCEmbedResponse *add_cfunc(LuaCEmbed *args) {
    double a = LuaCEmbed_get_double_arg(args, 0);
    double b = LuaCEmbed_get_double_arg(args, 1);
    if (LuaCEmbed_has_errors(args)) {
        return LuaCEmbed_send_error(LuaCEmbed_get_error_message(args));
    }
    return LuaCEmbed_send_double(a + b);
}

LuaCEmbedResponse *sub_cfunc(LuaCEmbed *args) {
    double a = LuaCEmbed_get_double_arg(args, 0);
    double b = LuaCEmbed_get_double_arg(args, 1);
    if (LuaCEmbed_has_errors(args)) {
        return LuaCEmbed_send_error(LuaCEmbed_get_error_message(args));
    }
    return LuaCEmbed_send_double(a - b);
}

// The function name MUST be "luaopen_" + library_name
int luaopen_my_lib(lua_State *state) {
    LuaCEmbed *l = newLuaCEmbedLib(state);
    LuaCEmbed_add_callback(l, "add", add_cfunc);
    LuaCEmbed_add_callback(l, "sub", sub_cfunc);
    return LuaCembed_perform(l);
}

Compile as a shared library:

gcc -shared -fPIC -o my_lib.so main.c

Use in Lua:

local lib = require("my_lib")

print(lib.add(10, 20))  -- 30.0
print(lib.sub(20, 5))   -- 15.0

Static properties in the library

You can also expose constants and fixed values:

int luaopen_my_lib(lua_State *state) {
    LuaCEmbed *l = newLuaCEmbedLib(state);
    LuaCEmbed_set_long_lib_prop  (l, "version", 1);
    LuaCEmbed_set_string_lib_prop(l, "author",  "Mateus");
    LuaCEmbed_set_double_lib_prop(l, "pi",      3.14);
    LuaCEmbed_set_bool_lib_prop  (l, "debug",   false);
    return LuaCembed_perform(l);
}

In Lua:

local lib = require("my_lib")
print(lib.version)  -- 1
print(lib.author)   -- Mateus

Security: Timeout and Memory Limit

When you execute code submitted by users, it’s important to protect your program against infinite loops and excessive memory usage.

Timeout

Interrupts execution after a number of seconds:

#include "LuaCEmbedOne.c"

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();

    LuaCEmbed_set_timeout(2);  // interrupts after 2 seconds

    LuaCEmbed_evaluate(l, "while true do end");  // infinite loop

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }

    // After a timeout, you can clear the error and continue using the instance
    LuaCEmbed_clear_errors(l);
    LuaCEmbed_evaluate(l, "r = 'still works'");
    printf("%s\n", LuaCEmbed_get_evaluation_string(l, "r"));

    LuaCEmbed_free(l);
    return 0;
}

Output:

error: timeout error
still works

Memory Limit

Prevents Lua code from consuming memory indefinitely (default: 100 MB):

#include "LuaCEmbedOne.c"

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();

    LuaCEmbed_set_memory_limit(l, 1);  // limits to 1 MB

    // Code that tries to grow a string indefinitely
    LuaCEmbed_evaluate(l, "t = 'a'; while true do t = t .. t end");

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));
    }

    LuaCEmbed_free(l);
    return 0;
}

Output:

error: not enough memory

Error Handling

Always check for errors after evaluations. If you don’t check, silent errors could cause unexpected behaviors.

#include "LuaCEmbedOne.c"

LuaCEmbedResponse *my_function(LuaCEmbed *args) {
    double val = LuaCEmbed_get_double_arg(args, 0);
    if (LuaCEmbed_has_errors(args)) {
        // Propagates the error to Lua instead of continuing with an invalid value
        return LuaCEmbed_send_error(LuaCEmbed_get_error_message(args));
    }
    return LuaCEmbed_send_double(val * 2);
}

int main(int argc, char *argv[]) {
    LuaCEmbed *l = newLuaCEmbedEvaluation();

    LuaCEmbed_evaluate(l, "invalid code %%%");

    if (LuaCEmbed_has_errors(l)) {
        printf("error: %s\n", LuaCEmbed_get_error_message(l));

        // Clears the error to continue using the same instance
        LuaCEmbed_clear_errors(l);
    }

    LuaCEmbed_add_callback(l, "my_function", my_function);
    double result = LuaCEmbed_get_evaluation_double(l, "my_function(21)");
    printf("result: %lf\n", result);

    LuaCEmbed_free(l);
    return 0;
}

Output:

error: [string "invalid code %%%"]:1: unexpected symbol near '%'
result: 42.000000

Quick Reference

Function Description
newLuaCEmbedEvaluation() Creates a new instance
LuaCEmbed_evaluate(l, code) Executes Lua code (string)
LuaCEmbed_evaluete_file(l, path) Executes a Lua file
LuaCEmbed_load_native_libs(l) Enables native libs (print, io, os…)
LuaCEmbed_add_callback(l, name, fn) Registers a C function in Lua
LuaCEmbed_get_evaluation_string(l, expr) Reads result as string
LuaCEmbed_get_evaluation_long(l, expr) Reads result as integer
LuaCEmbed_get_evaluation_double(l, expr) Reads result as decimal
LuaCEmbed_get_evaluation_bool(l, expr) Reads result as boolean
LuaCEmbed_get_evaluation_type(l, expr) Returns the type of the expression
LuaCEmbed_get_evaluation_table_size(l, expr) Returns the size of a table
LuaCEmbed_has_errors(l) Checks if there was an error
LuaCEmbed_get_error_message(l) Gets the error message
LuaCEmbed_clear_errors(l) Clears the error state
LuaCEmbed_set_timeout(seconds) Sets execution timeout
LuaCEmbed_set_memory_limit(l, mb) Sets memory limit in MB
LuaCEmbed_free(l) Frees the instance