如何在Lua代码中扩展SWIG的userdata?

我正在使用 SWIG 将 C++ 代码绑定到 Lua。到目前为止,效果很好,但现在我需要“欺骗”,在 Lua 中扩展单个 userdata,添加自定义字段和方法等。

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

到目前为止,我的唯一结论是使用 toLua++,它文档中有这个功能。请帮我避免很多转换工作!

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

编辑:为了澄清:

我通过具有 C 函数创建对象

SoundObj *loadSound(const char *name)
{
    return g_audio->loadSound(name); // 返回 SoundObj 指针
}

SWIG 通过在 *.i swig 定义中包括其原型来绑定此函数。

在 Lua 中,我编写如下代码:

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

编辑:进步!我遵循了 Schollii 的指示,并编写了以下 Lua 代码。这里,“ground”是之前使用绑定的 C++ 代码获取的 userdata。该代码存储了 swig 版本的 __index 和 __newindex,然后我重新创建这些功能,首先查询另一个("_other")表。

我目前的问题在于,存储在“_other”表中的新值在所有该类型的 userdata 对象之间共享。换句话说,如果 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

编辑:我实现了此答案中的解决方案:在 Lua + SWIG 中动态将成员添加到类

问题在于现在我的对象类型不再是 userdata,而是表。这意味着我不能将其作为参数自然地传递给其他需要 userdata 作为参数的绑定 C++ 函数。有解决方法吗?而且,我必须对返回 userdata 对象的每个函数进行此操作。

点赞
用户869951
用户869951

我这里会有点懒: 在 Lua 中,一个表格通过使用另一个表格作为元表来“继承”另一个表格。因此,如果你将 C++ Foo 导出到 Lua 并想要一个从 Foo 派生的 Lua “class”,你可以创建一个表格并将其元表设置为 Foo。然后,当你使用该 new table 访问一个不存在于表格中的字段时,它将在元表中查找是否存在该字段。以下是伪代码示例:

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
2014-01-06 05:41:49
用户355516
用户355516

我已经根据 Schollii 的例子制作了一个解决方案......但做出了更少的牺牲。 这并不会将您的用户数据转换为表,因此您仍然拥有用户数据类型 (我可以将其传递给其他需要用户数据的C++函数)。 Schollii 的解决方案实际上将用户数据转换为表包装器。

因此,第一行使用变量 "ground" - 这是由 SWIG 包装的一个用户数据实例。 在执行开始时这样做,会为 "ground" 用户数据类型的所有实例更改元表,但每个实例保留了一个由用户数据的内存位置索引的个人表。该表位于_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 代码的位置,也不知道如何更优雅地获取用户数据的元表,而不是实际使用现有变量。

2014-01-06 22:17:28
用户869951
用户869951

所以你有一个C++中的SoundObject类,你将其导出到Lua(无论是通过SWIG还是tolua++或手动方式)。你的应用程序运行一个Lua脚本,该脚本创建了一个SoundObject实例,并添加了一个属性(在Lua中)和一个方法(同样在Lua中),然后你希望能够从C++中使用该属性并调用该方法。类似于:

-- Lua脚本:
sound = audio.loadSound("beep.mp3")
sound.scale = 0.2   -- 这个属性由SWIG导出
sound.loop = true   -- 这个是仅在Lua中使用的
sound.effect = function (self, a) print(a) end  -- 这个是仅在Lua中使用的
sound:play()        -- 这个方法由SWIG导出

// C++伪代码:
将SoundObject* load_sound(string)与“audio.loadSound”绑定
运行Lua脚本

而且你希望play()方法调用其他属性和方法(如loop和effect)并执行某些操作。我无法想象是什么操作,但希望这能为你更清楚地表达问题提供帮助。

2014-01-06 23:34:54