绑定标量全局变量二 |
|
C函数来操作内部对象?这在 Lua 4 中很容易,但 Lua 5 缺少大多数用于实现简单 Lua 4 的元方法。
这里有一个解决方案,它允许你将全局变量设置为构造函数的结果。请参阅绑定标量全局变量一以了解不同的实现。
免责声明:我对此内容负责,但我不会在我的代码中使用它,不是因为它很慢,而是因为它太奇怪了。最终,使用 getter 和 setter 函数一样容易。但是,我把它放在这里,因为我认为它是一个有趣的元方法可能性示例,并且它展示了一种绕过gettable和settable元方法缺失的有用方法。这在构建C结构的代理时非常有用。
为了实现这一点,你至少需要将一个已知的元表与每种不同类型的C对象关联起来。这个元表实际上是对象的“类型”。元表必须包含键__get和__set,它们的值是(可能是C)函数,用于获取和设置C标量的值。如果__get是恒等函数,那么没有什么能阻止你为类型/元方法表填充其他元方法,例如算术运算符或比较运算符,甚至__call、__index或__newindex。
构造函数需要将C对象与元表关联起来,并且有必要使用register_type(见下文)注册元表。(现在我写完两年后,这个机制与 lauxlib 中的机制并没有太大区别。)
不需要使用C和userdata;以下示例使用Lua函数和表,但希望C接口足够清晰。
一旦构造函数的结果被分配给一个全局变量,未来对该全局变量的设置和获取将被延迟到__get和__set伪元方法,因此你永远无法摆脱绑定(好吧,可能存在一个解除绑定函数,但我没有编写它)。
在BoundScalarGlobalsOne中,我尝试处理具有现有元表的全局表。这次我没有这样做——虽然相同的策略在这种情况下也能奏效,但它只是让代码更难理解,而代码本身已经足够难理解了 :)
为了效率,我假设你不会想要将现有的全局变量重新定义为C值,并且在执行此过程之后,不会有太多新的非C值全局变量。这意味着系统库函数仍然可以在没有干扰的情况下被查找。
-- BoundScalar2
-- Version 0.1
-- RiciLake
--
do
local types = {} -- table of metatables we're going to deal with
local global_meta = {} -- metatable for globals
local sub_meta = {} -- metatable for new globals
local c_proxied = {} -- table of proxied values.
local new_globals = {} -- normal globals
-- use this function to register your metatables (types)
function register_type(meta)
types[meta] = true
end
-- __index metamethod for globals table
--
-- We know the key doesn't exist in the globals table,
-- which contains pre-existing globals. It
-- might be new global, or it might be a proxied c-value.
-- We defer the lookup to the new_globals
-- table which has its own __index metamethod
function global_meta.__index(t, k)
return new_globals[k]
end
-- __index metamethod for new_globals table
--
-- This gets invoked if the key wasn't found in the
-- new_globals table. So it is either a
-- proxied c-value or an undefined global
function sub_meta.__index(t, k)
local val = c_proxied[k]
if val then return getmetatable(val).__get(val) end
end
-- __newindex metamethod for globals table
--
-- With set, we need to follow a different strategy, because we need to
-- intercept every attempt to set a global (this doesn't happen
-- with pre-existing globals).
function global_meta.__newindex(t, k, v)
-- first we see if we're proxying this key, and if so we just pass on
-- the value. c_proxied does not have metamethods, so we don't need
-- rawget.
local cval = c_proxied[k]
if cval then
getmetatable(cval).__set(cval, v)
else
-- get the *value*'s type (well, metatable)
-- and see if it is one of ours.
local meta = getmetatable(v)
if meta and types[meta] then
-- It is a c_val, so we need to get rid of it from regular
-- globals, in case it exists, and put it into the proxied
-- table
new_globals[k] = nil
c_proxied[k] = v
else
-- it is neither an existing c_value nor a new one. So we just
-- stash it away
new_globals[k] = v
end
end
end
setmetatable(getfenv(), global_meta)
setmetatable(new_globals, sub_meta)
end
现在我们可以定义可绑定的类型。这是一个简单的示例。查看示例输出了解其工作原理。
do
local date_meta = {}
register_type(date_meta)
-- __get returns the date according to the object's format
function date_meta.__get(date_obj)
return os.date(date_obj.fmt)
end
-- __set changes the object's format
function date_meta.__set(date_obj, fmt)
date_obj.fmt = fmt
end
-- constructor
function Date(fmt)
return setmetatable({fmt = fmt or "%c"}, date_meta)
end
end
示例输出
> now = Date()
> print(now)
Thu Jan 16 16:41:13 2003
> gmt = Date("!%c")
> print(gmt)
Thu Jan 16 21:41:22 2003
> gmt = "!%F %R"
> print(gmt)
2003-01-16 21:41