Update: March 11, 2014

I have published a description of a new version of Baktun Shell in the MSDN Magazine. It also has been published on GitHub. In fact, it is a completely different project created from scratch on the same principles as Baktun Shell. This version is much closer to an actual production system. It lets the host and the plugins call each other, takes care of a "sudden death" of the shell or a plugin, adds more robust error logging, et cetera, et cetera.

What is Baktun Shell

Baktun Shell is a WPF application that hosts child windows in a separate process. It

Locates plugin assemblies on disk.

Lets user choose which plugin to load.

Runs the plugin in its own process.

Instantiates a UserControl from the plugin and displays it as a tab of the tab control.

Why is This a Good Idea?

Hosting in another process is useful for a number of reasons:

Reliability through isolation: a plugin runs in its own address space and cannot mess with other plugin's data

Unloading at will: a plugin can be safely unloaded at any time.

Mixing 32-bit and 64-bit code: as each process is either 32-bit or 64-bit, it is not possible to mix two types of code in a single process.

Other benefits of process isolation that are not yet implemented by this demo are:

Separate configuration for each plugin: it is possible to give each plugin its own app.config file.

Mixing CLR versions: by applying per plugin configuration it should be possible to run one plugin as .NET 4.0 while another as .NET 4.5, etc.

What is Baktun?

Baktun is a time period in
ancient Mayan calendar roughly equal to 400 years. The end of the 12th
Baktun on December 20, 2012 caused widespread rumors about (yet another) end
of the world. As I was finishing up the shell code on December 21st,
I noticed that the end of the world did not happen, and the 13th Baktun
has happily begun. In honor of this event, I decided to call my program Baktun Shell. After all,
we won't have another chance to celebrate beginning of a Baktun for about 400 more years.

How to Use the Shell

Download and unzip the project and compile the solution with Visual Studio 2010 or 2012. Upon startup the shell analyzes the assemblies
located in its binary directory and lets you choose which assembly and which class to load. Only classes that derive from UserControl
are shown. The shell process itself is 32-bit. A plugin can be loaded as 32-bit or 64-bit.
In DEBUG mode plugin processes are created with console windows that show some diagnostic information. In RELEASE modes
these windows are hidden.

The following projects are part of the shell core: Shell, Interfaces, PluginHost, PluginHost64.

SamplePlugin project contains a number of simple plugins:

SamplePluginControl

A user control with gradient background

PluginWithInput

A plugin with some text boxes and message boxes

BitnessCheck

A plugin that shows whether it is 32 or 64-bit and that can allocate memory in large quantities

I have also included two third party applications from CodePlex: 3D MoleculeViewer and Smith HTML Editor. The former required
slight modifications to convert main window to a UserControl. The latter is used as is, since it is already a user control.

This raw scheme does not quite work out of the box, but with a little tweaking it can be used successfully
to marshal WPF controls between processes.

Existing Plugin Frameworks and Isolation

A scenario where a GUI application is broken up into shell (host) and plugins (add-ons, extensions, modules)
is not new. Over the years a number of frameworks were developed to facilitate this:
OLE,
CAB,
MAF,
MEF, and
Prism
to name just a few. Of these only MAF, MEF and MOF Prism are relevant to WPF applications.

When a shell loads a plugin, it has three sensible choices for plugin isolation:

Load plugin assemblies into the shell's AppDomain, i.e. no isolation,

Run plugin in the shell process, but in separate AppDomain,

Run plugin in its own process,

followed perhaps by "run plugin on another machine", "run plugin in another country", and "run plugin on another planet",
but I digress here.

Higher level of isolation typically means more plumbing and more overhead, but also more degrees of freedom and more reliability. E.g.
if we want to unload our plugins at will, we must use separate AppDomains. AppDomains
provide certain level of protection against data corruption and failures, but processes provide even better protection.
If we want to mix 32-bit and 64-bit plugins, or mix different versions of the CLR, we must use separate processes.

