調用 Lua 函數僅需四步,核心是棧操作:
lua_getglobal(L, "f"):將函數壓入棧lua_pushnumber(L, x):壓入參數(支持多種類型)lua_pcall(L, nargs, nresults, errfunc):受保護調用lua_tonumber(L, -1):取結果,lua_pop(L, 1) 清理棧/*** Lua 端函數 ***
function f(x, y)
return (x^2 * math.sin(y)) / (1 - x)
end
***/
/* C 端封裝調用 */
double call_lua_f(lua_State *L, double x, double y) {
double z;
// 1. 獲取全局函數
lua_getglobal(L, "f");
// 2. 壓入兩個參數
lua_pushnumber(L, x);
lua_pushnumber(L, y);
// 3. 受保護調用:2個參數,1個返回值,無錯誤處理函數
if (lua_pcall(L, 2, 1, 0) != 0) {
// 錯誤信息在棧頂
const char *err = lua_tostring(L, -1);
fprintf(stderr, "Lua error: %s\n", err);
lua_pop(L, 1); // 彈出錯誤信息
return NAN;
}
// 4. 類型檢查與取值
if (!lua_isnumber(L, -1)) {
fprintf(stderr, "Function must return a number\n");
lua_pop(L, 1);
return NAN;
}
z = lua_tonumber(L, -1);
lua_pop(L, 1); // 彈出結果,棧平衡
return z;
}
// 使用錯誤處理函數(第4參數)
lua_pushcfunction(L, my_error_handler); // 壓入錯誤處理器
int errfunc_idx = lua_gettop(L); // 記錄其索引
lua_getglobal(L, "risky_func");
lua_pushnumber(L, arg);
if (lua_pcall(L, 1, 1, errfunc_idx) != 0) {
// 錯誤已由 handler 處理,棧頂為處理後的錯誤對象
handle_error(L);
}
lua_remove(L, errfunc_idx); // 移除錯誤處理器
lua_pcall(L, 0, 3, 0); // 期望3個返回值 // 棧頂為第3個返回值,依次向下 double r3 = lua_tonumber(L, -1); double r2 = lua_tonumber(L, -2); double r1 = lua_tonumber(L, -3); lua_pop(L, 3);
| 問題 | 原因 | 解決 |
|---|---|---|
| 棧不平衡 | 忘記 pop 結果/錯誤信息 | 每次 push/調用後檢查棧頂,確保對稱 pop |
| 類型錯誤 | Lua 返回非預期類型 | 用 lua_is* 系列檢查再轉換 |
| 錯誤信息丟失 | pcall 失敗未讀取棧頂 | 錯誤時必讀 lua_tostring(L, -1) 再 pop |
| 全局變量污染 | 直接操作 _G | 優先用 lua_getglobal/lua_setglobal |
static int l_sin(lua_State *L) {
double d = luaL_checknumber(L, 1);
lua_pushnumber(L, sin(d));
return 1; // 返回值個數
}
static const luaL_Reg mylib[] = {
{"sin", l_sin},
{NULL, NULL}
};
int luaopen_mylib(lua_State *L) {
luaL_newlib(L, mylib);
return 1;
}
編譯為動態庫,Lua 中 require "mylib" 即可使用 mylib.sin(x)。