I’ve written C extensions for Python, but I’ve never done it for Lua before. The basic idea is that you write a function that takes the lua_State *L pointer, and returns an int, referring to the number of values it’s pushing back onto Lua’s stack. Then you add some code to make your C code importable into Lua as a module. Finally, you compile your C code into a shared object and make it available to your Lua code, either in the same working directory, or on a path that Lua knows about.

C Code

// myclib.c

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

static int add_numbers(lua_State *L) {
    /* Check and get the two numbers from the stack */
    double num1 = luaL_checknumber(L, 1);
    double num2 = luaL_checknumber(L, 2);

    /* Perform the addition */
    double result = num1 + num2;

    /* Push the result onto the stack */
    lua_pushnumber(L, result);

    /* Return the number of results (1 in this case) */
    return 1;
}

/* Define a structure to map function names in Lua to your C functions */
static const struct luaL_Reg mylib_funcs[] = {
    {"add", add_numbers},
    {NULL, NULL} /* Sentinel to indicate the end of the array */
};

/* The module entry point function */
int luaopen_myclib(lua_State *L) {
    /* Create a new table and register the functions */
#if LUA_VERSION_NUM == 501
    luaL_register(L, "myclib", mylib_funcs); // For Lua 5.1
#else
    luaL_newlib(L, mylib_funcs); // For Lua 5.2 and later
#endif
    return 1; /* Return the module table */
}

I compiled the C code above with this command,

gcc -shared -fPIC -I/opt/homebrew/Cellar/lua/5.4.7/include/lua -L/opt/homebrew/Cellar/lua/5.4.7/lib -llua5.4 -o myclib.so myclib.c

The -L flag specifies the location of an external library, and the -l flag specifies the name of the shared library you want to link. These flags work together with the -fPIC flag, which generates position-independent code, so that the shared library can be loaded from any address, rather than a static one. The -I flag tells gcc where to look for header files, in this case, the Lua files we used so that the C code could interact with the Lua interpreter.

Lua Code

This code allows us to pull the add function from our C code, myclib.c, via the shared object, myclib.so, and call it from out Lua script.

-- script.lua

-- Require the module (assuming the shared library is named myclib.so/myclib.dll)
local mylib = require("myclib")

-- Call the C function
local sum = mylib.add(3, 8)
print("The sum is:", sum) -- 11

Bonus: Fennel

Fennel is a LISP that compiles to Lua code, so it works almost exactly the same as the Lua code above.

;; script.fnl

;; Load the C module named "myclib"
(local myclib (require "myclib"))

;; Access the "add" function from the mylib module
;; and call it with arguments 10 and 20
(local sum ((. myclib :add) 3 8))

;; Print the result
(print "The sum from C is:" sum)

Here, ((. myclib :add) 3 8) is like myclib.add(3, 8) in Lua, we’re accessing the add method on the myclib table.