Late Binding and On-the-Fly Code Generation Using Reflection in C#

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Environment: .NET, C#

Introduction

In this article we will learn how to achieve late binding and how to create and execute code on the fly (during runtime) by using the Refection namespace of C Sharp in .NET. For an introduction and how to use the Reflection namespace, please refer to my previous article, “An Introduction to Reflection in C#.”

Late Binding

In this example, we will see how to reflect types at run time, late bind to code, and dynamically emit and execute MSIL code. Assume that we need to develop a Peer-to-Peer (P2P) file sharing network that supports multiple protocols such as Napster, Kazaa, and eDonkey integrated with it, and we need to give the user the option to select a particular protocol. We will have these protocols in DLLs (assemblies) and we will list all supported protocols to user to select. This is analogous to calling the Win32 LoadLibrary to load a particular library and calling GetProcAddress to get the member functions. (The compiler knows nothing about these calls during compile time all are done during run time, “late binding.”)

Let’s start coding. First, create a CommonP2P class. This is the base class for all protocols. Enter the following code to this file, as shown in Listing 1.

Listing 1

Command line: Compile with the following command line
csc /t:library CommonP2P.cs
using System;

public abstract class CommonP2P
{
  // search string to find different dlls.
  Public static string strSearchDLL = "CommonP2P*.dll";
}

Compile this class to a DLL assembly so that we can refer to this in the forthcoming classes. Use the following command line options for compilation. This is also shown in the comments part of each class:

csc /t:library CommonP2P.cs 

Now, create CommonP2Pnapster.cs, CommonP2PEDonkey.cs, and CommonP2Pkazaa.cs (see Listings 2, 3, and 4). These protocol classes are derived from the CommonP2P.cs base class. Codes for these classes are shown below. Compile these classes to the DLL. Because we derive these classes from the CommonP2P class, we need to refer this during compilation with the /r option.

Listing 2

Command line: Compile with the following command line
csc /t:library CommonP2Pnapster.cs /r:CommonP2P.dll
using System;

public class CommonP2Pnapster : CommonP2P
{

  public static void MyMsg()
  {
    Console.WriteLine("t CommonP2Pnapster: Protocol Napster Loaded
                      Successfully.");
  }
}

Listing 3

Command line: Compile with the following command line
csc /t:library CommonP2Pkazaa.cs /r:CommonP2P.dll
using System;

public class CommonP2Pkazaa : CommonP2P
{

  public static void MyMsg()
  {
    Console.WriteLine("t CommonP2Pkazaa: Protocol KaZaA Loaded
                      Successfully.");
  }
}

Listing 4

Command line: Compile with the following command line
csc /t:library CommonP2PEDonkey.cs /r:CommonP2P.dll

using System;

public class CommonP2PEDonkey : CommonP2P
{
  public static void MyMsg()
  {
    Console.WriteLine("t CommonP2PEDonkey: Protocol eDonkey
                      Loaded Successfully.");
  }
}

Now, we will create a console application to test these DLLs with reflection. Add the codes to the main function, as shown in Listing 5.

Listing 5

using System;
using System.Reflection;
using System.IO;

static void Main()
{
string[] filenames = Directory.GetFiles
                    (Environment.CurrentDirectory,
                     CommonP2P.strSearchDLL);

foreach (string filename in filenames)
{
Console.WriteLine("Loading Protocol: {0}", filename);

  //Load the Assembly
  Assembly a = Assembly.LoadFrom(filename);

  // get all the types in the loaded assembly
  Type[] types = a.GetTypes();

  foreach (Type typ in types)
  {
    // dynamically create or activate(if exist) object
    object obj = Activator.CreateInstance(typ);

    // get required method by specifying name
    MethodInfo mi = typ.GetMethod("MyMsg");
    mi.Invoke(0, null);

    // or use this line of code instead of above two lines.
    // typ.InvokeMember("MyMsg", BindingFlags.InvokeMethod,
                         null, 0,
new Object[]{})
  }
}
}

Note that the using section contains a Reflection namespace for reflection operations and an IO namespace for file searching. We need to refer CommonP2P.dll to this project because we are using the strSearchDLL member variable for the DLL mask string for the search criteria. The GetFiles member function of the System.IO.Directory class returns an array or string of filenames that are matching for the given criteria and in the given directory. Here it’s the application exe directory. We load the assembly dynamically and call its member functions.

Load the assembly using Assembly.LoadFrom by giving the full path of the assembly; then get all types of an assembly by using the Assembly.GetTypes method. Using this type, we need to create the objects dynamically. For this purpose, we can use the Activator class. The activator class is used to dynamically create or activate (if already existing) a type. The CreateInstance member of the Activator class creates an instance of the type defined in an assembly by invoking the constructor that best matches the given arguments. If no arguments are passed, it calls the default constructor. If it successfully created an object dynamically, this member function returns a reference to that object. Once the object has been created, call the GetMethod method of the Type class by giving the method name string as a parameter. This method returns the MethodInfo object; MethodInfo discovers the attributes of a method and provides access to the metadata method. Then, call the Invoke method of the MethodInfo class. This calls the DLL’s MyMsg. Alternatively, we also can call the InvokeMember function of the Type class by specifying required parameter, as shown in Listing 5.

Creating and Executing Code On the Fly (Runtime)

In this example, we will see something about how to create code on the fly. By using namespace System.Reflection.Emit, we can create types at runtime. With this namespace we can dynamically:

  1. Define an assembly in memory.
  2. Create a module for an assembly.
  3. Define types (classes) for a module.
  4. Define members for types (classes) with calling convention, parameters, and access modifiers.
  5. Emit IL Opcodes for the application logic.

The code in this example is divided into two parts: a DLL of the IL code generator and an application that instantiates the code generating class and calls its member function. First, we will see the code generator DLL code, shown in Listing 6.

Listing 6

using System;
using System.Reflection;
using System.Reflection.Emit;   // To emit MSIL
using System.Threading;         // To get Current AppDomain

public class MSILGen
{
public MSILGen()
{
  // Create a simple name for the assembly.
  AssemblyName assemblyName = new AssemblyName();
  assemblyName.Name = "CodeGenAssembly";

  // Create the dynamic assembly.
  AppDomain appDomain = Thread.GetDomain();
  AssemblyBuilder assembly = appDomain.DefineDynamicAssembly
                            (assemblyName,
                             AssemblyBuilderAccess.Run);

  // Create a dynamic module.
  ModuleBuilder module = assembly.DefineDynamicModule
                                  ("CodeGenModule");


  // Define a public class named "CodeGenClass" in the assembly.
  TypeBuilder helloWorldClass = module.DefineType("CodeGenClass",
                                TypeAttributes.Public);

  // Define a private String field named "Message" in the type.
  FieldBuilder greetingField = helloWorldClass.DefineField
                               ("Message", typeof(String),
                               FieldAttributes.Private);

  // Create the constructor.
  Type[] constructorArgs = { typeof(String) };
  ConstructorBuilder constructor = helloWorldClass.
                                   DefineConstructor(
                                   MethodAttributes.Public,
                                   CallingConventions.Standard,
                                   constructorArgs);

  // Generate IL for the method. The constructor calls its
  // superclass constructor. The constructor stores its argument
  // in the private field.
  ILGenerator constructorIL = constructor.GetILGenerator();
  constructorIL.Emit(OpCodes.Ldarg_0);

  ConstructorInfo superConstructor = typeof(Object).
                  GetConstructor(new Type[0]);

  constructorIL.Emit(OpCodes.Call, superConstructor);
  constructorIL.Emit(OpCodes.Ldarg_0);
  constructorIL.Emit(OpCodes.Ldarg_1);
  constructorIL.Emit(OpCodes.Stfld, greetingField);
  constructorIL.Emit(OpCodes.Ret);

  // Create the GetMessage method.
  MethodBuilder getGreetingMethod = helloWorldClass.DefineMethod(
                                    "GetMessage",
                                    MethodAttributes.Public,
                                    typeof(String), null);

  // Generate IL for GetGreeting.
  ILGenerator methodIL = getGreetingMethod.GetILGenerator();
  methodIL.Emit(OpCodes.Ldarg_0);
  methodIL.Emit(OpCodes.Ldfld, greetingField);
  methodIL.Emit(OpCodes.Ret);

  // Fry the class CodeGenClass.
  typ = helloWorldClass.CreateType();
}

  Type typ;

  public Type T
  {
    get { returm this.typ; }
  }
}

Let’s see some description about the code. In the using section, we included the System.Reflection.Emit namespace. This namespace contains classes that allow the compiler/tools to produce (emit) metadata and Intermediate Language (MSIL). And the System.Threading namespace is used to get the current AppDomain. An AppDomain is a logical grouping of assemblies. This is something similar to the Win32 processes. We just code the steps explained above. First, we create a new AssemblyName object. This class describes the assembly’s unique identity. AssemblyName objects are used to bind and retrieve information about an assembly. We need to give a name to identify the assembly; here, it’s “CodeGenAssembly”. Once we have an initialized assembly name and current domainn, we can create the assembly by using the appDomain.DefineDynamicAssembly method. Note that we need to pass two parameters to this function:

Thread.GetDomain and the mode of access AssemblyBuilderAccess.Run tell that the assembly can be executed and not saved in memory. The DefineDynamicAssembly method returns an AssemblyBuilder object that we then cast to an Assembly object. At this point, we have a fully functional assembly in memory. Now, we need to create its temporary module and that module’s type.

Next, create a module dynamically by using the AssemblyBuilder.DefineDynamicModule method by specifying the module name. Here, it’s “CodeGenModule”, which returns a ModuleBuilder object. The DefineDynamicModule method returns an object of ModuleBuilder. Once we have ModuleBuilder, call its DefineType method to create a new type (class), namely the “CodeGenClass” and specifying the access modifier attributes TypeAttributes.Public. This method returns the TypeBuilder object. Once we have the TypeBuilder object, we can create any type of member we want by using TypeBuilder.DefineMember, a field by using TypeBuilder.DefineField, and a constructor by using TypeBuilder.DefineConstructor. Note that a private string field “message” is defined in the class to store the message that goes from the client. We have defined a constructor for our class with a string argument using TypeBuilder.DefineConstructor.

Now, all we do is decide what code to place in this method. To do this, the code instantiates an ILGenerator object by using the ConstructorBuilder.GetILGenerator method and calls the different ILGenerator methods to write MSIL code into the method.
Generate IL for the method. The constructor calls its superclass constructor. The constructor stores its argument in the private field.

Now, we create a “GetMessage” method by calling TypeBuilder.DefineMethod; this method returns a MethodBuilder object. Get the ILGenerator of this object and emit MSIL code for this method. Finally, call the CreateType of the class; this should always be the last step performed after you’ve defined the members for a new type. The Emit function emits the opcodes to the MSIL stream to the Just-In-Time compiler. The operation that we want to emit is specified by the OpCodes class’s members, which are passed as parameters. Once all the opcode work is finished, we need to call OpCodes.Ret, which returns from the current method. Then, we can take this Type in the client. That’s it…. We have completed the code generating part.

Build the DLL by using the following command line switch:

csc /t:library MSILGen.cs

Now, we develop a console client application to use this code generator. Listing 7 shows the code for this.

Don’t forget to refer MSILGen.dll to this application. First, create a new instance of the MSILGen class (Type). Create the MSILGen type dynamically by using the Activator.CreateInstance member. Note that we pass a string to the constructor argument to construct the object and the type. Great; now we have an activated object in memory. Just call tje Type class’s InvokeMember…. Mission done!!! We called the member.

Listing 7

using System;
using System.Reflection;
using System.Reflection.Emit;

class Class1
{
static void Main()
  {
    MSILGen codeGen = new MSILGen();

    Type typ = codeGen.T;

    // Create an instance of the "CodeGenClass" class.
    object codegen = Activator.CreateInstance(typ, new object[]
{"Hallo from code Generator."});

    // Invoke the "GetMessage" method of the "CodeGenClass" class.
object obj = typ.InvokeMember("GetMessage",
BindingFlags.InvokeMethod, null, codegen, null);

    Console.WriteLine("CodeGenClass.GetMessage
                       returned: "" + obj + """);
  }
}

Downloads


Download demo files – 43 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read