强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

17 - C API / Lua C API

C API / Lua C API

Lua 的 C API 设计极其优雅——通过一个虚拟栈(virtual stack)与 Lua 交互。所有操作都是压栈/出栈操作。

Lua’s C API is elegantly designed — all interaction happens through a virtual stack. Every operation is push/pop.


🟢 基础 / Basics

1. 嵌入 Lua 解释器 / Embedding Lua

// hello.c — 在 C 中嵌入 Lua
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>

int main() {
    // 创建 Lua 状态机
    lua_State *L = luaL_newstate();
    
    // 打开标准库
    luaL_openlibs(L);
    
    // 执行 Lua 代码
    luaL_dostring(L, "print('Hello from Lua!')");
    
    // 执行 Lua 文件
    luaL_dofile(L, "script.lua");
    
    // 关闭
    lua_close(L);
    return 0;
}

编译:

gcc hello.c -o hello -llua -lm -ldl
# 或指定路径:
gcc hello.c -o hello -I/usr/local/include -L/usr/local/lib -llua

2. Lua 栈操作 / Stack Operations

Lua 栈示意图 / Stack illustration:

       ┌─────────┐  ← 栈顶 (index -1)
       │ "world" │
       ├─────────┤  ← index -2
       │  42     │
       ├─────────┤  ← index 3 (1-based from bottom)
       │  true   │
       ├─────────┤  ← index 2
       │  "hello"│
       ├─────────┤  ← index 1 (栈底)
       │  3.14   │
       └─────────┘
// 压入不同类型的值
lua_pushinteger(L, 42);        // 整数
lua_pushnumber(L, 3.14);       // 浮点数
lua_pushstring(L, "hello");    // 字符串
lua_pushboolean(L, 1);         // 布尔
lua_pushnil(L);                // nil
lua_pushcfunction(L, myFunc);  // C 函数

// 读取栈上的值
lua_Integer i = lua_tointeger(L, 1);     // 位置 1 的整数
lua_Number n = lua_tonumber(L, 2);       // 位置 2 的数字
const char *s = lua_tostring(L, 3);      // 位置 3 的字符串
int b = lua_toboolean(L, 4);             // 位置 4 的布尔

// 类型检查
int type = lua_type(L, 1);
if (type == LUA_TNUMBER) { /* 是数字 */ }
// 或使用辅助函数:
lua_isstring(L, 1);    // 是否可以转为字符串
lua_isnumber(L, 1);    // 是否可以转为数字
lua_istable(L, 1);     // 是否是表
lua_isnil(L, 1);       // 是否是 nil

// 获取栈大小
int top = lua_gettop(L);    // 栈中元素数量

// 弹出元素
lua_pop(L, 1);         // 弹出 1 个元素
lua_settop(L, 0);      // 清空栈

// 推入全局变量到栈
lua_getglobal(L, "print");   // 把 _G["print"] 压栈

// 设置全局变量
lua_pushstring(L, "value");
lua_setglobal(L, "myVar");   // _G["myVar"] = "value"

🟡 进阶 / Intermediate

1. C 函数注册到 Lua / Registering C Functions

// C 函数的签名必须是:
// int func(lua_State *L)
// 返回值:返回值的数量(压入栈的值的个数)

// 示例:一个加法函数
static int l_add(lua_State *L) {
    // 检查参数
    luaL_checktype(L, 1, LUA_TNUMBER);
    luaL_checktype(L, 2, LUA_TNUMBER);
    
    // 获取参数
    lua_Number a = luaL_checknumber(L, 1);
    lua_Number b = luaL_checknumber(L, 2);
    
    // 压入结果
    lua_pushnumber(L, a + b);
    
    return 1;    // 返回 1 个值
}

// 注册到 Lua
static const struct luaL_Reg mylib[] = {
    {"add", l_add},
    {NULL, NULL}    // 哨兵
};

int luaopen_mylib(lua_State *L) {
    luaL_newlib(L, mylib);
    return 1;
}

// 或者直接设置为全局函数
lua_pushcfunction(L, l_add);
lua_setglobal(L, "add");
// Lua 中就可以用 add(3, 5) 了

2. 操作 Lua 表 / Operating on Tables

// 创建表
lua_newtable(L);                    // 压入一个空表

// 设置表字段:t["key"] = value
lua_pushstring(L, "name");
lua_pushstring(L, "Alice");
lua_settable(L, -3);               // t["name"] = "Alice"(-3 是表的位置)

// 快捷方式:设置字符串键
lua_pushstring(L, "Bob");
lua_setfield(L, -2, "name");       // t["name"] = "Bob"

// 读取表字段:value = t["key"]
lua_getfield(L, -1, "name");       // 压入 t["name"]
const char *name = lua_tostring(L, -1);
lua_pop(L, 1);

// 数组式操作:t[1] = value
lua_pushinteger(L, 42);
lua_seti(L, -2, 1);               // t[1] = 42

// 读取数组元素
lua_geti(L, -1, 1);               // 压入 t[1]
lua_Integer val = lua_tointeger(L, -1);
lua_pop(L, 1);

