Introduction
My application requires me to load a DLL dynamically at run time, set some properties on a class and then execute a method on the class. Most of this is pretty straight forward and well documented in any number of articles on reflection.
The tricky part is that one of the properties I need to set is an enum type that is defined within the DLL that I'm loading. I need to get the enum item values from the DLL, then I need to use those values to set an enum variable in the DLL. And because I'm loading it dynamically the type definition for the enum is not available at compile time.
I spent some quality time searching for a way to do this and didn't come up with much except for a couple of message board posts from other people asking about the same thing. Having figured out how to do it, I thought I'd share the magic with the community.
Background
There is a good article by Eric Gunnerson on how to load DLL's dynamically on MSDN at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp05162002.asp.
I have omitted that aspect of the code from my examples for simplicity.
The Remote DLL
The DLL code looks something like the snippet below. There is a public enum (BitFields) defining a bunch of named bit field values, and a class with a property (ThirdPartyBitFields) that provides access a variable of the enum type.
using System;
namespace ThirdPartyDll
{
public enum BitFields
{
InitialValue = 0,
OneSetting = 1,
AnotherSetting = 2,
SomethingElse = 4,
LastOne = 8
}
public class ThirdPartyClass
{
private BitFields thirdPartyBitFields = BitFields.InitialValue;
public ThirdPartyClass()
{
}
public BitFields ThirdPartyBitFields
{
get { return this.thirdPartyBitFields; }
set { this.thirdPartyBitFields = value; }
}
public void DoSomeGood()
{
// Make some decisions based on the value
// of thirdPartyBitFields
}
}
}
The Easy Way
If I wasn't loading the DLL dynamically then I would simply "Or-Equal" the enum values into the enum variable, like so:
using System;
using ThirdPartyDll;
namespace LocalUser
{
public class LocalUserClass
{
public LocalUserClass()
{
}
public void TestTheDll()
{
ThirdPartyClass myClass = new ThirdPartyClass();
myClass.ThirdPartyBitFields |= BitFields.AnotherSetting;
myClass.ThirdPartyBitFields |= BitFields.SomethingElse;
myClass.DoSomeGood();
}
}
}
The Reflection Way
Unfortunately the easy way isn't an option in my scenario so let's walk through the steps I have to take to accomplish the same thing with reflection. I'll present the code snippet by snippet to keep it simple, and show the complete method at the end of the article. Feel free to skip to the end if you just want to see the final result.
The first thing I need to do is load the DLL (which is really an assembly in the .Net world). I happen to know that the DLL is in the same folder with my executable, so I can load it like this:
AssemblyName name = new AssemblyName();
name.CodeBase = "file://" + Directory.GetCurrentDirectory() + "ThirdPartyDll.dll";
Assembly assembly = AppDomain.CurrentDomain.Load(name);
Next I need to instantiate the class and create a Type object for it.
object theirClass = assembly.CreateInstance("ThirdPartyDll.ThirdPartyClass");
Type theirType = assembly.GetType("ThirdPartyDll.ThirdPartyClass");
Now I load the enum definition so that I can access the individual enum items that I need.
Type enumType = assembly.GetType("ThirdPartyDll.BitFields");
FieldInfo enumItem1 = enumType.GetField("AnotherSetting");
FieldInfo enumItem2 = enumType.GetField("SomethingElse");
Now we get to the crux of the problem. I can call GetValue() on the FieldInfo enumItem1 and enumItem2 objects, but GetValue() returns an object. I need to perform an "OR" operation and I can't do that on an object. The only way I can perform the "OR" is if I cast the objects into a type that supports that operation.
int enumValue1 = (int)enumItem1.GetValue(enumType);
int enumValue2 = (int)enumItem2.GetValue(enumType);
int currentValue = (int)flagsInfo.GetValue(remoteObject, null);
int newValue = currentValue | enumValue1 | enumValue2;
So now I have an int that contains the correct value, but what good is it? I can't store an int back in the enum variable because there is a type mismatch between int and enum. Only an enum item can be stored into an enum type.
The solution is that I can create a new enum object based on the Type class that I have and set its value explicitly to the int value that I calculated. Here is the magic:
object newEnumValue = Enum.ToObject(enumType, newValue);
Once I have the enum item object created it is a simple matter to store it back to the original enum variable. Then I just invoke my method and I'm done!
flagsInfo.SetValue(remoteObject, newEnumValue, null);
MethodInfo method = remoteType.GetMethod("DoSomeGood");
Method.Invoke(remoteObject,null);
The Final Solution
Here is the final code for accessing the dynamically loaded DLL with reflection.
using System;
using System.Reflection;
using System.IO;
namespace RemoteUser
{
public class RemoteUserClass
{
public RemoteUserClass()
{
// Load the remote assembly
AssemblyName name = new AssemblyName();
name.CodeBase = "file://" + Directory.GetCurrentDirectory() + "ThirdPartyDll.dll";
Assembly assembly = AppDomain.CurrentDomain.Load(name);
// Instantiate the class
object remoteObject = assembly.CreateInstance("ThirdPartyDll.ThirdPartyClass");
Type remoteType = assembly.GetType("ThirdPartyDll.ThirdPartyClass");
// Load the enum type
PropertyInfo flagsInfo = remoteType.GetProperty("ThirdPartyBitFields");
Type enumType = assembly.GetType("ThirdPartyDll.BitFields");
// Load the enum values
FieldInfo enumItem1 = enumType.GetField("AnotherSetting");
FieldInfo enumItem2 = enumType.GetField("SomethingElse");
// Calculate the new value
int enumValue1 = (int)enumItem1.GetValue(enumType);
int enumValue2 = (int)enumItem2.GetValue(enumType);
int currentValue = (int)flagsInfo.GetValue(remoteObject, null);
int newValue = currentValue | enumValue1 | enumValue2;
// Store the new value back in Options.FieldFlags
object newEnumValue = Enum.ToObject(enumType, newValue);
flagsInfo.SetValue(remoteObject, newEnumValue, null);
// Call the method
MethodInfo method = remoteType.GetMethod("DoSomeGood");
Method.Invoke(remoteObject,null);
}
}
}