Unfortunately, neither MEF nor Prism provide isolation support out of the box. However, see
Piotr Włodek's post
regarding possible isolation solution for MEF. MAF does support isolation, and there is even a
rough sample for cross-process
WPF components, with some context given in
this thread.

The biggest trouble with MAF is that it is very, very complex. Baktun Shell uses MAF's mechanism for marshalling
WPF controls, but bypasses the rest of the MAF pipeline model. This makes it much simpler and easier to work with.

Baktun Shell Inner Workings

Loading a Plugin

Shell's main window contains a standard TabControl slightly enhanced with the "close" button for each tab.
When the user clicks on the "Load" button, main window asks PluginHostProxy class to create a new Plugin
instance and creates a tab for it. PluginHostProxy class is responsible for spinning off and communicating to
the child process that will host the plugin.

Unloading a Plugin

When user clicks on the [x] button or when the whole application is closed, MainWindow will remove
the plugin from the tab control and will call Dispose() on it. This alerts the PluginHostProxy,
that asks the plugin host process to terminate itself.

Spinning Off Plugin Host Process

When PluginHostProxy receives a request to load plugin, it starts a new process. The process
executable is either PluginHost.exe or PluginHost64.exe, depending on the requested bitness.
The process receives in its command line unique process name based on a GUID, e.g.

PluginHost64.exe PluginHost.f3287246-6b77-48de-826c-6d383c42124e

The plugin host process sets up a remoting service of type PluginHostLoader
listening on the URL ipc://PluginHost.f3287246-6b77-48de-826c-6d383c42124e/PluginHostLoader.
When the remoting server is ready, the plugin host signals a named "ready" event. In this case
the name of the event would be "PluginHost.f3287246-6b77-48de-826c-6d383c42124e.Ready".

After receiving the "ready" signal, PluginHostProxy instance in the shell process
requests a remoting object of type IPluginLoader at
ipc://PluginHost.f3287246-6b77-48de-826c-6d383c42124e/PluginHostLoader, where IPluginLoader
is defined as follows:

PluginHostProxy then makes a call to IPluginLoader.LoadPlugin() with plugin assembly
and type name. The PluginLoader class in the context of the remote process loads the requested assembly,
creates instance of requested type, converts it to INativeHandleContract and returns it back to the shell
process. The shell process then converts INativeHandleContract to a FrameworkElement, makes
it part of a new Plugin instance and adds it to the main tab control.

I did consider a reverse arrangement, when the shell sets up a remoting server and the
plugin host makes a call. This eliminates the need for "ready" event, but it creates a
difficulty with error reporting. Plugin creation errors, if any, are reported to the
plugin host, and it has no easy way of reporting them back to the shell. This definitely can
be fixed, but overall solutions seems more complicated than the solution when the shell "drives".
Besides, the plugin host process will have to implement some kind of server anyway, so the shell
could tell it to terminate: creating TerminateProcess() is just not cool.

Terminating Plugin Process

This one is much easier. When a plugin is disposed, it will signal Disposed event
to which its parent PluginHostProxy is subscribed. PluginHostProxy
will then call IPluginLoader.Terminate() which gracefully ends plugin host process.

Peculiarities of Cross Process Remoting

Initializing Remoting Server

We are forced to use Remoting and not WCF, because we want to marshal INativeHandleContract,
which is not marked with [ServiceContract] attribute. Therefore, WCF will not agree to marshal it.

The most common form of remoting I encountered so far was remoting between two AppDomains in the same process.
Cross process remoting is a little different in certain aspects. In particular, you must manually initialize
your channels and register services. While doing that, you must keep in mind that returning
MarshalByRefObject from a method call is not allowed by default. To enable it, one must use
a binary formatter with TypeFilterLevel set to Full, see code below.

Another hurdle is that the URL on which the server will listen is defined as an element of
a properties hashtable with a magic name of "portname". In our case "portname" is the unique
name passed to the host process by the shell, e.g. PluginHost.f3287246-6b77-48de-826c-6d383c42124e.
We register a well known service of type PluginLoader at the URI PluginLoader
with Singleton activation. The remoting system will create an instance o PluginLoader
on our behalf when first call to that service is made.

Since our remoting channel is IPC channel, our "portname" is PluginHost.f3287246-6b77-48de-826c-6d383c42124e,
and our service URI is PluginLoader, the full URI for the service as used on the shell side is

Client Side Type Casts and Internal Method Calls

Another peculiarity of cross-process remoting is that it handles type conversions on the client side
in an unexpected way. To illustrate this, let me start from the beginning of the ordeal that led to his discovery. In the PluginLoader
class I used to have the following code:

What happened here is that we returned a INativeHandleContract from the plugin host process, and
it could not be converted back to FrameworkElement on the client side. The failure occurred
within the constructor of an internal class MS.Internal.Controls.AddInHost. With a little help from
.NET reflector it turned out that the offending code looks like this:

The reason this code fails is as follows. When the shell calls PluginLoader.LoadPlugin() via remoting,
it receives back an INativeHandleContract reference. However, the remoting system retains information
about its real type in the remote process. The "as" conversion on line 4 succeeds, and the "if" condition on line 5 is true.

When on line 7 we make a call to AddInHwndSourceManager.RegisterKeyboardInputSite(), remoting
realizes that this is an internal method call, and such calls are not allowed to go cross-process for security reasons.
In other words, it is OK for the client to convert the proxy to an internal type, but it is not OK to call any internal methods.
Note that this limitation does not apply when calling a different AppDomain within the same process. This is why this code works
fine with AppDomains.

The solution to this problem is to return an object that implements INativeHandleContract, but does
not inherit from AddInHwndSourceWrapper. For this purpose I created the NativeHandleContractInsulator
class. This is a simple decorator that forwards all its methods to a real INativeHandleContract. Its only
purpose in life is to prevent unwanted type casts on the client side.

The working implementation of the PluginLoader.LoadPlugin() therefore looks like this:

Lower CLR version of plugin host to 3.5 and allow to specify a CLR version for a plugin: 3.5, 4.0, 4.5, ...

Add ability to load plugins from arbitrary locations on disk.

Improve error reporting when PluginHost process crashes.

Conclusion

Hosting a WPF window in another process requires some plumbing, but it works surprisingly well. If your task
is limited to visual integration, Baktun shell may be all you need, perhaps with some improvements specified in
the previous section. If your application simply cannot be fit into a single process due to conflicting requirements
(e.g. "module X must interact with this old 32-bit code, but module Y needs 10G of memory"), such multi-process solution may be the only reasonable way out.

Share

About the Author

Ivan is a hands-on software architect/technical lead working for Thomson Reuters in the New York City area. At present I am mostly building complex multi-threaded WPF application for the financial sector, but I am also interested in cloud computing, web development, mobile development, etc.

Comments and Discussions

Hello Ivan,
it's such a amazing project, thank you very much.
I have only one question:
I would like to load a plugin (user control) which include usercontrols from surface sdk 2.0.
There comes always an error message if i use some of this controls.
The error appears always on the same position with the function:
"Activator.CreateInstance(assembly, typeName)"
If I use "normal" controls - everything is fine...
I include everywhere the references but nothing happend... hope you can help me!
For example if i include a button to sample plugin:

First of all, thanks for this wonderful & detailed post. Hats off to you..

Have you considered how things with span out when the Plugin App needs a modal popup. Since there is no owner window for the plugin. I needed to create a separate app in plugin code as some styles needed to be loaded into resources for the plugin.

Because of this the modal popup/dialogs in the plugin were no more modal as there was no owner window.

One thing am trying is to send the Window from Host process to child to set it as Application.Current.MainWindow. Not sure if this the right approach or if there is a better alternative to solve this.

great work at all! But i have one question. Your example works well. But when i export this to my own solution i got an error on client side: Hosted HWND must be a child window of the specified parent. I add FrameworkElement also to an TabControl. What could be the reason for this error message?

I previously worked on a project when our WPF app had a supporting update and monitor service in a separate process, which used WCF for communication. In regards to dealing with orphaned plugins, one option would be to have a heartbeat where the hosts and plugins checked each other, and plugin could determine if the host was still alive.

