绑定标量全局变量二

lua-users home
wiki

问题:如何定义一个全局变量,以便访问和设置其值实际上会调用一个C函数来操作内部对象?这在 Lua 4 中很容易,但 Lua 5 缺少大多数用于实现简单 Lua 4 的元方法。

这里有一个解决方案,它允许你将全局变量设置为构造函数的结果。请参阅绑定标量全局变量一以了解不同的实现。

免责声明:我对此内容负责,但我不会在我的代码中使用它,不是因为它很慢,而是因为它太奇怪了。最终,使用 getter 和 setter 函数一样容易。但是,我把它放在这里,因为我认为它是一个有趣的元方法可能性示例,并且它展示了一种绕过gettablesettable元方法缺失的有用方法。这在构建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

最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于2004年11月4日下午6:53 GMT (差异)