快速掌握Lua 5.3 —— userdata (2)


Q:如何使用”userdata”的”metamethods”?

A:我們繼續來修改上一節中的例子,這次我們的目標是使用面向對象的方式調用”userdata”的方法。這個目標既可以在Lua中實現,也可以在C庫中實現,我們先來看一個比較簡單的方式,在Lua中實現。”mylib.c”中代碼無需更改,只需要修改”a.lua”中的代碼,

local array = require "mylib"

--[[ 這里創建一個大小為1的數組,僅僅是為了獲取其"metatable"。
     雖然Lua代碼無法更改"userdata""metatable",
     但是獲取"metatable"以及修改"metamethods"是不受影響的。
     因為所有通過"array.new"創建的數組,
     均使用存儲在"registry"中的同一個"metatable"。
     所以這里修改了此"metatable""metamethods",
     下面所創建的數組也就擁有了這些"metamethods"。]]
local metaarray = getmetatable(array.new(1))
metaarray.__index = metaarray
metaarray.set = array.set
metaarray.get = array.get
metaarray.size = array.size

a = array.new(1000)
print(a)    --> userdata: 0x240f268
print(a:size())    --> 1000
for i = 1, 1000 do 
    a:set(i, 1 / i)
end
print(a:get(10))    --> 0.1

在Lua中實現的方式雖然方便,但在實際應用中卻並不推薦。因為提供C庫的意義就在於讓使用者方便的調用提供的函數,而基於這種實現方式下,難道要在發布的C庫的”Readme.txt”中說明在使用時需要增加的代碼,以及暴露自己C庫中定義的函數名稱?
所以為了避免這些問題,我們接下來將在C庫中實現”userdata”的”metamethods”。

/* 將"l"中所有的函數注冊到從棧頂開始"nup""upvalues"下的"table"中,
 * 被注冊的函數共享"nup""upvalues"。
 * "nup""upvalues"在函數執行完成后會被出棧。
 */
void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup);

“mylib.c”文件中:

#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

typedef struct NumArray
{
    int size;
    double values[1];
} NumArray;

static NumArray *checkarray(lua_State *L)
{
    void *ud = luaL_checkudata(L, 1, "LuaBook.array");
    luaL_argcheck(L, ud != NULL, 1, "'array' expected");

    return (NumArray *)ud;
}

static double *getelem(lua_State *L)
{
    NumArray *a = checkarray(L);
    int index = luaL_checkinteger(L, 2);

    luaL_argcheck(L, 1 <= index && index <= a->size, 2,
            "index out of range");

    return &a->values[index - 1];
}

static int newarray(lua_State *L)
{
    int n = luaL_checkinteger(L, 1);
    size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
    NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);

    luaL_getmetatable(L, "LuaBook.array");
    // 注意,這里使用的不是"luaL_setmetatable",兩個函數的功能不同。
    lua_setmetatable(L, -2);

    a->size = n;

    return 1;
}

static int setarray(lua_State *L)
{
    double value = luaL_checknumber(L, 3);
    *getelem(L) = value;

    return 0;
}

static int getarray(lua_State *L)
{
    lua_pushnumber(L, *getelem(L));

    return 1;
}

static int getsize(lua_State *L)
{
    NumArray *a = checkarray(L);
    lua_pushnumber(L, a->size);

    return 1;
}

static int array2string(lua_State *L)
{
    NumArray *a = checkarray(L);
    lua_pushfstring(L, "array(%d)", a->size);

    return 1;
}

/* 現在C庫中只需要向外部提供創建數組的方法,
 * 那些需要通過對象調用的方法都以"metamethods"的形式放在了"metatable"中。
 */
static const struct luaL_Reg arraylib_f[] = {
    {"new", newarray}, 
    {NULL, NULL}
};

