Есть ли способ вернуть значение dotNetObject из пользовательского плагина 3ds Max?
Вопрос
У меня есть пользовательский плагин для 3ds Max, который взаимодействует с некоторым управляемым кодом на серверной части.В некоторых случаях я хотел бы переслать управляемый объект в MAXScript для прямого взаимодействия, т. е.возвращает обернутый объект из одной из моих функций.
MAXScript способен относительно хорошо манипулировать управляемыми объектами напрямую с помощью другого плагина (msxdotNet), входящего в комплект Max (я использую 3ds Max 2008).Он в основном обертывает объект и использует отражение для поздних связанных вызовов, но он полностью самодостаточен и не имеет никакого доступа к sdk.Сама библиотека dll плагина также не предоставляет ничего, кроме минимального интерфейса, требуемого Max для добавления нескольких скриптовых классов верхнего уровня.
Скриптовые классы позволяют создавать экземпляр нового объекта с помощью конструктора
local inst = (dotNetObject "MyPlugin.MyClass" 0 0 "arg3")
В моем случае у меня уже есть экземпляр объекта, который я хотел бы использовать.
Есть ли способ создать экземпляр оболочки dotNetObject из моего плагина, чтобы вернуться к максимальному значению?
В идеале, я хотел бы иметь вспомогательную функцию с сигнатурой (C ++ / CLI), подобной:
Value* WrapObject(System::Object ^obj);
Некоторые основные гарантии, которые я могу дать:
- Плагин msxdotNet уже загружен.
- Плагин msxdotNet и мои управляемые сборки находятся в одном домене приложения.
Исходный код для плагина msxdotNet является включен в качестве образца sdk, но для удобства управления изменять его и перекомпилировать не представляется возможным.
Решение
Я решил эту проблему, используя тот факт, что любой объект CLR, обернутый dotNetObject, автоматически обернет возвращаемые значения (результаты метода и значения свойств) другой оболочкой.Это даже относится к статическим методам и свойствам типов CLR, обернутых dotNetClass.
Допустим, у меня уже есть метод в моем плагине, который позволяет мне выполнять произвольный MAXScript:
Value* EvalScript(System::String ^script);
Теперь мне просто нужно сериализовать объект в строку и снова вернуться к активному объекту (ссылка на тот же объект, а не просто копия!).
Я делаю это, хватая GCHandle
объекта, используя GCHandle::ToIntPtr
чтобы преобразовать его во что-то блиттабельное и используя GCHandle::FromIntPtr
материализовать тот же объект в другом контексте.Конечно, я делаю это в процессе (и в том же домене приложения), иначе это не сработало бы.
Value* WrapObject(System::Object ^obj)
{
GCHandle handle = GCHandle::Alloc(obj)
try
{
return EvalScript(System::String::Format(
L"((dotNetClass \"System.Runtime.InteropServices.GCHandle\").FromIntPtr (dotNetObject \"System.IntPtr\" {0})).get_Target()",
GCHandle::ToIntPtr(handle));
}
finally
{
handle.Free();
}
}
Комментарий, который у меня есть, объясняющий это в реальном коде, более чем в 10 раз длиннее самого кода.