Daily Archives: January 13, 2014
Reflection (part 3, advanced, professional), plugin
We are progressing further.
Reflection tells us all we need for plugins. Once we know the assembly we can examine it like a poor frog on the dissection table. If the dissector cannot find something, then it is not the fault of the frog. Sorry for the macabre metaphor, I could not resist my ugly thoughts.
First of all we need an assembly. In the Solution Explorer right-click your Project. The tab-menu shows (Application, Build, Build Events, Debug …). Select Application. The first entry is “Assembly name:”. You can enter your prefered assembly name here. Let’s use “TestAssembly” for our example. Now the compiler will create an assembly with the name TestAssembly.
A short test program gives you an idea where your assembly is stored and what its name is.
Assembly lAssembly = Assembly.GetCallingAssembly(); Console.WriteLine(lAssembly.Location); // prints the full path of your assembly Console.WriteLine(lAssembly.FullName);
You can load assemblies into applications. We are concentrating on plugins today.
In theory you can get all required information to eg. call methods or properties via reflection itself. There is no need for extra definition files. But you can also make your life easier and predefine signatures, which are used by both the application and the plugin. Of course you are implementing constraints that reduce the flexibility, but on the other hand you reduce the error-proneness and save a lot of debugging time.
So we are going to use an interface to predefine signatures. It is important that you do not copy the interface source code into both projects. You should compile the interface into a DLL-library and access these definitions from outside in the two assemblies. Otherwise the compilers generate different identifiers (see GUID) and two equal interfaces cannot be recognized as the same type.
To clearly show the independence I want you to open Visual Studio three times. Please do not create three projects in one single solution.
Visual Studio 1
Create a library project and paste the following code. We don’t need any “using” or “namespace”. Five lines of code are enough in our example.
public interface IPlugin { string Name { get; } int Born { get; } double DoSomething(double d, string s); } // interface
Compile the project and move the library DLL-file to your preferred location.
Visual Studio 2
Add a reference to our interface library. (Right click on References in your Solution Explorer. Add Reference, then browse for the library file.)
We are creating the plugin now. The method Test() prints a path that you will need in the source code for the calling application (just to make your life easier).
public class MyPlugin1 : IPlugin { public string Name { get { return "Diana Frances"; } } public int Born { get { return 1961; } } public double DoSomething(double d, string s) { return 1.0; } } // class public class MyPlugin2 : IPlugin { public string Name { get { return "Jeanne d'Arc"; } } public int Born { get { return 1412; } } public double DoSomething(double d, string s) { return 1.0; } } // class public static class TestClass { public static void Test() { Assembly lPluginAssembly = Assembly.GetCallingAssembly(); // Assembly lPluginAssembly = Assembly.Load("TestAssembly"); // alternative Console.WriteLine(lPluginAssembly.Location); Console.WriteLine(lPluginAssembly.FullName); Console.ReadLine(); } // } // class
Visual Studio 3
The projects in Visual Studio 1 and Visual Studio 2 are compiled. You can close these two programs.
Once again add a reference to our interface library. In below example code you need to replace a path by the one, which was printed out in Visual Studio 2. (in line Assembly lPluginAssembly = Assembly.LoadFile(@”xxxxxx\TestAssembly.exe”);)
static void Main(string[] args) { Assembly lPluginAssembly = Assembly.LoadFile(@"xxxxxx\TestAssembly.exe"); //Assembly lPluginAssembly = Assembly.Load("TestAssembly"); => if loaded already var lPlugins = from t in lPluginAssembly.GetTypes() where typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface select t; foreach (Type t in lPlugins) { IPlugin lPlugin = Activator.CreateInstance(t) as IPlugin; Console.WriteLine(lPlugin.Name + " was born in " + lPlugin.Born); double d = lPlugin.DoSomething(2.0, "hello"); } Console.ReadLine(); } // main
output example:
Diana Frances was born in 1961
Jeanne d’Arc was born in 1412
The program loaded the assembly, created instances for each class and called the properties and the method. This basically is all you need for Plugins.
Of course there are also dirty ways to invoke plugin methods. One is:
Assembly lPluginAssembly = Assembly.LoadFile(@"xxxxxx\TestAssembly.exe"); Type t = lPluginAssembly.GetType("MyNamespaceName.MyPlugin1"); MethodInfo m = t.GetMethod("DoSomething"); double d = (double)m.Invoke(Activator.CreateInstance(t), new object[] { 2.0, "hello" });