Lua 與 C 通過虛擬棧交換數據:C 將參數壓棧調用 Lua 函數,Lua 將返回值壓棧供 C 取用。缺點是 C 代碼需手動維護棧平衡,稍顯繁瑣,但機制簡單可靠。
利用 Lua 註冊表存儲函數對象,C 端持有引用索引,需要時取出調用。
luaL_ref(L, LUA_REGISTRYINDEX):彈出棧頂值(須為函數),存入註冊表,返回整數引用索引。lua_rawgeti(L, LUA_REGISTRYINDEX, ref):按索引從註冊表取值壓棧。lua_call(L, nargs, nresults):調用棧頂函數,nargs 參數個數,nresults 返回值個數。
#include <stdio.h>
#include <stdlib.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
static int lua_callback = LUA_REFNIL; // 全局變量保存引用
// Lua 調用:註冊回調函數
static int setnotify(lua_State *L) {
lua_callback = luaL_ref(L, LUA_REGISTRYINDEX); // 保存棧頂函數
return 0;
}
// Lua 調用:觸發回調
static int testnotify(lua_State *L) {
lua_rawgeti(L, LUA_REGISTRYINDEX, lua_callback); // 取出函數
lua_call(L, 0, 0); // 調用,無參無返
return 0;
}
// 庫註冊
static const luaL_Reg cblib[] = {
{"setnotify", setnotify},
{"testnotify", testnotify},
{"testenv", testenv}, // 見方法二
{NULL, NULL}
};
int luaopen_cb(lua_State *L) {
luaL_register(L, "cb", cblib);
return 1;
}
require("cb")
function callback()
print("Callback from registry")
end
cb.setnotify(callback) -- 註冊
cb.testnotify() -- 觸發,輸出 "Callback from registry"
print("Done")
| 優點 | 缺點 |
|---|---|
| 函數對象生命周期由 C 完全控制 | 需管理全局引用變量,多回調需多變量/表 |
| 不污染 Lua 全局環境 | 跨模塊傳遞引用需額外協作 |
| 支持匿名函數/閉包 | 引用釋放需手動 luaL_unref 防洩漏 |
C 直接按名稱從全局表 _G 獲取函數並調用,類似 Lua 調用 C。
static int testenv(lua_State *L) {
lua_getglobal(L, "defcallback"); // 從 _G 取函數壓棧
lua_call(L, 0, 0); // 直接調用
return 0;
}
function defcallback()
print("Predef callback from global")
end
cb.testenv() -- 直接調用全局 defcallback
| 優點 | 缺點 |
|---|---|
| 代碼極簡潔,無需管理引用 | 強依賴固定全局函數名,靈活性差 |
| 調試直觀,Lua 端可直接覆蓋/模擬 | 污染全局命名空間,易衝突 |
-- Lua 端封裝,對 C 隔離全局名
local _callbacks = {}
function register_callback(name, fn)
_callbacks[name] = fn
end
function trigger_callback(name, ...)
local fn = _callbacks[name]
if fn then return fn(...) end
end
-- C 只需約定調用 trigger_callback("xxx", ...)
#include <stdio.h>
#include <stdlib.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
static int lua_callback = LUA_REFNIL;
static int setnotify(lua_State *L) {
lua_callback = luaL_ref(L, LUA_REGISTRYINDEX);
return 0;
}
static int testnotify(lua_State *L) {
lua_rawgeti(L, LUA_REGISTRYINDEX, lua_callback);
lua_call(L, 0, 0);
return 0;
}
static int testenv(lua_State *L) {
lua_getglobal(L, "defcallback");
lua_call(L, 0, 0);
return 0;
}
static const luaL_Reg cblib[] = {
{"setnotify", setnotify},
{"testnotify", testnotify},
{"testenv", testenv},
{NULL, NULL}
};
int luaopen_cb(lua_State *L) {
luaL_register(L, "cb", cblib);
return 1;
}
require("cb")
function callback()
print("Callback via registry")
end
function defcallback()
print("Predef callback via global")
end
cb.setnotify(callback)
cb.testnotify() -- 輸出: Callback via registry
print("Done")
cb.testenv() -- 輸出: Predef callback via global
方法一調用時棧變化 (testnotify): [底] ... [函數] [頂] -> lua_rawgeti -> [底] ... [函數] [頂] -> lua_call(0,0) -> [底] ... [頂] 方法二調用時棧變化 (testenv): [底] ... [頂] -> lua_getglobal -> [底] ... [函數] [頂] -> lua_call(0,0) -> [底] ... [頂]
lua_call 會彈出函數及參數,壓入返回值。lua_pcall 替代 lua_call,捕獲 Lua 端錯誤避免宿主崩潰。luaL_unref(L, LUA_REGISTRYINDEX, lua_callback) 釋放引用。掌握這兩種機制,即可在 C 庫中靈活實現異步通知、事件驅動、插件系統等高級模式,充分發揮 Lua 作為配置/腳本語言的優勢。