November 28, 2014
Hot Topics:

Discover Dynamic Code Compilation

  • October 7, 2005
  • By Mark Strawmyer
  • Send Email »
  • More Articles »

Compile in Memory

The final step is to take the generated source code and compile it into an actual assembly. For this example, you'll contain the example to memory rather than a physical file. The actual compile action is performed through the specific language provider, which in this case will be the CSharpCodeProvider. You set any desired compile options and then compile the assembly from the source code.

The following sample code generates an assembly from your constructed code:

static Assembly CompileInMemory(string code){   CSharpCodeProvider provider = new CSharpCodeProvider();   CompilerParameters options = new CompilerParameters();   options.IncludeDebugInformation = false;   options.GenerateExecutable = false;   options.GenerateInMemory = true;   CompilerResults results =      provider.CompileAssemblyFromSource(options, code);   provider.Dispose();   Assembly generatedAssembly = null;   if (results.Errors.Count == 0)   {      generatedAssembly = results.CompiledAssembly;   }   return generatedAssembly;}

A call such as Assembly a = CompileInMemory(GenerateCode(typeNamespace, typeName, "return inputMessage;")); would yield a new assembly. You could make subsequent calls with whatever method body you want in place of the "return inputMessage;" to create desired variants.

Creating an Instance

You've dynamically created an assembly and compiled it into memory. The next task is to create an instance of the class from the assembly. This is actually more complicated than it sounds. The assembly you've created exists in memory. No references to it exist, so you can't simply create a new instance because they wouldn't resolve. As a workaround, create a class to hold all of the compiled assemblies. You'll override the type resolution event so that when a type is requested you can use one of your types.

ExecutionHost Sample Code

The following code defines a class named ExecutionHost, which tracks all of your dynamically compiled assemblies:

using System;using System.Collections;using System.Reflection;namespace CodeGuru.CodeDomSample{   class ExecutionHost   {      private Hashtable assemblies = null;      public ExecutionHost()      {         assemblies = new Hashtable();         // Respond to the type resolution event request         // to intercept it and search our types         AppDomain.CurrentDomain.TypeResolve += new            ResolveEventHandler(CurrentDomain_TypeResolve);      }      private Assembly CurrentDomain_TypeResolve(object sender,                                                 ResolveEventArgs args)      {         // Search our assemblies for the desired type         Assembly a = null;         if (assemblies.ContainsKey(args.Name))         {            a = (Assembly)assemblies[args.Name];         }         return a;      }      public void AddAssembly(string fullTypeName, Assembly a)      {         assemblies.Add(fullTypeName, a);      }      public string Execute(string typeFullName, string msg)      {         // Try to create the necessary type that triggers the event         Type targetType = Type.GetType(typeFullName, true, true);         object target =            targetType.Assembly.CreateInstance(typeFullName);         IExecutableModule m = (IExecutableModule)target;         return m.SayHello(msg);      }   }}namespace CodeGuru.CodeDomSample{   public interface IExecutableModule   {      string SayHello(string inputMessage);   }}public static CodeCompileUnit CreateExecutionClass(string typeNamespace,                                                   string typeName,                                                   string scriptBody){   // Create the CodeCompileUnit to contain the code   CodeCompileUnit ccu = new CodeCompileUnit();   // Assign the desired namespace   CodeNamespace cns = new CodeNamespace(typeNamespace);   cns.Imports.Add(new CodeNamespaceImport("System"));   ccu.Namespaces.Add(cns);   // Create the class   CodeTypeDeclaration parentClass = new CodeTypeDeclaration(typeName);   cns.Types.Add(parentClass);   // NEW LINE - Add an implementation for the IExecutableModule   // interface   parentClass.BaseTypes.Add(typeof(CodeGuru.CodeDomSample.                                    IExecutableModule));   // Create the SayHello method that takes a parameter and has a   // string return   CodeMemberMethod method = new CodeMemberMethod();   method.Name = "SayHello";   method.Attributes = MemberAttributes.Public;   CodeParameterDeclarationExpression arg = new      CodeParameterDeclarationExpression(typeof(string),                                         "inputMessage");   method.Parameters.Add(arg);   method.ReturnType = new CodeTypeReference(typeof(string));   // Add the desired code to the method body   CodeSnippetStatement methodBody = new      CodeSnippetStatement(scriptBody);   method.Statements.Add(methodBody);   parentClass.Members.Add(method);   return ccu;}

Notice the Execute method. It uses reflection to create an instance of the desired type. This will trigger the _TypeResolve event and allow one of your assemblies to be returned if it has been added into the ExecutionHost through the AddAssembly method.

Also notice the added interface implementation in your dynamically generated code. Without it, you wouldn't know how to call the desired method. The IExecutableModule interface is provided along with an updated copy of the CreateExecutionClass method that adds the additional interface as a base class for your generated code.

Additionally, because you added an interface that now needs to be used within your CodeGuru.DynamicCode assembly, you must add a reference to the CodeGuru.CodeDomSample that contains the IExecutableModule declaration. See the updated copy of CompileInMemory below:

static Assembly CompileInMemory(string code){   CSharpCodeProvider provider = new CSharpCodeProvider();   CompilerParameters options = new CompilerParameters();   options.IncludeDebugInformation = false;

options.GenerateExecutable = false; options.GenerateInMemory = true; // NEW LINE – add a reference to the necessary assembly options.ReferencedAssemblies.Add( "CodeGuru.CodeDomSample.exe"); CompilerResults results = provider.CompileAssemblyFromSource(options, code); provider.Dispose(); Assembly generatedAssembly = null; if (results.Errors.Count == 0) { generatedAssembly = results.CompiledAssembly; } return generatedAssembly;}

Now, you can use the following test code to test the end-to-end process of dynamically generating an assembly and then making a call to a method:

string typeNamespace = "CodeGuru.DynamicCode";string typeName = "ScriptType" + Guid.NewGuid().ToString("N");Assembly a = CompileInMemory(GenerateCode(typeNamespace, typeName,                                          "return inputMessage;"));ExecutionHost host = new ExecutionHost();string fullTypeName = typeNamespace + "." + typeName;host.AddAssembly(fullTypeName, a);string test = host.Execute(fullTypeName, "Hello World!");

Using the Guid generates a unique object name each time you generate code.

Possible Enhancements

You've run through a very basic example to demonstrate a complex topic and the code required to accomplish the task. The added Guid in the type name ensures it is unique, so you can compile and use as many different types as you desire without clashing on names. Feel free to alter the "return inputMessage;" method body to whatever code you would like and experiment. You could change it so that all of the code for the method body is stored in a database and retrieved at runtime.

Future Columns

The next column has yet to be determined. If you have something in particular that you would like me to explain, please contact me at mstrawmyer@crowechizek.com.

About the Author

Mark Strawmyer (MCSD, MCSE, MCDBA) is a senior architect of .NET applications for large and mid-sized organizations. He is a technology leader with Crowe Chizek in Indianapolis, Indiana, specializing in architecture, design, and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the second year in a row. You can reach Mark at mstrawmyer@crowechizek.com.





Page 2 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel