Nanfeng

Notes on software development, code, and curious ideas

An Overview of the Lua C API

Lua is both embeddable and extensible. A C application can run Lua as a library, while Lua code can call functions implemented in C. Both directions use the same C API and the same central abstraction: lua_State.

Creating a state and running code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

int main(void) {
lua_State *L = luaL_newstate();
if (!L) return 1;
luaL_openlibs(L);

if (luaL_dostring(L, "return 6 * 7") != LUA_OK) {
fprintf(stderr, "%s\n", lua_tostring(L, -1));
lua_close(L);
return 1;
}

printf("%lld\n", (long long)lua_tointeger(L, -1));
lua_close(L);
}

Only open the standard libraries a host actually intends to expose, especially in restricted environments.

Understanding the stack

Positive indices count from the bottom, starting at 1. Negative indices count backward from the top, where -1 is the top value. Common operations include:

1
2
3
4
lua_pushinteger(L, 42);
lua_pushstring(L, "answer");
int top = lua_gettop(L);
lua_settop(L, 0); /* clear the stack */

Query functions such as lua_type, lua_isnumber, and lua_tostring inspect values. Conversion functions do not all report errors, so library code should use the validating luaL_check* helpers.

Use lua_absindex before pushing more values if a relative index must remain stable. Stack manipulation functions include lua_pushvalue, lua_rotate, lua_copy, and lua_remove.

Calling Lua safely

Push the function first, followed by its arguments:

1
2
3
4
5
6
lua_getglobal(L, "transform");
lua_pushinteger(L, 21);
if (lua_pcall(L, 1, 1, 0) != LUA_OK) {
fprintf(stderr, "%s\n", lua_tostring(L, -1));
lua_pop(L, 1);
}

lua_call propagates an error using Lua’s long-jump mechanism. lua_pcall returns an error code, which is usually appropriate at an application boundary.

Errors and memory

C library functions can report argument errors through luaL_argerror or luaL_error. Because Lua errors may bypass ordinary C control flow, do not rely on cleanup code after an unprotected API call; manage external resources carefully.

Lua allocates through a configurable allocator supplied to lua_newstate. Objects managed by Lua must remain reachable from the Lua side while C uses them. For arbitrary C memory or operating-system handles, use full userdata with a suitable __gc metamethod and an explicit close operation where deterministic cleanup matters.

The API favors small primitives over large convenience wrappers. Reliable integrations therefore keep stack effects local, check capacity when necessary, define clear ownership, and test against the exact Lua version used in production.

+