Managed WPF and native code interop. WPF hosted in ServicedComponent

260 views Asked by At

I'm struggling to host WPF application in ServicedComponent. I have a WPF library, which I need to use from native code. In order to reach this goal, I created a outproc COM+ component, put all WPF calls in it and call this component from the native code as follows:

// 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));

It works flawlessly as a prototype, but when I add actual WPF functionality, everything starts falling apart.

I cannot create WPF window in COM+ ServicedComponent because of the infamous WPF exception, "The calling thread must be STA, because many UI components require this". One of the solutions is to use Dispatcher. The problem is that WPF dispatcher, Dispatcher.CurrentDispatcher doesn't call the function in 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();
        });
    }

Another option is to use Application.Current.Dispatcher. The problem with this approach is that in this call Application.Current is null, so no Dispatcher available.

OK. The next thing to try is spawning a threat in STA model as follows:

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();
    }
}

This code helps partially. Since the spawning thread, which has been set to STA model, gets called in MTA anyway if _Window is the class member (but it's STA if not) and so new Window() throws the same "must be STA" exception.

At this point I totally got stuck. How can I actually create WPF elements in ServicedComponent? Or how can I interact between native code and WPF? Any ideas are appreciated.

UPDATE: Strangely, the assignment (_Window = new Window()) influence the threading model. If _Window is a member of the class, the treading model is still MTA. If it's a local variable, the threading model gets changed to MTA. It seems that _Window should be assigned in some other way as a member of the class.

1

There are 1 answers

0
ShipOfTheseus On

I may be right on track for a solution or completely off the track-

http://drwpf.com/blog/2007/10/05/managing-application-resources-when-wpf-is-hosted/

Basically, above approach loads all the resource dictionaries and creates WPF environment. Please check "Manage a Collection of Resource Dictionaries in Code and Merge them at the Element Level".

So, after this, you can just call your windows from WPFCommand without having to worry about STA\MTA.