首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

C 模塊回調 Lua 函數的兩種核心機制實戰詳解

     編輯:關於C

Lua 與 C 交互基礎:虛擬棧

Lua 與 C 通過虛擬棧交換數據:C 將參數壓棧調用 Lua 函數,Lua 將返回值壓棧供 C 取用。缺點是 C 代碼需手動維護棧平衡,稍顯繁瑣,但機制簡單可靠。

方法一:註冊表保存函數引用

利用 Lua 註冊表存儲函數對象,C 端持有引用索引,需要時取出調用。

核心 API

  • luaL_ref(L, LUA_REGISTRYINDEX):彈出棧頂值(須為函數),存入註冊表,返回整數引用索引。
  • lua_rawgeti(L, LUA_REGISTRYINDEX, ref):按索引從註冊表取值壓棧。
  • lua_call(L, nargs, nresults):調用棧頂函數,nargs 參數個數,nresults 返回值個數。

C 實現


#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;
}

Lua 測試腳本


require("cb")

function callback()
    print("Callback from registry")
end

cb.setnotify(callback)  -- 註冊
cb.testnotify()         -- 觸發,輸出 "Callback from registry"
print("Done")

優缺點分析

優點缺點
函數對象生命周期由 C 完全控制需管理全局引用變量,多回調需多變量/表
不污染 Lua 全局環境跨模塊傳遞引用需額外協作
支持匿名函數/閉包引用釋放需手動 luaL_unref 防洩漏

方法二:直接訪問 Lua 全局環境

C 直接按名稱從全局表 _G 獲取函數並調用,類似 Lua 調用 C。

C 實現


static int testenv(lua_State *L) {
    lua_getglobal(L, "defcallback");  // 從 _G 取函數壓棧
    lua_call(L, 0, 0);                // 直接調用
    return 0;
}

Lua 測試腳本


function defcallback()
    print("Predef callback from global")
end

cb.testenv()  -- 直接調用全局 defcallback

優缺點分析

優點缺點
代碼極簡潔,無需管理引用強依賴固定全局函數名,靈活性差
調試直觀,Lua 端可直接覆蓋/模擬污染全局命名空間,易衝突

工程化建議: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", ...)

完整示例:cb.c + test.lua

cb.c(雙模式共存)


#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;
}

test.lua


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) -> [底] ... [頂]

最佳實踐總結

  1. 靈活回調:優先使用註冊表引用法,支持動態註冊/註銷、多實例隔離。
  2. 固定鉤子:全局環境法適合框架級固定回調(如初始化、退出鉤子)。
  3. 棧平衡鐵律:每次 API 調用後,心中要有棧圖;lua_call 會彈出函數及參數,壓入返回值。
  4. 錯誤處理:生產代碼應用 lua_pcall 替代 lua_call,捕獲 Lua 端錯誤避免宿主崩潰。
  5. 引用清理:模塊卸載時調用 luaL_unref(L, LUA_REGISTRYINDEX, lua_callback) 釋放引用。

掌握這兩種機制,即可在 C 庫中靈活實現異步通知、事件驅動、插件系統等高級模式,充分發揮 Lua 作為配置/腳本語言的優勢。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved