我正在使用SWIG将C ++代码绑定到Lua。到目前为止它看起来不错,但现在我需要“欺骗”并从Lua中扩展单个用户数据,添加自定义字段和方法等。
在SWIG的指令中工作时,我无法找到完成它的方法。我知道魔术发生在包装器代码中的哪个位置,但我不完全理解__index和__newindex是如何工作的。此外,SWIG使用__setitem和__getitem,其注释为“/ * NEW:查找__setitem()fn这是用户提供的set fn * /” - 不知道这意味着什么。最后,我的环境会在每次构建之前自动调用脚本将SWIG指令绑定到C ++包装器,因此如果我选择重新编译或添加更多Lua绑定,那么之后修改源代码非常繁琐。
到目前为止,我唯一的结论是与Lua ++一起使用,其中记录了此功能。请帮我避免很多过渡工作!
编辑:我还发现5.2中的“lua_setuservalue”API调用 - 它看起来很有用,但我不明白在所有SWIG绑定代码中我将它称之为。编辑:要清理:
我通过C函数
创建对象SoundObj *loadSound(const char *name)
{
return g_audio->loadSound(name); // Returns SoundObj pointer
}
此功能由SWIG绑定,方法是将其原型包含在* .i swig定义中。
在Lua我写这样的代码:
sound = audio.loadSound("beep.mp3")
sound.scale = 0.2
sound:play()
编辑:进步!我按照Schollii的指示编写了以下Lua代码。这里,“ground”是之前使用绑定C ++代码获取的用户数据。代码存储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
编辑:我从这个答案实施了解决方案:Add members dynamically to a class using Lua + SWIG
问题是现在我的对象类型不再是userdata,而是一个表。这意味着我不能再将它作为参数有机地传递给以userdata作为参数的其他绑定C ++函数。对此有何解决方案?我必须对每个返回userdata对象的函数执行此操作。
答案 0 :(得分:1)
我将在这里马虎:在Lua中,一个表“继承”另一个表,将其用作metatable。因此,如果您将C ++ Foo导出到Lua并想要从Foo派生的Lua“类”,您可以创建一个表并将其metatable设置为Foo。然后,当您使用表中不存在的字段访问新表时,它将查找metatable以查看它是否存在。示例伪代码:
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 ++类,你不能改变它的metatable但你可以改变你在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
答案 1 :(得分: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()方法调查其他属性和方法(如循环和效果)并执行某些操作。什么,我无法想象,希望这会让你知道如何更清楚地表达你的问题。
答案 2 :(得分:0)
按照Schollii的例子,我已经制定了一个解决方案......但牺牲更少。这不会将您的userdata转换为表,因此您仍然具有userdata类型(我可以将其传递给其他带有userdata的C ++函数)。 Schollii的解决方案有效地将用户数据转换为表格包装。
所以第一行使用变量“ground” - 这是由SWIG包装的userdata实例。在执行开始时执行此操作会改变该类型的所有实例的“地面”用户数据类型的元表,但每个实例保留由userdata的内存位置索引的个人表。该表位于_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代码的最佳位置,或者如何在不实际使用现有变量的情况下获得更优雅的方法来获取userdata的metatable。