Lua 与 C 互调
更新时间:2026-08-29。本文回答:Lua 和 C 怎么交换数据?为什么所有互调都经过"栈"?怎么把 C 结构体暴露给 Lua?
一、Lua 栈:唯一的桥梁
Lua 与 C 之间不直接传数据结构,而是通过一份虚拟栈交换值。C 侧用 lua_push* 压栈、lua_to* 取栈,Lua 侧像调普通函数一样调用。栈是两边唯一的契约边界。
c
#include <lua.h>
#include <lauxlib.h>
// 从 C 调用 Lua 函数
double call_lua(lua_State *L, double x) {
lua_getglobal(L, "square"); // 把 Lua 函数压栈
lua_pushnumber(L, x); // 参数压栈
lua_call(L, 1, 1); // 调用:1 参 1 返
double r = lua_tonumber(L, -1);
lua_pop(L, 1); // 弹出返回值
return r;
}核心结论:栈把"两种内存模型(C 的 struct/堆 与 Lua 的 GC 对象)"解耦——C 永远通过索引存取栈上值,不直接碰 Lua 内部。
二、从 Lua 调 C:lua_CFunction
把 C 函数注册进 Lua,签名固定为 int (*)(lua_State *)(返回值个数):
c
static int l_add(lua_State *L) {
double a = luaL_checknumber(L, 1); // 取第 1 个参数并校验
double b = luaL_checknumber(L, 2);
lua_pushnumber(L, a + b); // 结果压栈
return 1; // 告诉 Lua 返回 1 个值
}
// 注册
lua_pushcfunction(L, l_add);
lua_setglobal(L, "cadd");lua
print(cadd(2, 3)) -- 从 Lua 调到 C,输出 5luaL_checknumber既取值又做类型校验,错类型会抛 Lua 错误(和 Rust Result 的"显式错误"异曲同工)。- 这正是"热点下沉 C"的标准路径:Lua 写编排逻辑,C 写计算热点。
三、把 C 结构体暴露给 Lua:userdata + 元表
Lua 不能直接持有 C 指针,要用 userdata(一块 C 管理的内存),再绑元表提供方法:
c
typedef struct { double x, y; } Vec;
static int l_vec_add(lua_State *L) {
Vec *a = luaL_checkudata(L, 1, "Vec");
Vec *b = luaL_checkudata(L, 2, "Vec");
Vec *r = lua_newuserdata(L, sizeof(Vec));
r->x = a->x + b->x; r->y = a->y + b->y;
return 1;
}lua_newuserdata在 Lua 堆上分配一块内存,由 Lua GC 管理生命周期(但里面的 C 资源需__gc元方法释放)。- 用元表把
l_vec_add挂成方法,Lua 侧就能v1:add(v2)这样面向对象地用。
四、与本站 C 主线的衔接
| Lua/C 机制 | 本站对应 | 衔接文档 |
|---|---|---|
| 栈作为边界 | L5 ELF/ABI 调用约定 | 跨语言数据传递的契约 |
| userdata 内存 | 内存布局 | C 结构体如何存活于 Lua |
__gc 释放 | C 手动内存管理 | 资源释放责任 |
| 错误传播 | Rust Result/panic | 跨边界错误传递 |
五、常见坑
- 栈平衡:C 函数返回前必须保证栈上只留"返回值个数"个元素,多压少弹会导致栈错位。
- userdata 需
__gc:C 分配的资源(文件句柄、堆内存)要在元表__gc里释放,否则泄漏。 - 错误处理用
luaL_error:在 C 里抛 Lua 错误要用它,不要用 C 的longjmp直接跳,会破坏 Lua 栈。
一句话总结
Lua 与 C 互调 = 一切经过虚拟栈 + lua_CFunction 把 C 函数暴露给 Lua + userdata+元表把 C 结构体封装成 Lua 对象;栈是两种内存模型之间唯一、安全的桥梁。
继续:游戏脚本与配置场景。