Управляемый 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.

Другие вопросы по тегам