name: Lua C 集成 user-invocable: false description: 当使用Lua C API扩展Lua与原生代码时使用,包括栈操作、从Lua调用C、从C调用Lua、创建C模块、用户数据类型、C中的元表和性能优化技术。 allowed-tools: []
Lua C 集成
介绍
Lua的C API使得与C代码无缝集成,允许开发者使用高性能原生功能扩展Lua,或将Lua嵌入为C应用程序中的脚本引擎。这种双向集成使Lua非常适合需要脚本功能的性能关键应用。
C API通过虚拟栈在Lua和C之间传递值,提供操作Lua值、调用函数和管理内存的函数。理解栈操作和Lua的数据模型对于安全、高效的C集成至关重要。
本技能涵盖Lua栈、从Lua调用C、从C调用Lua、创建C模块、用户数据和元表、错误处理、内存管理和性能优化模式。
Lua栈基础
Lua-C API使用虚拟栈进行所有Lua和C之间的值交换,需要理解推入/弹出操作。
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
// 基本栈操作
void demonstrate_stack(lua_State *L) {
// 将值推入栈
lua_pushinteger(L, 42); // 栈: 42
lua_pushnumber(L, 3.14); // 栈: 42 | 3.14
lua_pushstring(L, "hello"); // 栈: 42 | 3.14 | "hello"
lua_pushboolean(L, 1); // 栈: 42 | 3.14 | "hello" | true
lua_pushnil(L); // 栈: 42 | 3.14 | "hello" | true | nil
// 获取栈大小
int top = lua_gettop(L); // 返回 5
// 通过索引访问值(基于1)
lua_Integer i = lua_tointeger(L, 1); // 42(从底部索引)
lua_Number n = lua_tonumber(L, 2); // 3.14
const char *s = lua_tostring(L, 3); // "hello"
int b = lua_toboolean(L, 4); // 1(true)
// 负索引(从顶部)
lua_Number n2 = lua_tonumber(L, -4); // 3.14(从顶部第4个)
const char *s2 = lua_tostring(L, -3); // "hello"(从顶部第3个)
// 类型检查
if (lua_isnumber(L, 1)) {
// 处理数字
}
if (lua_isstring(L, 3)) {
// 处理字符串
}
// 移除元素
lua_pop(L, 1); // 移除顶部元素(nil)
lua_remove(L, 2); // 移除索引2处的元素(3.14)
// 替换元素
lua_pushstring(L, "world");
lua_replace(L, 3); // 用"world"替换索引3
// 清空栈
lua_settop(L, 0); // 清空栈
}
// 栈操作模式
void stack_patterns(lua_State *L) {
// 在特定位置插入
lua_pushstring(L, "new value");
lua_insert(L, 1); // 插入到底部
// 复制元素
lua_pushvalue(L, 1); // 复制索引1处的元素
// 旋转元素
lua_rotate(L, 1, 2); // 从索引1开始旋转2个元素
// 检查栈空间
if (!lua_checkstack(L, 100)) {
// 无法分配栈空间
}
// 绝对索引(不随栈修改改变)
int abs_idx = lua_absindex(L, -1);
}
// 类型检查辅助函数
int check_arguments(lua_State *L) {
int argc = lua_gettop(L);
if (argc < 2) {
return luaL_error(L, "期望至少2个参数");
}
if (!lua_isnumber(L, 1)) {
return luaL_error(L, "参数1必须是数字");
}
if (!lua_isstring(L, 2)) {
return luaL_error(L, "参数2必须是字符串");
}
return 0;
}
// 表操作
void table_operations(lua_State *L) {
// 创建表
lua_newtable(L); // 栈: {}
// 设置字段: table["key"] = "value"
lua_pushstring(L, "value");
lua_setfield(L, -2, "key");
// 获取字段: value = table["key"]
lua_getfield(L, -1, "key");
const char *value = lua_tostring(L, -1);
lua_pop(L, 1);
// 使用任意键设置
lua_pushstring(L, "key2");
lua_pushinteger(L, 42);
lua_settable(L, -3); // table[key2] = 42
// 数组样式: table[1] = "first"
lua_pushinteger(L, 1);
lua_pushstring(L, "first");
lua_settable(L, -3);
// Rawset/rawget(绕过元方法)
lua_pushstring(L, "rawkey");
lua_pushstring(L, "rawvalue");
lua_rawset(L, -3);
// 表长度
lua_len(L, -1);
lua_Integer len = lua_tointeger(L, -1);
lua_pop(L, 1);
}
// 全局变量
void global_operations(lua_State *L) {
// 设置全局: my_global = 42
lua_pushinteger(L, 42);
lua_setglobal(L, "my_global");
// 获取全局: value = my_global
lua_getglobal(L, "my_global");
lua_Integer value = lua_tointeger(L, -1);
lua_pop(L, 1);
}
掌握栈操作以实现高效的C-Lua值交换,并通过适当清理避免栈溢出。
从Lua调用C函数
C函数遵循特定签名和约定,以便从Lua脚本可调用。
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
// 可从Lua调用的基本C函数
// int my_function(lua_State *L)
// 返回推入栈的返回值数量
static int add(lua_State *L) {
// 获取参数
lua_Number a = luaL_checknumber(L, 1);
lua_Number b = luaL_checknumber(L, 2);
// 计算结果
lua_Number result = a + b;
// 推送结果
lua_pushnumber(L, result);
// 返回结果数量
return 1;
}
// 多个返回值
static int divide_with_remainder(lua_State *L) {
lua_Integer a = luaL_checkinteger(L, 1);
lua_Integer b = luaL_checkinteger(L, 2);
if (b == 0) {
return luaL_error(L, "除以零");
}
lua_pushinteger(L, a / b); // 商
lua_pushinteger(L, a % b); // 余数
return 2; // 返回2个值
}
// 带有默认值的可选参数
static int greet(lua_State *L) {
const char *name = luaL_optstring(L, 1, "World");
lua_pushfstring(L, "Hello, %s!", name);
return 1;
}
// 表作为参数
static int sum_table(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
lua_Number sum = 0;
lua_Integer len = luaL_len(L, 1);
for (lua_Integer i = 1; i <= len; i++) {
lua_geti(L, 1, i); // 获取 table[i]
sum += lua_tonumber(L, -1);
lua_pop(L, 1);
}
lua_pushnumber(L, sum);
return 1;
}
// 返回一个表
static int create_point(lua_State *L) {
lua_Number x = luaL_checknumber(L, 1);
lua_Number y = luaL_checknumber(L, 2);
lua_newtable(L);
lua_pushnumber(L, x);
lua_setfield(L, -2, "x");
lua_pushnumber(L, y);
lua_setfield(L, -2, "y");
return 1;
}
// 可变参数
static int print_all(lua_State *L) {
int n = lua_gettop(L); // 参数数量
for (int i = 1; i <= n; i++) {
const char *str = luaL_tolstring(L, i, NULL);
printf("%s
", str);
lua_pop(L, 1); // 弹出 luaL_tolstring 的字符串
}
return 0;
}
// 带有回调的函数
static int each(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
luaL_checktype(L, 2, LUA_TFUNCTION);
lua_Integer len = luaL_len(L, 1);
for (lua_Integer i = 1; i <= len; i++) {
lua_pushvalue(L, 2); // 推送函数
lua_geti(L, 1, i); // 推送 table[i]
lua_pushinteger(L, i); // 推送索引
// 用2个参数调用函数
if (lua_pcall(L, 2, 0, 0) != LUA_OK) {
return lua_error(L);
}
}
return 0;
}
// 注册函数
static const luaL_Reg mylib[] = {
{"add", add},
{"divide_with_remainder", divide_with_remainder},
{"greet", greet},
{"sum_table", sum_table},
{"create_point", create_point},
{"print_all", print_all},
{"each", each},
{NULL, NULL} // 哨兵
};
// 库初始化
int luaopen_mylib(lua_State *L) {
luaL_newlib(L, mylib);
return 1;
}
// 替代注册
void register_functions(lua_State *L) {
lua_register(L, "add", add);
lua_register(L, "greet", greet);
}
C函数必须遵循Lua的调用约定并妥善管理栈,以实现可靠的集成。
从C调用Lua
C代码可以加载Lua脚本、调用Lua函数并访问Lua全局变量。
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
// 执行Lua脚本
void execute_script(const char *filename) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
if (luaL_dofile(L, filename) != LUA_OK) {
fprintf(stderr, "错误: %s
", lua_tostring(L, -1));
lua_close(L);
return;
}
lua_close(L);
}
// 执行Lua字符串
void execute_string(const char *code) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
if (luaL_dostring(L, code) != LUA_OK) {
fprintf(stderr, "错误: %s
", lua_tostring(L, -1));
}
lua_close(L);
}
// 调用Lua函数
void call_lua_function(lua_State *L, const char *func_name, int arg) {
lua_getglobal(L, func_name); // 获取函数
if (!lua_isfunction(L, -1)) {
fprintf(stderr, "%s 不是函数
", func_name);
lua_pop(L, 1);
return;
}
lua_pushinteger(L, arg); // 推送参数
if (lua_pcall(L, 1, 1, 0) != LUA_OK) {
fprintf(stderr, "调用 %s 错误: %s
",
func_name, lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
// 获取结果
lua_Integer result = lua_tointeger(L, -1);
printf("结果: %lld
", result);
lua_pop(L, 1);
}
// 使用多个参数和返回值调用
void call_with_multiple(lua_State *L) {
lua_getglobal(L, "divide");
lua_pushinteger(L, 10);
lua_pushinteger(L, 3);
// 用2个参数、2个返回值调用
if (lua_pcall(L, 2, 2, 0) != LUA_OK) {
fprintf(stderr, "错误: %s
", lua_tostring(L, -1));
return;
}
lua_Integer quotient = lua_tointeger(L, -2);
lua_Integer remainder = lua_tointeger(L, -1);
lua_pop(L, 2);
printf("商: %lld, 余数: %lld
", quotient, remainder);
}
// 带有错误处理器的保护调用
int error_handler(lua_State *L) {
const char *msg = lua_tostring(L, -1);
luaL_traceback(L, L, msg, 1);
return 1;
}
void safe_call(lua_State *L, const char *func_name) {
lua_pushcfunction(L, error_handler);
int errfunc_idx = lua_gettop(L);
lua_getglobal(L, func_name);
lua_pushinteger(L, 42);
if (lua_pcall(L, 1, 1, errfunc_idx) != LUA_OK) {
fprintf(stderr, "错误: %s
", lua_tostring(L, -1));
lua_pop(L, 1);
} else {
// 处理结果
lua_pop(L, 1);
}
lua_pop(L, 1); // 移除错误处理器
}
// 从C访问Lua表
void access_lua_table(lua_State *L) {
lua_getglobal(L, "config");
if (!lua_istable(L, -1)) {
fprintf(stderr, "config 不是表
");
lua_pop(L, 1);
return;
}
// 获取字段
lua_getfield(L, -1, "timeout");
lua_Integer timeout = lua_tointeger(L, -1);
lua_pop(L, 1);
// 迭代表
lua_pushnil(L); // 第一个键
while (lua_next(L, -2) != 0) {
// 键在-2,值在-1
const char *key = lua_tostring(L, -2);
const char *value = lua_tostring(L, -1);
printf("%s = %s
", key, value);
lua_pop(L, 1); // 移除值,保留键用于下一个
}
lua_pop(L, 1); // 移除表
}
// 从C设置Lua全局变量
void set_lua_global(lua_State *L, const char *name, lua_Integer value) {
lua_pushinteger(L, value);
lua_setglobal(L, name);
}
// 加载Lua块而不执行
void load_chunk(lua_State *L, const char *code) {
if (luaL_loadstring(L, code) != LUA_OK) {
fprintf(stderr, "加载错误: %s
", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
// 块作为函数在栈上
// 准备好时调用它
if (lua_pcall(L, 0, 0, 0) != LUA_OK) {
fprintf(stderr, "执行错误: %s
", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
从C调用Lua使得可以在C应用程序中使用Lua作为配置或脚本层。
创建C模块
C模块将相关功能和常量打包供Lua程序使用。
#include <lua.h>
#include <lauxlib.h>
#include <math.h>
// 模块函数
static int vector_new(lua_State *L) {
lua_Number x = luaL_checknumber(L, 1);
lua_Number y = luaL_checknumber(L, 2);
lua_newtable(L);
lua_pushnumber(L, x);
lua_setfield(L, -2, "x");
lua_pushnumber(L, y);
lua_setfield(L, -2, "y");
return 1;
}
static int vector_add(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
luaL_checktype(L, 2, LUA_TTABLE);
lua_getfield(L, 1, "x");
lua_Number x1 = lua_tonumber(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "y");
lua_Number y1 = lua_tonumber(L, -1);
lua_pop(L, 1);
lua_getfield(L, 2, "x");
lua_Number x2 = lua_tonumber(L, -1);
lua_pop(L, 1);
lua_getfield(L, 2, "y");
lua_Number y2 = lua_tonumber(L, -1);
lua_pop(L, 1);
lua_newtable(L);
lua_pushnumber(L, x1 + x2);
lua_setfield(L, -2, "x");
lua_pushnumber(L, y1 + y2);
lua_setfield(L, -2, "y");
return 1;
}
static int vector_magnitude(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
lua_getfield(L, 1, "x");
lua_Number x = lua_tonumber(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "y");
lua_Number y = lua_tonumber(L, -1);
lua_pop(L, 1);
lua_pushnumber(L, sqrt(x*x + y*y));
return 1;
}
// 模块表
static const luaL_Reg vector_funcs[] = {
{"new", vector_new},
{"add", vector_add},
{"magnitude", vector_magnitude},
{NULL, NULL}
};
// 模块初始化
int luaopen_vector(lua_State *L) {
luaL_newlib(L, vector_funcs);
// 添加常量
lua_pushnumber(L, M_PI);
lua_setfield(L, -2, "PI");
lua_pushnumber(L, M_E);
lua_setfield(L, -2, "E");
return 1;
}
// 子模块模式
static const luaL_Reg math_basic[] = {
{"add", add},
{"subtract", subtract},
{NULL, NULL}
};
static const luaL_Reg math_trig[] = {
{"sin", trig_sin},
{"cos", trig_cos},
{NULL, NULL}
};
int luaopen_mathlib(lua_State *L) {
lua_newtable(L);
// 基本子模块
luaL_newlib(L, math_basic);
lua_setfield(L, -2, "basic");
// 三角子模块
luaL_newlib(L, math_trig);
lua_setfield(L, -2, "trig");
return 1;
}
// 带有状态的模块
typedef struct {
int counter;
char name[64];
} ModuleState;
static int get_counter(lua_State *L) {
ModuleState *state = (ModuleState *)lua_touserdata(L, lua_upvalueindex(1));
lua_pushinteger(L, state->counter);
return 1;
}
static int increment_counter(lua_State *L) {
ModuleState *state = (ModuleState *)lua_touserdata(L, lua_upvalueindex(1));
state->counter++;
return 0;
}
int luaopen_stateful(lua_State *L) {
ModuleState *state = (ModuleState *)lua_newuserdata(L, sizeof(ModuleState));
state->counter = 0;
strncpy(state->name, "default", sizeof(state->name));
// 以状态作为上值的函数
lua_newtable(L);
lua_pushvalue(L, -2); // 推送状态
lua_pushcclosure(L, get_counter, 1);
lua_setfield(L, -2, "get_counter");
lua_pushvalue(L, -2); // 推送状态
lua_pushcclosure(L, increment_counter, 1);
lua_setfield(L, -2, "increment");
return 1;
}
将相关功能组织到模块中,以实现清晰的API设计和命名空间管理。
用户数据和元表
用户数据包装C结构以供在Lua中使用,并通过元表实现自定义行为。
#include <lua.h>
#include <lauxlib.h>
#include <stdlib.h>
// C结构
typedef struct {
double x;
double y;
} Point;
#define POINT_METATABLE "Point"
// 构造函数
static int point_new(lua_State *L) {
double x = luaL_checknumber(L, 1);
double y = luaL_checknumber(L, 2);
Point *p = (Point *)lua_newuserdata(L, sizeof(Point));
p->x = x;
p->y = y;
luaL_getmetatable(L, POINT_METATABLE);
lua_setmetatable(L, -2);
return 1;
}
// 从栈获取用户数据
static Point *check_point(lua_State *L, int index) {
return (Point *)luaL_checkudata(L, index, POINT_METATABLE);
}
// 方法
static int point_distance(lua_State *L) {
Point *p1 = check_point(L, 1);
Point *p2 = check_point(L, 2);
double dx = p2->x - p1->x;
double dy = p2->y - p1->y;
double dist = sqrt(dx*dx + dy*dy);
lua_pushnumber(L, dist);
return 1;
}
static int point_tostring(lua_State *L) {
Point *p = check_point(L, 1);
lua_pushfstring(L, "Point(%f, %f)", p->x, p->y);
return 1;
}
static int point_add(lua_State *L) {
Point *p1 = check_point(L, 1);
Point *p2 = check_point(L, 2);
Point *result = (Point *)lua_newuserdata(L, sizeof(Point));
result->x = p1->x + p2->x;
result->y = p1->y + p2->y;
luaL_getmetatable(L, POINT_METATABLE);
lua_setmetatable(L, -2);
return 1;
}
static int point_eq(lua_State *L) {
Point *p1 = check_point(L, 1);
Point *p2 = check_point(L, 2);
lua_pushboolean(L, p1->x == p2->x && p1->y == p2->y);
return 1;
}
static int point_index(lua_State *L) {
Point *p = check_point(L, 1);
const char *key = luaL_checkstring(L, 2);
if (strcmp(key, "x") == 0) {
lua_pushnumber(L, p->x);
return 1;
} else if (strcmp(key, "y") == 0) {
lua_pushnumber(L, p->y);
return 1;
}
return 0;
}
static int point_newindex(lua_State *L) {
Point *p = check_point(L, 1);
const char *key = luaL_checkstring(L, 2);
double value = luaL_checknumber(L, 3);
if (strcmp(key, "x") == 0) {
p->x = value;
} else if (strcmp(key, "y") == 0) {
p->y = value;
}
return 0;
}
// 垃圾回收
static int point_gc(lua_State *L) {
Point *p = check_point(L, 1);
// 如果需要清理(例如释放分配的内存)
return 0;
}
// 元表方法
static const luaL_Reg point_metamethods[] = {
{"__tostring", point_tostring},
{"__add", point_add},
{"__eq", point_eq},
{"__index", point_index},
{"__newindex", point_newindex},
{"__gc", point_gc},
{NULL, NULL}
};
// 模块初始化
int luaopen_point(lua_State *L) {
// 创建元表
luaL_newmetatable(L, POINT_METATABLE);
luaL_setfuncs(L, point_metamethods, 0);
// 创建模块表
lua_newtable(L);
lua_pushcfunction(L, point_new);
lua_setfield(L, -2, "new");
lua_pushcfunction(L, point_distance);
lua_setfield(L, -2, "distance");
return 1;
}
// 轻量用户数据(无GC的指针)
static int create_light_userdata(lua_State *L) {
Point *p = (Point *)malloc(sizeof(Point));
p->x = 10;
p->y = 20;
lua_pushlightuserdata(L, p);
return 1;
}
用户数据使得可以将C结构传递给Lua,同时通过元表控制访问。
最佳实践
-
始终检查 lua_pcall 结果 以正确捕获和处理Lua错误
-
使用 luaL_check 函数 进行参数验证并提供清晰的错误消息
-
平衡推入和弹出操作 以防止栈溢出和泄漏
-
为用户数据创建元表 以实现自然的Lua风格访问模式
-
使用 luaL_newlib 进行模块 以简化函数表注册
-
使用 luaL_error 处理错误 而不是返回错误代码
-
避免对数字使用 lua_tostring 因为它会修改栈;使用 lua_tonumber
-
使用 lua_absindex 当栈位置在操作期间改变时
-
为用户数据清理注册元方法 以防止内存泄漏
-
在注释中记录栈效果 对于复杂的C函数
常见陷阱
-
由不平衡的推入/弹出导致的栈溢出 导致崩溃和未定义行为
-
未检查函数返回类型 导致类型不匹配和错误
-
未检查就使用 lua_tostring 对非字符串会修改栈
-
直接调用 lua_error 而不是 luaL_error 失去错误上下文
-
访问无效的栈索引 导致未定义行为和崩溃
-
未在用户数据上设置元表 使垃圾回收不可靠
-
混合绝对和相对索引 导致混淆和错误
-
忘记 lua_pcall 错误处理 导致未捕获的异常
-
未使用 luaL_checkudata 允许类型混淆和崩溃
-
过早关闭 lua_State 使所有引用无效并导致崩溃
何时使用此技能
当性能关键操作超出纯Lua能力时应用C集成。
使用C模块来包装现有C库以供Lua应用程序使用。
利用用户数据在Lua代码中管理复杂的C结构。
在需要运行时配置的C应用程序中将Lua嵌入为脚本引擎。
为不适合Lua的计算密集型算法创建C扩展。
通过C绑定实现低级系统操作或硬件接口。