/* "metamethods"。
 * 因為"userdata"根本就沒有"keys",所以每當調用C庫中未向外部提供的方法時,
 * 都會來尋找對應的"metamethods"。
static const struct luaL_Reg arraylib_m[] = {
    // 將對象轉換為字符串時,默認會調用的"metamethod"。
    {"__tostring", array2string}, 
    {"set", setarray}, 
    {"get", getarray}, 
    {"size", getsize}, 
    {NULL, NULL}
};

extern int luaopen_mylib(lua_State* L)
{
    luaL_newmetatable(L, "LuaBook.array"); // 新創建的"metatable"會入棧。

    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2); // 復制一份"metatable"再次入棧。
    lua_settable(L, -3); // "metatable.__index = metatable"
    // 將"metamethods"都存入"metatable"中。
    luaL_setfuncs(L, arraylib_m, 0);

    luaL_newlib(L, arraylib_f);

    return 1;
}

將”mylib.c”編譯為動態連接庫,

prompt> gcc mylib.c -fPIC -shared -o mylib.so -Wall
prompt> ls
mylib.c    mylib.so    a.lua

“a.lua”文件中:

local array = require "mylib"

a = array.new(1000)
--[[ 傳遞給"print"的參數會自動調用對象的"__tostring"方法, C庫中提供了此"metamethod",所以這里不再打印"userdata: ***", 而是打印指定格式的信息。]]
print(a)    --> array(1000)
print(a:size())    --> 1000
for i = 1, 1000 do
    a:set(i, 1 / i)
end
print(a:get(10))    --> 0.1

附加:

1、我們還可以使用常規數組的訪問方式訪問我們在C庫中定義的數組,只需要將metaarray.__index指向getarraymetaarray.__newindex指向setarray。由於這種實現方式,metaarray.__index只能指向單一的函數,getsizearray2string這類函數還是需要定義在luaL_newlib接收的數組中。
在Lua中實現:
“mylib.c”文件中:

#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

typedef struct NumArray
{
    int size;
    double values[1];
} NumArray;

static NumArray *checkarray(lua_State *L)
{
    void *ud = luaL_checkudata(L, 1, "LuaBook.array");
    luaL_argcheck(L, ud != NULL, 1, "'array' expected");

    return (NumArray *)ud;
}

static double *getelem(lua_State *L)
{
    NumArray *a = checkarray(L);
    int index = luaL_checkinteger(L, 2);

    luaL_argcheck(L, 1 <= index && index <= a->size, 2,
            "index out of range");

    return &a->values[index - 1];
}

static int newarray(lua_State *L)
{
    int n = luaL_checkinteger(L, 1);
    size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
    NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);

    luaL_getmetatable(L, "LuaBook.array");
    // 注意,這里使用的不是"luaL_setmetatable",兩個函數的功能不同。
    lua_setmetatable(L, -2);

    a->size = n;

    return 1;
}

static int setarray(lua_State *L) {
    double value = luaL_checknumber(L, 3);
    *getelem(L) = value;

    return 0;
}

static int getarray(lua_State *L)
{
    lua_pushnumber(L, *getelem(L));

    return 1;
}

static int getsize(lua_State *L)
{
    NumArray *a = checkarray(L);
    lua_pushnumber(L, a->size);

    return 1;
}

static int array2string(lua_State *L)
{
    NumArray *a = checkarray(L);
    lua_pushfstring(L, "array(%d)", a->size);

    return 1;
}

static const struct luaL_Reg arraylib[] = {
    {"new", newarray},
    {"size", getsize},
    {"set", setarray},
    {"get", getarray},
    {"tostring", array2string},
    {NULL, NULL}
};

extern int luaopen_mylib(lua_State* L)
{
    luaL_newmetatable(L, "LuaBook.array");
    luaL_newlib(L, arraylib);

    return 1;
}

“a.lua”文件中:

local array = require "mylib"

local metaarray = getmetatable(array.new(1))
metaarray.__index = array.get
metaarray.__newindex = array.set

a = array.new(1000)
print(array.tostring(a))    --> array(1000)
print(array.size(a))    --> 1000
for i = 1, 1000 do
    a[i] = (1 / i)
end
print(a[10])    --> 0.1

在C庫中實現:
“mylib.c”文件中:

#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

typedef struct NumArray
{
    int size;
    double values[1];
} NumArray;

static NumArray *checkarray(lua_State *L)
{
    void *ud = luaL_checkudata(L, 1, "LuaBook.array");
    luaL_argcheck(L, ud != NULL, 1, "'array' expected");

    return (NumArray *)ud;
}

static double *getelem(lua_State *L)
{
    NumArray *a = checkarray(L);
    int index = luaL_checkinteger(L, 2);

    luaL_argcheck(L, 1 <= index && index <= a->size, 2,
            "index out of range");

    return &a->values[index - 1];
}

static int newarray(lua_State *L)
{
    int n = luaL_checkinteger(L, 1);
    size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
    NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);

    luaL_getmetatable(L, "LuaBook.array");
    lua_setmetatable(L, -2);

    a->size = n;

    return 1;
}

static int setarray(lua_State *L) {
    double value = luaL_checknumber(L, 3);
    *getelem(L) = value;

    return 0;
}

static int getarray(lua_State *L)
{
    lua_pushnumber(L, *getelem(L));

    return 1;
}

static int getsize(lua_State *L)
{
    NumArray *a = checkarray(L);
    lua_pushnumber(L, a->size);

    return 1;
}

static int array2string(lua_State *L)
{
    NumArray *a = checkarray(L);
    lua_pushfstring(L, "array(%d)", a->size);

    return 1;
}

static const struct luaL_Reg arraylib[] = {
    {"new", newarray},
    {"size", getsize},
    // 數組轉換為字符串的函數不再作為"metamethod",而是顯式的提供給外部。
    {"tostring", array2string}, 
    {NULL, NULL}
};

extern int luaopen_mylib(lua_State* L)
{
    luaL_newmetatable(L, "LuaBook.array");

    lua_pushstring(L, "__index");
    lua_pushcfunction(L, getarray);
    lua_settable(L, -3);    // metatable.__index = getarray

    lua_pushstring(L, "__newindex");
    lua_pushcfunction(L, setarray);
    lua_settable(L, -3);    // metatable.__newindex = setarray

    luaL_newlib(L, arraylib);

    return 1;
}

“a.lua”文件中:

local array = require "mylib"

a = array.new(1000)
print(array.tostring(a))    --> array(1000)
print(array.size(a))    --> 1000
for i = 1, 1000 do
    a[i] = (1 / i)    -- 常規數組訪問方式。
end
print(a[10])    --> 0.1 -- 常規數組訪問方式。

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com 联系我们: