Как вернуть управляемый объект из другого домена приложений при использовании управляемого C ++? - PullRequest
4 голосов
/ 17 декабря 2010

Я использую неуправляемую библиотеку, написанную на C ++. Библиотека имеет управляемую оболочку C ++ (CLI), и я использую библиотеку из управляемого кода. Неуправляемая библиотека (включая оболочку CLI) написана третьей стороной, но у меня есть доступ к исходному коду.

К сожалению, управляемая оболочка плохо работает с доменами приложений. Неуправляемая библиотека создает потоки и обращается к управляемому коду из этих потоков. Это приводит к проблемам, когда управляемый код выполняется в домене приложения не по умолчанию. И мне нужны кросс-доменные вызовы, чтобы можно было проводить модульное тестирование с использованием стандартных инструментов.

Чтобы решить эту проблему, я ввел делегатов в управляемую оболочку и использовал Marshal.GetFunctionPointerForDelegate(), чтобы получить указатель на функцию, позволяющую успешно выполнять вызовы между доменами AppDomain.

Обычно это работает хорошо, но теперь у меня возникла новая проблема. У меня следующая последовательность событий.

Unmanaged thread ->
Unmanaged code 1 ->
Managed wrapper 1 ->
AppDomain transition (via delegate) ->
Managed wrapper 2 ->
Unmanaged code 2 ->

Я упустил некоторые подробности о том, как библиотека позволяет вам переопределить некоторые функции в управляемом коде выше управляемая обертка 2 , что является главной целью сделать переход от неуправляемого к управляемому в первую очередь.

В конечном итоге неуправляемый код 2 должен будет вернуть неуправляемый объект в неуправляемый код 1 .

Без делегата и содержимого AppDomain управляемая оболочка 2 обернет неуправляемый объект и вернет его в управляемую оболочку 1 , которая затем передаст состояние неуправляемому объекту, используемому неуправляемый код 1 .

К сожалению, мне трудно вернуть управляемый объект через переход AppDomain.

Я подумал, что должен был сделать управляемый объект, передаваемый через границу AppDomain, сериализуемым. Однако это нелегко сделать. Вместо этого я создал простой класс, в котором я могу хранить тип объекта, который я хочу передать, и строку, представляющую состояние объекта. И Type, и String легко маршалируются, и, к счастью, я всегда могу создать экземпляр объекта, используя конструктор по умолчанию, а затем инициализировать его из строки:

// Message is the base class of large hierarchy of managed classes.

[Serializable]
// Sorry for the "oldsyntax", but that is what the C++ library uses.
__gc class SerializedMessage {
public:
  SerializedMessage(Message* message)
    : _type(message->GetType()), _string(message->ToString()) { }

  Message* Create() {
    Message* message = static_cast<Message*>(Activator::CreateInstance(_type));
    message->InitializeFromString(_string);
    return message;
  }

private:
  Type* _type;
  String* _string;
};

В управляемой оболочке 2 Я возвращаю SerializedMessage, а в управляемой оболочке 1 Затем я получаю копию исходного сообщения, вызывая SerializedMessage::Create. Или, по крайней мере, это было то, чего я надеялся достичь.

К сожалению, переход AppDomain завершается с ошибкой InvalidCastException с сообщением Невозможно привести объект типа 'SerializedMessage' к типу 'SerializedMessage'.

Я не уверен, что происходит, но сообщение об ошибке может указывать, что к объекту SerializedMessage обращаются из неправильного домена приложения. Однако весь смысл использования Marshal.GetFunctionPointerForDelegate заключается в возможности вызова через домены приложений.

Я также пытался извлечь SerializedMessage из MarshalByRefObject, но затем я получил и InvalidCastException с сообщением Невозможно привести объект типа 'System.MarshalByRefObject' к типу 'SerializedMessage'.

Что мне нужно сделать, чтобы иметь возможность передавать управляемый объект обратно из другого домена приложений, когда я вызываю указатель, возвращаемый Marshal.GetFunctionPointerForDelegate()?

Комментарии к принятому ответу

Правильная часть о том, как сборка была загружена во второй домен приложений.

Код C ++ выполняется в домене приложений по умолчанию, который контролируется модулем модульного тестирования. Управляемая сборка загружается во второй домен приложений, созданный этим модулем тестирования модулей. Я использую Marshal.GetFunctionPointerForDelegate для включения управляемых вызовов C ++ от первого до второго домена приложений.

Изначально я получил несколько FileNotFoundException, пытающихся загрузить свою управляемую сборку, и, чтобы обойти это, я скопировал свою управляемую сборку в AppBase программы запуска модульных тестов. Я все еще немного сбит с толку, почему .NET настаивает на загрузке самой сборки, которая выполняется, но решил поработать над этой проблемой позже и просто скопировал отсутствующий файл в качестве кладжи.

К сожалению, это загружает один и тот же тип из двух разных копий одной и той же сборки в двух разных доменах приложений, и я предполагаю, что это является основной причиной InvalidCastException.

Мой вывод заключается в том, что яневозможно использовать «стандартный» модуль модульных тестов для тестирования управляемой библиотеки C ++, потому что обратные вызовы из этой библиотеки получены с неправильного домена приложений, который я не могу контролировать.

1 Ответ

1 голос
/ 17 декабря 2010

Невозможно привести объект типа 'SerializedMessage' к типу 'SerializedMessage'.

Я бы сосредоточился на этой проблеме для сути вашей проблемы.Реальное сообщение должно быть "типа Foo.SerializedMessage для типа Bar.SerializedMessage".Другими словами, здесь задействованы два типа, из различных сборок.Идентификатор типа .NET - это не просто имя класса, он также включает полное имя сборки.Которое является отображаемым названием сборки, версией и культурой сборки.Контрмера DLL Ад.

Проверьте, как создаются ваши сборки, и убедитесь, что SerializedMessage отображается только один раз в любой из ваших сборок.Это также может быть вызвано тем, как сборка была загружена во второй домен приложений, например, с помощью LoadFile ().Он загружает сборки без контекста загрузки, и любые типы, загруженные из такой сборки, даже не совместимы с точно таким же типом из той же самой сборки, которая была загружена нормально.), неуправляемые указатели не соблюдают границы AppDomain.Хотя я и понятия не имею, зачем вы его используете.

...