绑定标量全局变量二 |
|
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