Управляемый WPF и родной код взаимодействия. WPF размещен в ServicedComponent
Я пытаюсь разместить приложение WPF в ServicedComponent. У меня есть библиотека WPF, которую мне нужно использовать из нативного кода. Для достижения этой цели я создал компонент COM+ outproc, поместил в него все вызовы WPF и вызвал этот компонент из собственного кода следующим образом:
// Managed
[ComVisible(true)]
public class HpPcKeyboardSrv : ServicedComponent, ISrv
{
...
}
// Native
CComPtr<ISrv> spISrv;
hr = spISrv.CoCreateInstance(__uuidof(MySrv), nullptr, CLSCTX_SERVER);
ATLVERIFY(SUCCEEDED(hr));
hr = spISrv->WPFCommand();
ATLVERIFY(SUCCEEDED(hr));
Он работает безупречно в качестве прототипа, но когда я добавляю реальную функциональность WPF, все начинает разваливаться.
Я не могу создать окно WPF в COM+ ServicedComponent из-за печально известного исключения WPF, "The calling thread must be STA, because many UI components require this"
, Одним из решений является использование Dispatcher. Проблема в том, что диспетчер WPF, Dispatcher.CurrentDispatcher
не вызывает функцию в BeginInvoke()
:
public void WPFCommand()
{
Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate
{
System.Threading.ApartmentState aptStateLocal = Thread.CurrentThread.GetApartmentState();
Debug.WriteLine("Spawned thread apartment: {0}", aptStateLocal);
_Window = new Window();
});
}
Другой вариант заключается в использовании Application.Current.Dispatcher
, Проблема с этим подходом состоит в том, что в этом вызове Application.Current
имеет значение null, поэтому диспетчер недоступен.
ХОРОШО. Следующее, что нужно попробовать, это создать угрозу в модели STA следующим образом:
public void WPFCommand()
{
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
{
Thread thread = new Thread(() =>
{
System.Threading.ApartmentState aptState = Thread.CurrentThread.GetApartmentState();
Debug.WriteLine("Spawned thread apartment: {0}", aptState); // <- Still MTA
// !!! If _Window is the member of the class, the thread will be MTA
// !!! otherwise STA
_Window = new Window();
System.Windows.Threading.Dispatcher.Run();
});
Debug.WriteLine("Thread apartment state1: {0}", thread.GetApartmentState());
thread.SetApartmentState(ApartmentState.STA); // <- even though set as STA
Debug.WriteLine("Thread apartment state2: {0}", thread.GetApartmentState());
thread.IsBackground = true;
thread.Start();
thread.Join();
}
}
Этот код помогает частично. Поскольку порождающий поток, которому была задана модель STA, в любом случае вызывается в MTA, если _Window является членом класса (но если нет, то STA), и так new Window()
выдает то же самое исключение "должен быть STA".
На данный момент я полностью застрял. Как я могу на самом деле создавать элементы WPF в ServicedComponent? Или как я могу взаимодействовать между собственным кодом и WPF? Любые идеи приветствуются.
ОБНОВЛЕНИЕ: Странно, назначение (_Window = new Window()) влияет на модель потоков. Если _Window является членом класса, модель протектора по-прежнему MTA. Если это локальная переменная, модель потоков меняется на MTA. Кажется, что _Window должен быть назначен каким-либо другим способом как член класса.
1 ответ
Я могу быть на пути к решению или полностью сойти с пути-
http://drwpf.com/blog/2007/10/05/managing-application-resources-when-wpf-is-hosted/
По сути, вышеприведенный подход загружает все словари ресурсов и создает среду WPF. Пожалуйста, отметьте "Управление коллекцией ресурсных словарей в коде и объединение их на уровне элемента".
Поэтому после этого вы можете просто вызывать свои окна из WPFCommand, не беспокоясь о STA\MTA.