views:

813

answers:

3

Problem statement: Implement a plug-in system that allows the associated assemblies to be overwritten (avoid file locking). In .Net, specific assemblies may not be unloaded, only entire AppDomains may be unloaded.

I'm posting this because when I was trying to solve the problem, every solution made reference to using multiple AppDomains. Multiple AppDomains are very hard to implement correctly, even when architected at the start of a project.

Also, AppDomains didn't work for me because I needed to transfer Type across domains as a setting for Speech Server worfklow's InvokeWorkflow activity. Unfortunately, sending a type across domains causes the assembly to be injected into the local AppDomain.

Also, this is relevant to IIS. IIS has a Shadow Copy setting that allows an executing assembly to be overwritten while its loaded into memory. The problem is that (at least under XP, didnt test on production 2003 servers) when you programmatically load an assembly, the shadow copy doesnt work (because you are loading the DLL, not IIS).

+5  A: 
  1. Check if the assembly is already loaded (to avoid memory leaks caused by loading the same assembly over and over again).
  2. If its not loaded, read the assembly into a byte array. This will prevent locking the file.
  3. Supply the byte array as an arguement to Assembly.Load

The following code assumes you know the FullName of an assembly.

Assembly assembly = null;

foreach(Assembly loadedAssembly in AppDomain.CurrentDomain.GetAssemblies())
    if (loadedAssembly.FullName == "foobar, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")
     assembly = loadedAssembly;

if(assembly == null)
{
    byte[] studybin = System.IO.File.ReadAllBytes(@"C:\pathToAssembly\foobar.dll");
    assembly = Assembly.Load(studybin);                
}

Note that if you are trying to find a specific assembly in a directory, you can run 'System.Reflection.AssemblyName.GetAssemblyName(path);' to see if the FullName matches what you are looking for. 'GetAssemblyName(path)' does NOT inject the assembly into your current AppDomain.

Also note that this solution is not appropriate for applications that must only rarely restart AND the assemblies change with high frequency. Every time an assembly is loaded, the memory footprint of you application will grow. There is no method to unload an assembly, thus the only option to shrink memory usage is to restart the application. However, this limitation is often preferable to the large performance, memory, and code complexity overhead of using multiple app domains. This limitation is also unavoidable if you wish to work with Types.

Abtin Forouzandeh
I assume this means you would still have to restart the application to use the new plug in once it had been replaced on disk? I was under the impression that once a type has been loaded it cannot be changed, even if you reload the assembly.
Jamie Penney
You can use the new type simultaneously. You are not technically "reloading" the assembly. Instead, you are loading the new assembly side by side with the old one. If the assemblies change frequently, you will eventually run out of memory and need to restart the app.
Abtin Forouzandeh
+1  A: 

If you want to isolate your addins, you must...

  1. Create a type that extends MarshallByRefObject which will interact with your addins (lets call it FOOMASTER)
  2. Create a new appdomain (lets call it addinLand)
  3. Call addinLand.CreateInstanceAndUnwrap to create an instance of FOOMASTER in addinLand and get a proxy in the current app domain
  4. Tell FOOMASTER to load your addins

Now your addins are loaded in addinLand and you can interact with them from your primary appdomain via your FOOMASTER proxy. When your addins crash, they don't take down your application.

Its an interesting and slightly confusing process. I originally thought the idea was to load your addins and then bring them over into the current app domain as transparent proxies, but the best design is to leave the addins as simple objects that interact with more complex types you create (FOOMASTER), that extend MarshallByRefObject, that are loaded in the addinLand app domain, and whose transparent proxies you interact with.

Chapters 21 and 22 of CLR Via C# are very helpful in understanding the process.

Will
+1  A: 

If you are interested in a serious method for building a system with a plugin architecture, you might want to check out MAF (Managed Add-in Framework), which is now part of the .NET Framework, namely the System.AddIn namespace.

It will help you manage add-in isolation and versioning, even backward compatibility with contracts.

There is a bit of a learning curve here though, so this may not be exactly what you are looking for.

http://msdn.microsoft.com/en-us/library/bb384200.aspx

markt