0

我正在使用 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 dynamic to a class using Lua + SWIG

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

4

3 回答 3

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
于 2014-01-06T05:41:49.643 回答
1

因此,您在 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() 方法调查其他属性和方法(如循环和效果)并做一些事情。什么,我无法想象,希望这能让你知道如何更清楚地表达你的问题。

于 2014-01-06T23:34:54.077 回答
0

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

所以第一行使用变量“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 代码的最佳位置,或者如何在不实际使用现有变量的情况下获得一种更优雅的方式来获取用户数据的元表。

于 2014-01-06T22:17:28.683 回答