I am writing a plugin-hosting WPF application. For variety of reasons I have to put plugin-ins into their own app domains. To name just a few:
- plugin unload is required
- each plugin has its own config file
- plugins may be located anywhere on the disk
I am having serious troubles due to the last requirement. It’s one of those cases when one bad thing leads to another and you are starting to think that this was done on purpose, just to get at you.
First, you cannot normally load an assembly into an app domain unless it is under that domain’s
ApplicationBase. Yes, there is a number of
LoadFrom-like methods that take arbitrary path, but they load your assembly into this or another special context where it is basically useless.
This means that if my application running from
c:\foo\myhost\host.exe creates an app domain based in
c:\bar\plugins\SomePlugin, no type from
host.exe can be loaded into this new app domain. This means no callbacks, no initialization code, nothing. Complete separation. This causes issues if the plugin must expose non-marshallable types such as WPF
The only thing the two app domains can have in common is GAC. But you need to be an admin to write to the GAC!
Also, it turns out, adding things to the GAC is not easy. According to Microsoft, redistributing
GacUtil is a no-no. The recommended method is to write an MSI installer, but it brings the whole lot of issues. Your application now needs to appear in “Programs and Features”, all kinds of reference counting quirks come into effect (“a previous version of this product is already installed!”), and to top it off, setup projects (
.vdproj) cannot be built by MSBuild, so one needs to intall a full fledged Visual Studio on the continuous integration server. At this point I bailed on MSI.
Another method is to use this call:
new System.EnterpriseServices.Internal.Publish().GacInstall(path). This one has two troubles:
- it does not return any errors except for an Event log message that comes from “COM+ SOAP Services” and just states that installing
path into GAC failed.
- there actually two GACs now. One for .NET 2.0 and one for .NET 4. If your assembly is a .NET 4 assembly, the above code must be run under .NET 4 runtime. Otherwise GAC instlalation will fail.
Figuring out under which runtime your code runs may not be an easy task, especially if it is invoked from a service via complex system of build and installation scripts.
So yes, after spending almost two days fighting all that I won, but I am still not certain why all that should be that hard. If only it were allowed to load arbitrary assemblies into an app domain, all this complexity would go away. Another, less attractive, but still better solution would be to have a local assembly cache that does not require admin rights. Alas, it is what it is. No one promised us free lunch.