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

Lua 學習筆記(十六):userdata 與 Metatable 實現 C/C++ 對象綁定

     編輯:關於C語言

前言

userdata 是 Lua 與 C/C++ 交互的核心機制:它是一塊由 Lua GC 管理的原始記憶體,配合 Metatable 可模擬面向對象行為。本筆記記錄從零構建一個 LuaArray (float 陣列) 的完整過程。

userdata 兩種使用模式

模式記憶體歸屬GC 行為適用場景
全托管對象數據直接放在 userdata 內Lua GC 回收 userdata 時自動銷毀對象小型、生命週期綁定 Lua 的對象
半托管userdata 僅存 指標,對象在 C++ 堆上需在 __gc 中手動 delete大型對象、需共享給多 Lua 狀態、或 C++ 端長期持有

本文演示全托管模式。

步驟 1:定義 C 結構與 Lua 接口聲明

struct LuaArray {
    int size;
    float data[1]; // 柔性數組成員 (C99),實際分配時多分配 size*sizeof(float)
};

static const char* LuaArrayTableName = "LuaArray";
static const luaL_Reg ArrayFuncs[] = {
    {"__newindex", SetArrayValue},
    {"__len",      GetArrayLength},
    {"__gc",       ReleaseArray},
    {"get",        GetArrayValue},
    {"sum",        SumArray},
    {"new",        NewArray},
    {NULL, NULL}
};

步驟 2:註冊 Metatable (InitArray)

void InitArray(lua_State* L) {
    luaL_register(L, LuaArrayTableName, ArrayFuncs); // 創建表並註冊函數
    lua_pushvalue(L, -1);            // 複製表
    lua_setfield(L, -2, "__index");  // 表.__index = 表 (自身)
    lua_pop(L, 1);                   // 彈出表,留在 registry 中可通过全局名訪問
}

關鍵點: __index = 自身 使得 arr:get(1)、arr.sum() 等方法調用能在 Metatable 中找到函數;同時 __newindex 支援 arr[1] = 100 語法糖。

步驟 3:構造函數 NewArray

int NewArray(lua_State* L) {
    int n = luaL_checkint(L, 1);                 // 參數 1:元素個數
    size_t memsize = sizeof(LuaArray) + n * sizeof(float);
    LuaArray* arr = (LuaArray*)lua_newuserdata(L, memsize); // 分配 userdata
    arr->size = n;
    for (int i = 0; i <= n; ++i) arr->data[i] = 0.0f; // 初始化

    lua_getglobal(L, LuaArrayTableName); // 取得 Metatable
    lua_setmetatable(L, -2);             // 綁定到 userdata
    return 1; // 返回 userdata
}

Lua 調用:arr = LuaArray.new(10)。

步驟 4:索引訪問 (__newindex / get)

int SetArrayValue(lua_State* L) {
    LuaArray* arr = (LuaArray*)lua_touserdata(L, 1);
    int idx = luaL_checkint(L, 2);
    float val = (float)luaL_checknumber(L, 3);
    arr->data[idx] = val; // 直接寫入 userdata 記憶體
    return 0;
}

int GetArrayValue(lua_State* L) {
    LuaArray* arr = (LuaArray*)lua_touserdata(L, 1);
    int idx = luaL_checkint(L, 2);
    lua_pushnumber(L, arr->data[idx]);
    return 1;
}

Lua 用法:

arr[1] = 100        -- 觸發 __newindex
print(arr:get(1))   -- 調用 get 方法 (因 __index 指向自身表)

步驟 5:其他實用方法

int GetArrayLength(lua_State* L) {
    LuaArray* arr = (LuaArray*)lua_touserdata(L, 1);
    lua_pushinteger(L, arr->size);
    return 1;
}

int SumArray(lua_State* L) {
    LuaArray* arr = (LuaArray*)lua_touserdata(L, 1);
    float sum = 0;
    for (int i = 0; i < arr->size; ++i) sum += arr->data[i];
    lua_pushnumber(L, sum);
    return 1;
}

步驟 6:垃圾回收 (__gc)

int ReleaseArray(lua_State* L) {
    LuaArray* arr = (LuaArray*)lua_touserdata(L, 1);
    // 全托管模式下,userdata 記憶體由 Lua 自動釋放,這裡通常無需操作
    // 若在 userdata 中藏有額外 new 出的指針(演示用),需手動 delete
    float* extra;
    memcpy(&extra, arr->data, sizeof(float*)); // 假設藏在 data[0]
    delete[] extra;
    return 0;
}

Lua 端:arr = nil; collectgarbage() 觸發 __gc。

完整 Lua 測試腳本

arr = LuaArray.new(10)
arr[1] = 100
arr[2] = 200
print("len:", #arr)        -- __len
print("get(1):", arr:get(1))
print("sum:", arr:sum())
arr = nil
collectgarbage()           -- 觸發 __gc
print("collected")

進階提示

  • 類型檢查: 在 lua_touserdata 前用 luaL_checkudata(L, 1, LuaArrayTableName) 確保傳入的是正確 userdata,否則報錯。
  • 繼承/多態: 可在 Metatable 再設 __index 指向基類表,實現單繼承。
  • 半托管模式: userdata 只存 T*,__gc 執行 delete static_cast(ptr),C++ 端可繼續持有對象。
  • 輕量 userdata: lua_pushlightuserdata 不受 GC 管理,僅作指標傳遞,慎用。

總結

userdata + metatable 是 Lua 擴展 C/C++ 類型的標準範式:

  1. 分配 userdata (或存指標)
  2. 綁定 Metatable 定義行為 (__index, __newindex, __gc, __len...)
  3. 在 C 函數中通過 lua_touserdata 取回指針操作數據
  4. 依賴 Lua GC 自動管理生命週期

掌握此機制,即可將任意 C/C++ 庫無縫暴露給 Lua 腳本層。

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