Как расширить пользовательские данные SWIG в коде Lua?
Я использую SWIG для привязки кода C++ к Lua. Пока это выглядит хорошо, но теперь мне нужно "обмануть" и расширить отдельные пользовательские данные из Lua, добавить настраиваемые поля и методы и т. Д.
Я не могу найти способ сделать это, работая в рамках директив SWIG. Я знаю, где в коде обертки происходит волшебство, но я не до конца понимаю, как работают __index и __newindex. Кроме того, SWIG использует __setitem и __getitem, который комментируется как "/* NEW: ищет __setitem() fn, это пользовательский набор fn */" - тоже не знаю, что это значит. Наконец, моя среда автоматически вызывает скрипт для привязки директив SWIG к оболочкам C++ перед каждой сборкой, поэтому изменение исходных кодов после этого очень утомительно, если я когда-либо решу перекомпилировать или добавить дополнительные привязки Lua.
Пока что мой единственный вывод - использовать toLua++, в котором эта функция задокументирована. Пожалуйста, помогите мне избежать многих переходных работ!
РЕДАКТИРОВАТЬ: Я также обнаружил, что API-вызов "lua_setuservalue" в 5.2 - это кажется полезным, но я не понимаю, где бы я назвал его среди всего кода привязки 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()
РЕДАКТИРОВАТЬ: улучшение! Я следовал инструкциям Шоллии и написал следующий код 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
РЕДАКТИРОВАТЬ: я реализовал решение из этого ответа: динамически добавлять членов в класс, используя Lua + SWIG
Проблема в том, что теперь мой тип объекта больше не userdata, это таблица. Это означает, что я больше не могу органически передавать его в качестве аргумента другим связанным функциям C++, которые принимают userdata в качестве аргумента. Любое решение этого? И я должен сделать это для каждой функции, которая возвращает объект пользовательских данных.
3 ответа
Я разработал решение, следуя примеру Шоллии... но меньше жертв. Это не превращает ваши пользовательские данные в таблицу, поэтому у вас все еще есть тип пользовательских данных (я могу передать их другим функциям 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, или как получить более элегантный способ получения метаданных пользовательских данных без фактического использования существующей переменной.
Я собираюсь быть неряшливым: в Lua таблица "наследуется" от другой таблицы, используя ее как метатабельную. Поэтому, если вы экспортируете C++ Foo в Lua и хотите получить "класс" Lua, производный от Foo, вы можете создать таблицу и установить для нее метатализируемое значение Foo. Затем, когда вы обращаетесь к новой таблице с полем, которого нет в таблице, она будет выглядеть в metatable, чтобы увидеть, есть ли она там. Пример псевдокода:
baseTable = {a=123}
assert( getmetatable(baseTable) == nil )
derived={b=456}
assert(derived.a == nil)
setmetatable(derived, baseTable )
assert(derived.a == 123)
BaseTable - это класс C++, экспортированный в Lua через SWIG, вы не можете изменить его метатабельность, но вы можете изменить таблицу, созданную в 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
Итак, у вас есть класс SoundObject в C++, вы экспортируете его в 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() исследовал дополнительные свойства и методы (например, цикл и эффект) и что-то делал. Что, я не могу себе представить, надеюсь, это даст вам идею, как более четко выразить вашу проблему.