为什么
把 Lua 当配置语言 + 脚本插件层塞进 C/C++ 程序。这是 Lua 的"原产业"——魔兽、Adobe Lightroom、VLC、Roblox 都这么做。
读者画像:你已经在写 C/C++ 程序,想加可热更的脚本能力。
链接 Lua
brew install lua / apt install liblua5.4-dev 后:
gcc main.c -llua -lm -ldl -o myapp
# 或
gcc main.c $(pkg-config --cflags --libs lua5.4) -o myapp
LuaJIT:-lluajit-5.1。
最小 host:跑一段 Lua 字符串
#include <stdio.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
int main(void) {
lua_State *L = luaL_newstate(); // 新建 Lua 状态机
luaL_openlibs(L); // 打开标准库
if (luaL_dostring(L, "print('Hello from Lua!')") != LUA_OK) {
fprintf(stderr, "Lua error: %s\n", lua_tostring(L, -1));
}
lua_close(L);
return 0;
}
luaL_newstate 创建独立 Lua 虚拟机。可以同进程开多个 lua_State——互不干扰。
加载脚本文件
if (luaL_dofile(L, "script.lua") != LUA_OK) {
fprintf(stderr, "%s\n", lua_tostring(L, -1));
}
script.lua:
print("loaded from C")
function greet(name) return "Hello, " .. name end
从 C 调 Lua 函数
luaL_dofile(L, "script.lua"); // 加载,注册 greet 到全局
lua_getglobal(L, "greet"); // 推函数到栈
lua_pushstring(L, "World"); // 推参数
if (lua_pcall(L, 1, 1, 0) != LUA_OK) { // 1 入参,1 返回值
fprintf(stderr, "%s\n", lua_tostring(L, -1));
} else {
printf("got: %s\n", lua_tostring(L, -1)); // "Hello, World"
}
lua_pop(L, 1); // 弹返回值
心智模型:Lua C API 全是基于一个栈操作——参数、返回值都通过推 / 弹来传递。
从 Lua 调 C 函数
static int l_sum(lua_State *L) {
double a = luaL_checknumber(L, 1);
double b = luaL_checknumber(L, 2);
lua_pushnumber(L, a + b);
return 1; // 返回值个数
}
int main(void) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
lua_pushcfunction(L, l_sum);
lua_setglobal(L, "csum"); // Lua 现在能调 csum()
luaL_dostring(L, "print(csum(3, 4))"); // 7
lua_close(L);
}
配置文件模式
config.lua:
return {
server = { host = "0.0.0.0", port = 8080 },
debug = true,
plugins = {"auth", "rate-limit", "metrics"},
}
C 端:
luaL_dofile(L, "config.lua"); // 配置返回一个表,留在栈顶
lua_getfield(L, -1, "server"); // 推 server 子表
lua_getfield(L, -1, "host");
const char *host = lua_tostring(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "port");
int port = (int)lua_tointeger(L, -1);
lua_pop(L, 2); // 弹 port 和 server
printf("listening on %s:%d\n", host, port);
配置文件用 Lua 比 JSON/YAML 灵活:能写计算、条件、引用、注释、共享变量。
安全:沙箱
让脚本只能用部分 API:
lua_State *L = luaL_newstate();
// 不打开所有标准库——只挑安全的
luaopen_base(L); lua_pop(L, 1);
luaopen_string(L); lua_setglobal(L, "string");
luaopen_math(L); lua_setglobal(L, "math");
// 不开 io / os / debug / package
或加载脚本前清掉危险全局:
luaL_dostring(L, "io = nil; os.execute = nil; package = nil");
更严格的方案用 lua_setfenv (5.1) / _ENV (5.2+) 做按脚本的环境隔离——见 Lua sandbox 教程。
错误处理
int r = lua_pcall(L, nargs, nresults, 0);
if (r != LUA_OK) {
const char *err = lua_tostring(L, -1);
fprintf(stderr, "Lua error (%d): %s\n", r, err);
lua_pop(L, 1);
}
lua_call 直接调(出错程序整个崩溃);lua_pcall 是受保护的(拿到错误字符串,宿主决定怎么处理)。
内存与 GC
lua_gc(L, LUA_GCCOLLECT, 0); // 强制 full GC
int kb = lua_gc(L, LUA_GCCOUNT, 0); // 当前内存 (KB)
长跑程序里:定期主动触发 GC 或调 lua_gc(L, LUA_GCSTEP, 0) 渐进式收。
替代:sol2(C++ 友好封装)
直接用 C API 啰嗦。C++ 项目用 sol2:
#include <sol/sol.hpp>
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::string);
lua.script("function add(a, b) return a + b end");
int result = lua["add"](3, 4); // 干净的语法
std::cout << result << "\n"; // 7
lua.set_function("greet", [](const std::string& name) {
return "Hello, " + name;
});
lua.script("print(greet('Sol'))");
C++14 + 模板魔法,几乎零运行时开销。强烈推荐 C++ 项目用。
→ 下一篇 Lua 路线图