UPDATE 5/17/2007

This is being edited to reflect current best practice and new APIs, Please review this page as the ‘home base’ for this subject:

http://blogs.msdn.com/clraddins/pages/info.aspx

It is critical in many scenarios to define ‘Intent’ via Interfaces and allow for the dynamic loading of implementation to realize that intent.

.NET gives us the ability to load AppDomains and load Assemblies (and Unload them) into these AppDomains. As we cannot unload Assemblies, only AppDomains, and unloading the initial AppDomain we are given to run our app, we must create new AppDomains and then run Assemblies inside these. I use metadata to match implementation with Intent (and it is very important that you have a separate Assembly for Interfaces only due to the loading rules).

For example, if you had implementation that references an Abstract Class, that assembly would need to also be loaded into the secondary AppDomain. Not so with Interfaces.

I tool a sample app written by a ledged (Eric Gunnerson) and made some changes to get it to work.

I have made some non-trivial changes and the full app is available to those who ask.

The loader in the MAIN application:

namespace SuperGraph
{

public class Loader
{
AppDomain appDomain;
RemoteLoader remoteLoader;

Utilities oUtil = null;

public T LoadClassInAppDomain(String AssemblyName, String TypeName)
{
return (T)appDomain.CreateInstanceAndUnwrap(AssemblyName, TypeName);
}
public void ShowAssemblies()
{
if (oUtil == null)
oUtil = LoadClassInAppDomain("SuperGraph",
"SuperGraph.Utilities");
//oUtil = (Utilities) appDomain.CreateInstanceAndUnwrap("SuperGraph",
// "SuperGraph.Utilities");
oUtil.ShowAssemblies(appDomain);
}
public Loader(string functionDirectory, String AppDomainName)
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
//setup.PrivateBinPath =
// AppDomain.CurrentDomain.BaseDirectory;
//setup.CachePath
setup.ApplicationName = "Graph";
setup.ShadowCopyFiles = "true";
setup.ShadowCopyDirectories = functionDirectory;
appDomain = AppDomain.CreateDomain(AppDomainName, null, setup);
SetAppDomainPolicy(appDomain);
remoteLoader = (RemoteLoader) appDomain.CreateInstanceAndUnwrap(
"SuperGraph",
"SuperGraphInterface.RemoteLoader");
}

// NOTE: I have not optimized this for security. This makes the AppDomain 'Wide Open' to do just about anything

void SetAppDomainPolicy(AppDomain appDomain)
{
// Create an AppDomain policy level.
PolicyLevel pLevel = PolicyLevel.CreateAppDomainLevel();
// The root code group of the policy level combines all
// permissions of its children.
UnionCodeGroup rootCodeGroup;
PermissionSet ps = new PermissionSet(PermissionState.Unrestricted);
ps.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags));
rootCodeGroup = new UnionCodeGroup(new AllMembershipCondition(),
new PolicyStatement(ps, PolicyStatementAttribute.All));
NamedPermissionSet localIntranet = FindNamedPermissionSet("LocalIntranet");
// The following code limits all code on this machine to local intranet permissions
// when running in this application domain.
UnionCodeGroup virtualIntranet = new UnionCodeGroup(
new ZoneMembershipCondition(SecurityZone.MyComputer),
new PolicyStatement(localIntranet,
PolicyStatementAttribute.Nothing));
virtualIntranet.Name = "Virtual Intranet";
// Add the code groups to the policy level.
rootCodeGroup.AddChild(virtualIntranet);
pLevel.RootCodeGroup = rootCodeGroup;
appDomain.SetAppDomainPolicy(pLevel);
}
private NamedPermissionSet FindNamedPermissionSet(string name)
{
IEnumerator policyEnumerator = SecurityManager.PolicyHierarchy();
while (policyEnumerator.MoveNext())
{
PolicyLevel currentLevel = (PolicyLevel)policyEnumerator.Current;
if (currentLevel.Label == "Machine")
{
IList namedPermissions = currentLevel.NamedPermissionSets;
IEnumerator namedPermission = namedPermissions.GetEnumerator();
while (namedPermission.MoveNext())
{
if (((NamedPermissionSet)namedPermission.Current).Name == name)
{
return ((NamedPermissionSet)namedPermission.Current);
}
}
}
}
return null;
}
public FunctionList LoadAssembly(string filename)
{
return remoteLoader.LoadAssembly(filename);
}
public void Unload()
{
AppDomain.Unload(appDomain);
appDomain = null;
}
}
}

