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

来源:互联网 发布:棋牌室收银软件 编辑:程序博客网 时间:2024/06/05 10:45

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 = metaarraymetaarray.set = array.setmetaarray.get = array.getmetaarray.size = array.sizea = array.new(1000)print(a)    --> userdata: 0x240f268print(a:size())    --> 1000for i = 1, 1000 do     a:set(i, 1 / i)endprint(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 -Wallprompt> lsmylib.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())    --> 1000for i = 1, 1000 do    a:set(i, 1 / i)endprint(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.getmetaarray.__newindex = array.seta = array.new(1000)print(array.tostring(a))    --> array(1000)print(array.size(a))    --> 1000for i = 1, 1000 do    a[i] = (1 / i)endprint(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))    --> 1000for i = 1, 1000 do    a[i] = (1 / i)    -- 常规数组访问方式。endprint(a[10])    --> 0.1 -- 常规数组访问方式。
0 0