I'm trying to make this concept work with an existing Prism Shell we have, and I'm almost there. By tying into a couple Prism interface hooks, I have what you've done here working like a charm, except one part. When the INativeHandleContract is cached on the remote side (so, when the user configures the view to be a singleton or when INavigationAware.IsNavigationTarget returns true on the remote process side of Prism), the view flickers in the main region only when re-sizing, and is otherwise not visible . It's odd, because the view (AddInHost) shows up fine the first time it's navigated to, but on the second navigation (after prism or the container on the remote process cache the view (AddInHost), it only flickers for a moment when re-sizing and then disappears... not sure if you have any suggestions. Any insight is welcome!

It may have to do with the fact that AddInHost is IDisposable. When first usage is over, it will be Disposed and is not supposed to be used again. On the other hand, it does not look like I actually call Dispose() on the AddInHost It is hard to guess what's going on, especially without seeing the code. Can you send me (a link to) an actual solution that reproduces the problem?

We ended up having to force disposal of the ms.internal.controls.addinhost that comes across from the other process by setting it = null and the forcing the garbage collector to dispose it before the view is shown again via a GC.Collect(). This is done in the hook we used in IRegionNavigationContentLoader to load the view from the remote plugin.

I do not see your message with the link here, but I got it in the mail.
Quick investigation with Spy++ shows that upon second load your child window lacks WS_VISIBLE style, i.e. it is hidden. I am not sure who hides it and why, probably something funky is going on when it becomes detached from the parent... WPF is simply not designed for this kind of stuff - Baktun Shell in its original form is already pushing the limit. I would start by trying to call ShowWindow() on it and seeing what happens.

Hi.
I have looked into your project and I find it very interesting for a new application I have started working on. My goal is to integrate several WPF independant applications within one common shell / window. With some modifications I can now successfully host another complex application (which has plenty of dll-references) living in a separate directory. It looks very promising

I am a bit worried, though, if this is a good choice for the future. Since Baktun Shell uses .NET Remoting, I did some research on the subject. I found a few links that indicate that .NET Remoting is considered legacy technology that possibly will be deprecated in the future:http://msdn.microsoft.com/en-us/library/72x4h507%28VS.85%29.aspx[^]

Yes, your question is very valid. I considered this, and here are my thouhgts:

0. Everything we use today will be deprecated at some point in the future. It most definitely will happen. The only question is how soon. Looking at the big scheme of things, WPF is also a legacy technology. Neither WPF nor remoting is part of Metro/WinRT. Having said that, I strongly doubt that Microsoft will remove remoting from, say, .NET 5. If, of course, there will be a .NET 5. It is simply not worth the effort.

1. I am using remoting mainly because MAF uses remoting. If I went with WCF, I would have to come up with my own mechanism for marshaling window handles.

2. Remoting is much deeper integrated with .NET framework that WCF. In order to remote something you only need to mark it as [Serializable] or MarshalByRefObject. Many BCL types are therefore remotable out of the box. In contrast, WCF needs all these [ServiceContract], [OperationContract] attributes.

3. Remoting has poorer performance, but in general we are willing to live with it. Premature optimization is the root of all evil.

4. It's a plumbing. If push comes to shove, I can replace it, but at a cost: all client types will have to be decorated with WCF attributes.

Of course, one may come up with as many (and perhaps more) arguments against using remoting. As it often the case with software development, there is no one right way.

I'm trying to use your sample in my project, but I found a problem and I'm asking you a way to solve it. I'm wondering how Host and plugins can comunicate each other. I read your answers in both codeproject and your site, but I didn't understand how to do it.

My project has a host and some plugins.
These plugins inherit from a Interface (IMyInterface).
I put this interface in Interfaces dll.

Everithing works fine, but, in the PluginLoader, in the function "CreateOnUiThread" when the instruction "FrameworkElementAdapters.ViewToContractAdapter(control as FrameworkElement);" is called, the plugin looses the Interface (IMyInterface) and so my host looses the ability to comunicate with the plugins.