// 遍历表
lua_pushnil(L);                    // 第一个键
while (lua_next(L, -2) != 0) {    // -2 是表的位置
    // key 在 -2, value 在 -1
    printf("%s = %s\n",
        lua_tostring(L, -2),
        lua_tostring(L, -1));
    lua_pop(L, 1);                 // 弹出 value,保留 key 用于下一次迭代
}

3. 错误处理 / Error Handling

// luaL_error 抛出 Lua 错误
static int l_divide(lua_State *L) {
    lua_Number a = luaL_checknumber(L, 1);
    lua_Number b = luaL_checknumber(L, 2);
    
    if (b == 0) {
        return luaL_error(L, "division by zero");
    }
    
    lua_pushnumber(L, a / b);
    return 1;
}

// luaL_argerror 参数错误
static int l_func(lua_State *L) {
    if (!lua_isnumber(L, 1)) {
        return luaL_argerror(L, 1, "number expected");
    }
    return 0;
}

// 使用保护调用(pcall 方式)
int status = luaL_dostring(L, "error('oops')");
if (status != LUA_OK) {
    const char *msg = lua_tostring(L, -1);
    printf("Error: %s\n", msg);
    lua_pop(L, 1);
}

🔴 高级 / Advanced

1. Userdata / 自定义数据

// userdata 让 Lua 持有 C 数据

typedef struct {
    double x;
    double y;
} Vector;

// 创建 userdata
static int l_vec_new(lua_State *L) {
    lua_Number x = luaL_checknumber(L, 1);
    lua_Number y = luaL_checknumber(L, 2);
    
    // 分配 userdata(Lua 管理其内存)
    Vector *v = (Vector *)lua_newuserdata(L, sizeof(Vector));
    v->x = x;
    v->y = y;
    
    // 设置 metatable(可选,用于方法调用)
    luaL_setmetatable(L, "Vector");
    
    return 1;    // 返回 userdata
}

// userdata 的方法
static int l_vec_len(lua_State *L) {
    Vector *v = (Vector *)luaL_checkudata(L, 1, "Vector");
    lua_pushnumber(L, sqrt(v->x * v->x + v->y * v->y));
    return 1;
}

static int l_vec_tostring(lua_State *L) {
    Vector *v = (Vector *)luaL_checkudata(L, 1, "Vector");
    lua_pushfstring(L, "(%g, %g)", v->x, v->y);
    return 1;
}

// 注册库
static const struct luaL_Reg vec_funcs[] = {
    {"new", l_vec_new},
    {NULL, NULL}
};

static const struct luaL_Reg vec_methods[] = {
    {"len", l_vec_len},
    {"__tostring", l_vec_tostring},
    {NULL, NULL}
};

int luaopen_Vector(lua_State *L) {
    // 创建 metatable
    luaL_newmetatable(L, "Vector");
    
    // 设置 __index 指向自己(允许方法调用)
    lua_pushvalue(L, -1);
    lua_setfield(L, -2, "__index");
    
    // 注册方法到 metatable
    luaL_setfuncs(L, vec_methods, 0);
    lua_pop(L, 1);
    
    // 创建库表
    luaL_newlib(L, vec_funcs);
    return 1;
}
-- Lua 端使用
local Vector = require("Vector")
local v = Vector.new(3, 4)
print(v:len())        -- 5
print(tostring(v))    -- (3, 4)

2. 注册表与引用 / Registry & References

// 注册表是全局的键值存储,C 端可以保存 Lua 值的引用
// Registry is a global key-value store for C to hold references to Lua values

// 创建一个引用
int ref = luaL_ref(L, LUA_REGISTRYINDEX);  // 弹出栈顶值,保存到注册表

// 获取引用的值
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);

// 释放引用
luaL_unref(L, LUA_REGISTRYINDEX, ref);

// 使用场景:C 端保存 Lua 回调函数
static int callback_ref = LUA_NOREF;

static int l_set_callback(lua_State *L) {
    luaL_checktype(L, 1, LUA_TFUNCTION);
    callback_ref = luaL_ref(L, LUA_REGISTRYINDEX);
    return 0;
}

static void trigger_callback(lua_State *L) {
    lua_rawgeti(L, LUA_REGISTRYINDEX, callback_ref);
    lua_pcall(L, 0, 0, 0);
}

3. 协程与 C / Coroutines from C

// 创建新协程
lua_State *co = lua_newthread(L);

// 向协程中放入函数
lua_getglobal(co, "myCoroutineFunc");

// 恢复协程
int nres;
int status = lua_resume(co, L, 0, &nres);
if (status == LUA_YIELD) {
    // 协程 yield 了
    printf("Yielded %d values\n", nres);
} else if (status == LUA_OK) {
    // 协程正常结束
    printf("Finished with %d values\n", nres);
} else {
    // 错误
    printf("Error: %s\n", lua_tostring(co, -1));
}

小结 / Summary

层级你需要知道的 / What You Need to Know
🟢 基础luaL_newstate、luaL_dostring、栈操作(push/pop/get/set)
🟡 进阶注册 C 函数、操作表(getfield/setfield)、错误处理
🔴 高级userdata、metatable 绑定、注册表引用、协程 API

下一章:实战案例 / Real-World Projects