【问题标题】:How to extend SWIG's userdata in Lua code?如何在 Lua 代码中扩展 SWIG 的用户数据?
【发布时间】:2014-01-23 08:13:18
【问题描述】:

我正在使用 SWIG 将 C++ 代码绑定到 Lua。到目前为止看起来不错,但现在我需要“作弊”并从 Lua 中扩展单个用户数据,添加自定义字段和方法等。

在 SWIG 的指令中工作时,我找不到完成它的方法。我知道魔术发生在包装器代码的哪个位置,但我不完全理解 __index 和 __newindex 是如何工作的。此外,SWIG 使用 __setitem 和 __getitem,它们被注释为“/* NEW:查找 __setitem() fn 这是用户提供的 set fn */”——也不知道这意味着什么。最后,我的环境会在每次构建之前自动调用脚本将 SWIG 指令绑定到 C++ 包装器,因此如果我选择重新编译或添加更多 Lua 绑定,那么之后修改源代码非常繁琐。

到目前为止,我唯一的结论是使用 toLua++,它记录了这个特性。请帮我避免大量的过渡工作!

编辑:我还发现 5.2 中的“lua_setuservalue”API 调用 - 它看起来很有用,但我不明白在所有 SWIG 绑定代码中我会在哪里调用它。

编辑:清除问题:

我通过 C 函数创建对象

SoundObj *loadSound(const char *name)
{
    return g_audio->loadSound(name); // Returns SoundObj pointer
}

此函数通过在 *.i swig 定义中包含它的原型被 SWIG 绑定。

在 Lua 中我这样写代码:

sound = audio.loadSound("beep.mp3")
sound.scale = 0.2
sound:play()

编辑:进步!我按照 Schollii 的说明编写了以下 Lua 代码。这里,“ground”是之前使用绑定 C++ 代码获取的用户数据。该代码存储了 swig 的 __index 和 __newindex 版本,然后我重新创建了这些函数,这些函数首先查询另一个(“_other”)表。

我目前的问题是存储在“_other”表中的新值在该类型的所有用户数据对象之间共享。换句话说,如果 ground.nameFoo = "ha!",所有其他对象都有 nameFoo 字段存储 "ha!"。我该如何解决这个问题?

mt = getmetatable(ground)
mt._other = {}
mt.__oldindex = mt.__index
mt.__oldnewindex = mt.__newindex

mt.__index = function(tbl, key)
    if mt.__oldindex(tbl, key) == nil then
        if mt._other[key] ~= nil then
            return mt._other[key]
        end
    else
        return mt.__oldindex(tbl, key)
    end
end

mt.__newindex = function(tbl, key, val)
    if mt.__oldnewindex(tbl, key, val) == nil then
        mt._other[key] = val
    end
end

编辑:我从这个答案中实现了解决方案:Add members dynamically to a class using Lua + SWIG

问题是现在我的对象类型不再是 userdata,它是一个表。这意味着我不能再有机地将它作为参数传递给其他以 userdata 作为参数的绑定 C++ 函数。有什么解决办法吗?而且我必须对每个返回用户数据对象的函数执行此操作。

【问题讨论】:

  • 我认为这真的归结为您是否可以清楚地描述您的问题以便有人可以提供帮助。再一次,展示你想做什么,不要只是描述它(代码就像一个 1000 字)。你说“从 Lua 中扩展单个用户数据,添加自定义字段和方法”:好的,但是然后呢?我将添加一个答案作为示例。注意:我不知道你可以在 tolua++ 中做而不能在 SWIG 中做的事情。

标签: c++ c lua lua-table lua-userdata


【解决方案1】:

在这里我会很草率:在 Lua 中,一个表通过将另一个表用作元表来“继承”另一个表。因此,如果您将 C++ Foo 导出到 Lua 并希望从 Foo 派生一个 Lua“类”,您可以创建一个表并将其元表设置为 Foo。然后,当您使用表中不存在的字段访问新表时,它将查看元表以查看它是否存在。示例伪代码:

baseTable = {a=123}
assert( getmetatable(baseTable) == nil )
derived={b=456}
assert(derived.a == nil)
setmetatable(derived, baseTable )
assert(derived.a == 123)

baseTable 是通过 SWIG 导出到 Lua 的 C++ 类,您无法更改其元表,但您可以更改在 Lua 中创建的表的元表。所以你不应该对 SWIG 代码做任何修改或使用 SWIG 指令,你可以在 Lua 中操作。示例:

-- define Foo class:
Foo = {}
Foo.__index = Foo -- make Foo a Lua "class"
setmettable(Foo, YourCppBaseClass) -- derived from your C++ class
-- instantiate it:
foo = {}
setmetatable(foo, Foo) -- foo is of "class" Foo

