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
指向getarray
,metaarray.__newindex
指向setarray
。由于这种实现方式,metaarray.__index
只能指向单一的函数,getsize
和array2string
这类函数还是需要定义在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 -- 常规数组访问方式。