here are, of course, multiple ways to achieve what you want.
One way would be to modify the IPluginLoader/PluginLoader class.

Now it returns a plain INativeHandleContract and that's the only thing that the shell can use. You can modify it to return an object with two properties, one being plugin logic and another being plugin window. E.g., something along the lines of:

I have a question on how to communicate between the processes. So, if I want to send a message from Shell to the SamplePlugin (for example PluginWithInput) and vice versa, what is the best way to do this?

My thinking is to create another interface ICommunicate in Interfaces that has a simple function (Let's say HelloWorld).
Then create another class Communicate in PluginWithInput that implements the function and use RemotingConfiguration.RegisterWellKnownServiceType to register it in initialization of PluginWithInput.xaml.cs
Finally, add a function in PluginHostProxy.cs that connects to the service with RemotingServices.Connect

Efficient, error-free and versionable message passing between a number of independently evolving components is going to be difficult, and I don't think "the right approach" has been found so far.

So, it all depends on your requirements.

The plugin process already registers well known type PluginLoader. If the shell implements services meaningful for plugins, you can define an interface like IBaktoonShell and pass it to the plugin constructor as part of the loading process. This way you don't need another well-known type. See my reply to Eloopdog.

If plugins need to talk to each other, you basically have two options:

- keep the shell out of it; the plugins can set-up ad hoc 1-to-1 communication channels (sockets, remoting, WCF), or use something like MSMQ, just like if they were independent processes and the shell did not exist

- implement a pub/sub bus architecture similar TIBCO or MQ inside the shell

In any case you want to minimize dependencies (plugins will change!) and avoid putting into the shell functionality that is meaningful only for one or two specific plugins.

First of all, well done- great explanation and example!
I had battled with trying to do this for weeks in MAF some time ago and had almost given up.
Shame about the UI message blocking.

I have two questions (not asking for full answer but just a pointer in the right direction):

1) What would be the easiest way to enable the plugin to request a second piece of UI to be opened in another tab? i.e. button on the plugin's first tab "Open Job" opens the job in a new tab etc

2) I have created an interface which contains two events CommTowardHost and CommTowardPlug, in order to allow the the two ends to communicate with each other in a generic way, I add handlers for the appropriate stages during the invoking of the pluginhost and the plugin itself, this all appears to work fine. When the plugin is loaded, if I raise the event on the plugin UI (in the direction of the host) the host receives the event changes some data in the object passed along and the event then goes back to the plugin where I can see the changed data - brilliant! but if I raise the event on the host (in the direction of the plugin) it goes as far as the event handler on the remote pluginloader object and then I get an exception "Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type."
Do you have any ideas of how I might get around this?
I have tried marking the handler as Messaging.OneWay which stops the exception but the event still goes no further?

1. Shooting in the dark here, but I would probably try to define an interface like IBaktunShell in the Interfaces assembly and pass it to the plugin loader along with the assembly name and type to load. The loader can then check whether the plugin has a constructor that accepts IBaktunShell , and if yes, pass the shell reference to the constructor.

I used similar technique in a real world application, but there is no doubt you will find all kinds of weirdnesses along the road. In particular, see here: http://www.ikriv.com/blog/?p=1134. I ended up enumerating the plugin constructors explicitly via reflection instead of relying on AppDomain.CreateInstanceAndUnwrap.

2. a) I would avoid events in the remoting scenario. They are typically a pain in the (insert your favorite body part here). Consider replacing events with direct method calls in the opposite direction, e.g. host.SendMessage("foo"), plugin.SendMessage("bar").

If your contracts are relatively stable, you may receive further benefit by making them explicit and strongly typed: put corresponding interfaces in the Interfaces assembly. The downside is that you will have to version them explicitly: IPlugin, IPlugin2 : IPlugin, IPlugin3 : IPlugin2, etc.: once an interface is out in the wild, it cannot be changed easily.

b) As far as your error is concerned, it is hard to tell without concrete working sample. Try replacing events with method calls, maybe it will go away