3

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.

Anton K
  • 4,658
  • 2
  • 47
  • 60
  • The last piece of code works for me. If you take it as is and put it in a Console App, the spawned thread apartement is indeed STA as it should. Are you sure it's the exact same code you have in your program? – Simon Mourier Jun 15 '14 at 05:24
  • It's exactly the same code. I got to that point too and that's what I found. See my update. If _Window is a member of the class the thread is MTA no matter what, if it's local variable, the thread model actually changed to STA. It seems that _Window should be assigned in some other way. – Anton K Jun 15 '14 at 05:38
  • Please share a repro code. This code http://pastebin.com/e41sTDiB shows the spawned thread is STA. – Simon Mourier Jun 15 '14 at 08:42
  • @SimonMourier. Thanks for your sample, but it is not ServicedComponent. See my code here - http://pastebin.com/kDYdZfbr – Anton K Jun 15 '14 at 18:19
  • 1
    I can reproduce now. Before digging further, there are at least two ways to fix this: 1) mark the RequiredServer class as Apartement instead of letting the default registration value (which leads the default thread to be MTA instead of STA) or 2) remove the ServicedComponent derivation (but you can keep the COM+ server activation - why do you need ServicedComponent in the first place). – Simon Mourier Jun 16 '14 at 07:58

1 Answers1

1

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.

ShipOfTheseus
  • 234
  • 1
  • 10