C# has a pretty neat namespace named “System.Reflection.Emit”, which allows you to build assemblies at runtime using Intermediate Language. This is a basic example of how to create an assembly, define a global function, and execute that function.
The following is an example. It is designed to be easy to follow, but here is a quick overview. The first thing we do is define a dynamic assembly. Within that assembly, we define a module. The whole assembly vs. module thing can be a little confusing. An assembly contains the manifest, and a collection of modules. Now in practice, an assembly usually only has a single module defined inside of it. Any C# project you create will also follow this 1 to 1 relationship. The module contains all of the actual code. Within the module, we define a global function (unsupported by C#, but IL is cool with it). Using the IL generator on the function, we can load a string onto the evaluation stack (“hello world”), call a function (Console.WriteLine), and then return from our function.
namespace ReflectionEmitHelloWorld { using System; using System.Reflection; using System.Reflection.Emit; class Program { static void Main(string[] args) { // Define the assembly and module const string AssemblyName = "Badflyer.HelloWorld.dll"; var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(AssemblyName), AssemblyBuilderAccess.RunAndCollect); var module = assembly.DefineDynamicModule(AssemblyName); // Define the method and get the Intermediate Language generator. var methodBuilder = module.DefineGlobalMethod("HelloWorld", MethodAttributes.Final | MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[0]); var ilGenerator = methodBuilder.GetILGenerator(); //// Too easy - but this works too // ilGenerator.EmitWriteLine("Hello World"); // Get the method info for writeline var writeline = typeof(Console).GetMethod("WriteLine", BindingFlags.Public | BindingFlags.Static, Type.DefaultBinder, new[] { typeof(string) }, null); // Load the string onto the evaluation stack ilGenerator.Emit(OpCodes.Ldstr, "Hello World"); // Call writeline ilGenerator.EmitCall(OpCodes.Call, writeline, new[] { typeof(string) }); // Return ilGenerator.Emit(OpCodes.Ret); // Create the global functions (since that is what we defined) module.CreateGlobalFunctions(); // Get our new method info var helloWorld = module.GetMethod("HelloWorld"); // Invoke our new method. helloWorld.Invoke(null, null); // Wait for input Console.ReadLine(); } } }
Pretty cool! You can do things in IL which you cannot do from C# directly. Here for example we defined a global function which is not the member of any class. Following is the resulting IL which was generated from saving our generated .dll. Just a warning, dotnet core does not allow you to save the generated .dll. The regular .NET framework does.
// To Save: var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(AssemblyName), AssemblyBuilderAccess.RunAndSave); // To Save: assembly.Save(AssemblyName); .method public final static void HelloWorld () cil managed { // Method begins at RVA 0x2050 // Code size 11 (0xb) .maxstack 1 IL_0000: ldstr "Hello World" IL_0005: call void [mscorlib]System.Console::WriteLine(string) IL_000a: ret }
And that’s all there is to it! You can do a lot of learning by writing C# code snippets, and viewing the generated IL within the .dll with a tool. (For example ILSpy).