【讨论】:

  • 谢谢 Schollii,我考虑过这个选项,但我所有的对象都是在 C++ 中创建和绑定的。几乎所有事情(减去将表作为参数的复杂函数)都由 SWIG 完成。我也不能在 SWIG 中执行本机类方法。此外,这意味着我必须将我所有的用户数据创建代码包装在 Lua 中。谢谢,但这不是理想的解决方案。我真的很想让 SWIG 工作。
  • @Kaa 我不明白“我的所有对象都是在 C++ 中创建和绑定的”如何使这无关紧要。也许我根本不理解你的问题。 “SWIG 中的本机类方法”:这是什么意思。 “我将不得不在 Lua 中包装我所有的用户数据创建代码”:我不明白为什么。您是否尝试使用从 C++ 中的 C++ 对象派生的 Lua 对象?如果您可以尝试通过放置示例代码(一些 .cpp 和 .lua 来显示用法)来澄清(在您的问题中,而不是通过 cmets),我可能会发现 s/t 更相关。
  • 感谢您的代码!我明白,但在用户编码人员掌握它之前,我从来没有在 Lua 中持有 userdata 对象。如果您看到上面的示例代码 - 使用您的方法,我将不得不重命名那些 C++ 函数,然后在 Lua 中创建执行元表分配的包装器。
  • 谢谢,您的回答帮助很大。我按照您的指示编写了一些代码来修改元表并将 userdata 的个人查询表存储在 _G 表中,隐藏起来并使用 userdata 的内存位置进行索引。原始的 __index 和 __oldindex 被存储起来,并且在它之前编写了新的 index 和 newindex 函数。很酷的东西。
  • 很高兴听到这个消息。在 SO 上,您通过投票来感谢人们对您的问题和他们的 cmets 的回答,假设您当然发现它们很有用(而不是通过“谢谢” cmets)。如果有用的话,对其他答案进行投票也很重要,并且接受答案(并不意味着它是完美的,只是它是最有帮助的——在你的情况下,你应该接受你自己的答案,所以人们知道) 因为它可以帮助其他人快速找到最有用的答案。
【解决方案2】:

所以你在 C++ 中有一个 SoundObject 类,你将它导出到 Lua(不管是通过 SWIG 还是 tolua++ 还是手动)。您的应用程序运行一个 lua 脚本,该脚本创建一个 SoundObject 实例并添加一个属性(在 Lua 中)和一个方法(再次在 Lua 中),然后您希望能够使用该属性并从 C++ 调用该方法。类似的东西:

-- Lua script: 
sound = audio.loadSound("beep.mp3")
sound.scale = 0.2   -- this property exported by SWIG
sound.loop = true   -- this is Lua only
sound.effect = function (self, a) print(a) end  -- this is Lua only
sound:play()        -- this method exported by SWIG

// C++ pseudocode:
bind SoundObject* load_sound(string) to "audio.loadSound"
run Lua script 

并且您希望 play() 方法调查其他属性和方法(如循环和效果)并做一些事情。什么,我无法想象,希望这能让你知道如何更清楚地表达你的问题。

【讨论】:

  • 非常接近。我还希望 Lua 中的声音对象保持为 userdata 类型。目前 - SWIG 默认不允许我这样做。
  • 是什么让你认为它不允许 Lua 中的声音对象保持为 userdata 类型?像, lua_isuserdata 在 sound.effect 分配后返回false?还是 luaL_checkudata 变为 false?
  • 如果我使用您的初始解决方案,是的,在包装启用此功能后,类型变为表格。您如何建议我启用修改用户数据的功能,而无需像您建议的那样将其包装在表格中?
【解决方案3】:

按照 Schollii 的例子,我制定了一个解决方案……但牺牲更少。这不会把你的用户数据变成表格,所以你仍然有用户数据类型(我可以将它传递给其他接受用户数据的 C++ 函数)。 Schollii 的解决方案有效地将用户数据转换为表格包装器。

所以第一行使用变量“ground”——这是一个由 SWIG 包装的用户数据实例。在执行开始时执行此操作会更改该类型的所有实例的“地面”用户数据类型的元表,但每个实例都保留一个由用户数据的内存位置索引的个人表。该表位于 _G._udTableReg 表中。

-- Store the metatable and move the index and newindex elsewhere
mt = getmetatable(ground)
mt.__oldindex = mt.__index
mt.__oldnewindex = mt.__newindex

-- Store the global registry of tables associated with userdata, make a pointer to it in the metatable
_G._udTableReg = {}
mt._udTableReg = _G._udTableReg

-- Rewrite the new index function that looks in the udTableReg using 'self' as index before proceeding to use the old index as backup
mt.__index = function(self, key)
    local ret;
    local privateTable = mt._udTableReg[self]

    -- If the private table exists and has key, return key
    if privateTable ~= nil and privateTable[key] ~= nil then
        ret = privateTable[key]
    -- Use the old index to retrieve the original metatable value
    else ret = mt.__oldindex(self, key) end

    if ret == nil then return 0
    else return ret end
end

-- Try to assign value using the original newindex, and if that fails - store the value in
mt.__newindex = function(self, key, val)
    -- If old newindex assignment didn't work
    if mt.__oldnewindex(self, key, val) == nil then
        -- Check to see if the custom table for this userdata exists, and if not - create it
        if mt._udTableReg[self] == nil then
            mt._udTableReg[self] = {}
        end
        -- Perform the assignment
        mt._udTableReg[self][key] = val
    end
end

我仍然没有想出放置这个 Lua 代码的最佳位置,或者如何在不实际使用现有变量的情况下获取更优雅的方式来获取用户数据的元表。

【讨论】:

  • 希望我能提供更多帮助,但恐怕我只是不明白你想要做什么(但看看我的第二个答案,也许我更接近)。跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-09-12
  • 1970-01-01
  • 2021-09-30
  • 1970-01-01
  • 2010-10-13
  • 1970-01-01
  • 2022-12-18
相关资源
最近更新 更多