Skip to content
AgileLogics
7 min read

Embedding Lua in C

Prerequisites: Environments and sandboxing

Lua was designed to be embedded. A C (or C++) program creates a Lua state, runs scripts, and exchanges values through a virtual stack.

Running a script from C

// host.c — compile with: cc host.c -llua -lm -o host
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>

// A C function callable from Lua: add(a, b)
static int l_add(lua_State *L) {
    lua_Number a = luaL_checknumber(L, 1);   // argument 1
    lua_Number b = luaL_checknumber(L, 2);   // argument 2
    lua_pushnumber(L, a + b);                // push the result
    return 1;                                // number of results
}

int main(void) {
    lua_State *L = luaL_newstate();          // create a Lua state
    luaL_openlibs(L);                        // load the standard libraries

    lua_register(L, "add", l_add);           // expose add() to scripts

    if (luaL_dofile(L, "script.lua") != LUA_OK) {
        fprintf(stderr, "error: %s\n", lua_tostring(L, -1));
        return 1;
    }

    // Call the Lua function greet("Ada") defined in script.lua
    lua_getglobal(L, "greet");
    lua_pushstring(L, "Ada");
    if (lua_pcall(L, 1, 1, 0) == LUA_OK) {
        printf("Lua returned: %s\n", lua_tostring(L, -1));
        lua_pop(L, 1);
    }

    lua_close(L);
    return 0;
}
-- illustrative
-- script.lua
print("2 + 3 =", add(2, 3))

function greet(name)
  return "Hello, " .. name .. "!"
end
$ ./host
2 + 3 =	5.0
Lua returned: Hello, Ada!

The stack

Every value passed between C and Lua goes through the stack:

  • lua_push* functions push values (lua_pushnumber, lua_pushstring, lua_newtable).
  • lua_to* and luaL_check* read them by index — 1 is the bottom (first argument), -1 the top.
  • A C function receives its arguments on the stack and returns the number of results it pushed.
  • lua_pcall calls a Lua function in protected mode — never let Lua errors unwind through C++ code without it.

Exposing objects: userdata

To give scripts a C object (a sprite, a file, a socket), create a full userdata with lua_newuserdatauv, give it a metatable with methods (luaL_newmetatable / luaL_setfuncs), and a __gc metamethod to free it. Scripts then call sprite:move(10, 0) as if it were a Lua object.

Binding libraries

Writing bindings by hand is educational but tedious. sol2 (C++), mlua (Rust), NLua / MoonSharp (.NET), and gopher-lua (Go) generate bindings or implement Lua directly in those languages.