Monday, May 19, 2008

Dynamically Loading a Dll

The main purpose of this blog is to make a journal of common software problems that I have had to solve. As I've progressed through my career I've noticed that the same problems keep coming up, and I hate having to research out how to solve a problem that I've already solved.

This is my first entry. I have had to dynamically load dll's several times. .Net has tools for dynamically loading dll's, but since it something I only do one to two years, it's also something that I forget how to do.

The .Net Framework makes it easy to dynamically load a dll at run time, and instantiate classes inside of that dll. This is very useful when building a plug-in framework or a test harness framework.

In this posting I will create a rudimentary plug-in framework. This plug-in framework is intended only to demonstrate how to dynamically load a dll.

Our plug-in framework will dynamically load one or more dll's, instantiate objects inside of those dll's and forward commands to those objects. It is beneficial to have each plug-in implement a common interface, so our engine can communicate with our plug-ins through a contract. The following interface defines the contract between our plug-in framework and the plug-ins themselves.

public interface IPlugin{
//Retrieves the name for plug-in
string Name {get; }

//This method is called in order to allow the plug-in to execute its logic.
void Execute();
}

The above interface has Name property that will be used by the Plug-in framework in order to identify the plug-in to the user, and it has an Execute method that will be called by the framework in order to allow the plug-in to take over. This interface should typically be defined in a dll that contains all of the base plug-in interfaces and classes.

Now that we have our interface we can create our own plug-ins. We will create two plug-ins the first one will be called Fibonacci which predictably prints the Fibonacci sequence.

public sealed class Fibonacci : IPlugin{
public string Name {get {return "Fibonacci";}}

public void Execute(){
Console.WriteLine("Enter the number of fibonacci numbers you " +
"want to display.");
int sequenceSize = int.Parse(Console.ReadLine());
int currentNumber = 1;
int previousNumber = 0;
Console.Write("[");
for (int count = 0; count <>
Console.Write("{0}, ", currentNumber);
int tempCurrent = currentNumber ;
currentNumber = currentNumber + previousNumber;
previousNumber = tempCurrent;
}
Console.Write("]");
}
}

Now we'll make the second class. This class will be called Factorial, which will calculate and display the factorial of a number.

public sealed class Factorial : IPlugin{
public string Name{get{return "Factorial";}}

public void Execute(){
Console.WriteLine("Enter the number you want to take the Factorial of.");
int argument = int.Parse(Console.ReadLine());
int result = 1;
for (int multiplier=1; multiplier<= argument; ++multiplier)
result = result * multiplier;
Console.WriteLine("{0}! = {1}", argument, result);
}
}

Now that we've written our plug-ins we can write the engine. The engine will be a command line application that dynamically load all of the dll's in its plugin directory. The engine will then display the plug-ins and allow the user to choose which plug-in to execute. The user may quit the program by typing in the letter "q".

In order to facilitate the engine let's write some extension methods to aid us with reflection.

internal static class ReflectionExtensions{
public static object Instantiate(this Type type){
ConstructorInfo constructor = type.GetConstructor(new Type[]{});
return constructor.Invoke(new object[]{});
}

public static bool ImplementsInterface(this Type type, Type interfaceType){
Type[] interfaces = type.GetInterfaces();
return Array.Exists(interfaces, candidate => candidate == interfaceType);
}
}

Now let's implement the Main class:

public class Main{
private static string _pluginDir;
public static void Main(){
//Load the assemblies.
_pluginDir = Path.Combine(
Application.StartUpPath,
"Plugins");

IEnumerable plugins =
from dll in Directory.GetFiles(_pluginDir, "*.dll")
let asm = Assembly.LoadFromFile(dll)
from type in asm.GetTypes()
where type.ImplementsInterface(typeof(IPlugin))
select type.Instantaite() as IPlugin;

ExecuteEngine(plugins.ToArray());
}

private static void ExecuteEngine(IPlugin[] plugins){
string userSelection = string.Empty;
do{
for (int index = 0; index <>
Console.WriteLine("[{0}] {1}", index, plugins[index].Name);
}
Console.WriteLine("(Q)uit");
userSelection = Console.ReadLine();
int pluginIndex = 0;
if (int.TryParse(userSelection, out pluginIndex){
IPlugin selectedPlugin = plugins[pluginIndex];
selectedPlugin.Execute();
}
}while(userSelection.ToUpper() != "Q");
}
}

The above code is close, and it will certainly work with the plug-ins we've created. Unfortunately this code will only work with simple plug-ins like the ones we've created. The problem is the .Net framework for one reason or another has problems resolving Assemblies referenced by dynamically loaded Assemblies. In order to get around this you need to help the .Net framework resolve the assemblies referenced by your dynamically loaded assemblies. You do this by subscribing to the current AppDomain's AssemblyResolve event. The following code demonstrates how to do this:

public class Main{
private static string _pluginDir;
public static void Main(){
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
//Load the assemblies.
_pluginDir = Path.Combine(
Application.StartUpPath,
"Plugins");

IEnumerable plugins =
from dll in Directory.GetFiles(_pluginDir, "*.dll")
let asm = Assembly.LoadFromFile(dll)
from type in asm.GetTypes()
where type.ImplementsInterface(typeof(IPlugin))
select type.Instantaite() as IPlugin;

ExecuteEngin(plugins.ToArray());
}

private static void ExecuteEngine(IPlugin[] plugins){
string userSelection = string.Empty;
do{
for (int index = 0; index <>
Console.WriteLine("[{0}] {1}", index, plugins[index].Name);
}
Console.WriteLine("(Q)uit");
userSelection = Console.ReadLine();
int pluginIndex = 0;
if (int.TryParse(userSelection, out pluginIndex){
IPlugin selectedPlugin = plugins[pluginIndex];
selectedPlugin.Execute();
}
}while(userSelection.ToUpper() != "Q");
}

private static Assembly ResolveAssembly(object sender, ResolveEventArgs e){
string[] dlls = Directory.GetDirectories(_pluginDir, "*.dll");
string asmFile = Array.Find(
dlls,
candidate => AssemblyName.GetAssemblyName(candidate).Fullname = e.Name);
return Assembly.LoadFile(asmFile);
}
}

In the above example we subscribed to the current AppDomain's ResolveAssembly event. The .Net framework will call our ResolveAssembly method whenever it cannot resolve an Assembly itself. We look into the plug-in directory for the assembly, by searching for a dll that has the same Assembly name as the one passed in by the framework. Now our plug-in application should work. (Note: I actually haven't been able to run the current code through a compiler, so it actually probably doesn't work. I'd be shocked if it even compiled at this point. I'm downloading the compiler now and I will verify that this code is correct once it is installed.)

1 comment:

Unknown said...

Hi Ed,I was using Jim's comp and looked at your blog. Didn't understand anything about it. Dad