C Sharp

Late Binding with Reflection

A few years back, I worked for the IBM Multimedia division on the IBM/World Book Multimedia Encyclopedia product. One challenge we had was coming up with an application that would allow the user to configure different communications protocols for use with the World Book servers. This had to be a dynamic solution because the user could continually add and remove different protocols (for example, TCP/IP, IGN, CompuServ, and so on) from their system. However, the application had to "know" which protocols were present so that the user could select a specific protocol for configuration and use. The solution we came up with was creating DLLs with a special extension and installing them in the application folder. Then when the user wanted to see a list of installed protocols, the application would call the Win32 LoadLibrary function to load each DLL and then call the GetProcAddress function to acquire a function pointer to the desired function. This is a perfect example of late binding in standard Win32 programming in that the compiler knows nothing about these calls at build time. As you'll see in the following example, this same task could be carried out in .NET by using the Assembly class, type reflection, and a new class called the Activator class.

To get things rolling, let's create an abstract class called CommProtocol. I'll define this class in its own DLL so that it can be shared across multiple DLLs that want to derive from it. (Note that the command-line parameters are embedded in the code's comments.) -

// CommProtocol.cs
// Build with the following command line switches
//         csc /t:library commprotocol.cs
public abstract class CommProtocol
{
    public static string DLLMask = "CommProtocol*.dll";
    public abstract void DisplayName();
}

Now, I'll create two separate DLLs, each representing a communications protocol and containing a class derived from the abstract class CommProtocol. Note that both need to reference the CommProtocol.dll when compiled. Here's the IGN DLL: -

// CommProtocolIGN.cs
// Build with the following command line switches
//         csc /t:library CommProtocolIGN.cs /r:CommProtocol.dll
using System;
public class CommProtocolIGN : CommProtocol
{
    public override void DisplayName()
    {
        Console.WriteLine("This is the IBM Global Network");
    }
}

And here's the TCP/IP DLL: -

// CommProtocolTcpIp.cs
// Build with the following command line switches
//         csc /t:library CommProtocolTcpIp.cs /r:CommProtocol.dll
using System;
public class CommProtocolTcpIp : CommProtocol
{
    public override void DisplayName()
    {
        Console.WriteLine("This is the TCP/IP protocol");
    }
}

Let's look at how easy it is to dynamically load an assembly, search for a type, instantiate that type, and call one of its methods.

using System;
using System.Reflection;
using System.IO;
class LateBindingApp
{
    public static void Main()
    {
        string[] fileNames = Directory.GetFiles
                                (Environment.CurrentDirectory,
                                 CommProtocol.DLLMask);
        foreach(string fileName in fileNames)
        {
            Console.WriteLine("Loading DLL '{0}'", fileName);
            Assembly a = Assembly.LoadFrom(fileName);
            Type[] types = a.GetTypes();
            foreach(Type t in types)
            {
                if (t.IsSubclassOf(typeof(CommProtocol)))
                {
                    object o = Activator.CreateInstance(t);
                    MethodInfo mi = t.GetMethod("DisplayName");
                    Console.Write("\t");
                    mi.Invoke(o, null);
                }
                else
                {
                    Console.WriteLine("\tThis DLL does not have " +
                    "CommProtocol-derived class defined");
                }
            }
        }
    }
}

First I use the System.IO.Directory class to find all the DLLs in the current folder with a mask of CommProtocol*.dll. The Directory.GetFiles method will return an array of objects of type string that represents the filenames of files that match the search criteria. I can then use a foreach loop to iterate through the array, calling the Assembly.LoadFrom method that you learned about earlier in this chapter. Once an assembly is created for a given DLL, I then iterate through all the assembly's types, calling the Type.SubClassOf method to determine whether the assembly has a type that is derived from CommProtocol. I'm assuming that if I find one of these I have a valid DLL to work with. When I do find an assembly that has a type derived from CommProtocol, I instantiate an Activator object and pass to its constructor the type object. As you can probably guess from its name, the Activator class is used to dynamically create, or activate, a type.

I then use the Type.GetMethod method to create a MethodInfo object, specifying the method name DisplayName. Once I've done that, I can use the MethodInfo object's Invoke method, passing to it the activated type, and-voila!-the DLL's DisplayName method is called.