Я использую 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 ++. В этом коде хранятся версии __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 в качестве аргумента. Любое решение этого? И я должен сделать это для каждой функции, которая возвращает объект пользовательских данных.
Я собираюсь быть неряшливым: в 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 () исследовал дополнительные свойства и методы (например, цикл и эффект) и что-то делал. Что, я не могу себе представить, надеюсь, это даст вам идею, как более четко выразить вашу проблему.
Я разработал решение, следуя примеру Шоллии … но меньше жертв. Это не превращает ваши пользовательские данные в таблицу, поэтому у вас все еще есть тип пользовательских данных (я могу передать их другим функциям 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, или как получить более элегантный способ получения метаданных пользовательских данных без фактического использования существующей переменной.