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.
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.