The RemoteLoader to be loaded in the remote AppDomain:

using System;
using System.IO;
using System.Reflection;
using System.Security;
using System.Security.Policy;
using SuperGraph;
using System.Security.Permissions;

namespace SuperGraphInterface
{
///
/// Summary description for RemoteLoader.
///
public class RemoteLoader: MarshalByRefObject
{
public RemoteLoader()
{
//Console.WriteLine(AppDomain.CurrentDomain);
}

[FileIOPermission(SecurityAction.Assert)]
public FunctionList LoadAssembly(string fullname)
{
//string path = Path.GetDirectoryName(fullname);
//string filename = Path.GetFileNameWithoutExtension(fullname);
FunctionList functionList = new FunctionList();
Assembly assembly = Assembly.LoadFile(fullname);
foreach (Type t in assembly.GetTypes())
{
functionList.AddAllFromType(t);
}
return functionList;
}
}
}

The Interface in a SEPERATE ASSEMBLY:

using System;
namespace SuperGraph
{
public interface IQuantFunction
{
double Invoke(params double[] parameters);
string Name { get; }
}
}

An example of Implementation which would be loaded in a seperate assembly:

using System;
using System.Collections.Generic;
using System.Text;
namespace ExtraFunctions
{
class SqrFunction : IFunction
{
#region IFunction Members
public SqrFunction() {}
public double AlgorithmTransform(double T)
{
return T * T;
}
#endregion
}
}

Code called in the constructor of the loading form in the main AppDomain:

public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
const String ADDINDIRECTORY = @"D:\Documents and Settings\Damon Carr.AGILEFAST\My Documents\Visual Studio 2005\AppDomainWork\ExtraFunctions\bin\Debug";
LoadGraph();
try
{
graph.FunctionAssemblyDirectory = ADDINDIRECTORY;
graph.HookupFunctions();
graph.Min = Convert.ToDouble(this.min.Text);
graph.Max = Convert.ToDouble(this.max.Text);
graph.SetScale(graphPanel.Size);
graph.UpdateListbox(this.graphLineListbox);
graph.ReloadCountChanged += new Graph.ReloadCountHandler(ReloadChanged);
}
catch (PolicyException e)
{
MessageBox.Show(e.Message);
}
}

 

Here are the assemblies after execution loaded in my main AppDomain. I certainly do not want to see the ‘ExtraFunctions’ assembly as that is my add-in:

AppDomain is Name: SuperGraph.vshost.exe

There are no context policies.

mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Microsoft.VisualStudio.HostingProcess.Utilities, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
Microsoft.VisualStudio.HostingProcess.Utilities.Sync, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
vshost, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.EnterpriseServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
System.Runtime.Serialization.Formatters.Soap, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
SuperGraph, Version=1.0.2237.23642, Culture=neutral, PublicKeyToken=383df91068f0e632
Interfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9fbcac51bdeca844
System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

Looks good. Here is what is loaded in the new Assembly:

AppDomain is Name:AddInDomain
There are no context policies.

mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Microsoft.VisualStudio.HostingProcess.Utilities, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
SuperGraph, Version=1.0.2237.23642, Culture=neutral, PublicKeyToken=383df91068f0e632
Interfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9fbcac51bdeca844
ExtraFunctions, Version=1.0.2234.27895, Culture=neutral, PublicKeyToken=null
System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Interfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9fbcac51bdeca844

Perfect..

System.AppDomain


4 Comments

  1. I tried to use some of your code, but for some reason the security restrictions are not applied to the addin load into the AppDomain. Any clues?

  2. This code is a bit dated… I now recommend the System.AddIn set of classes. I’d be happy to look at your code. Do you want to send me the problem area? My email is damon at agilefactor dot com.

    Thanks.
    Damon

  3. hgjjhgjg

  4. hi,
    i would like to have the full app if it possible.
    10X


Post a Comment