יום חמישי, 16 במאי 2013

Generated .net code on the fly

Sometimes I need to provide the end user capabilities that require dynamic compilation.
For an example a signal generator with option to allow the user to write a complicated phrase that express the Y value according to a given x (time ) value.
.net Code dom is used in order to generate on the fly the code based on the user pharse.

First declare and interface to be implement using the user dynamic code for an example:
public interface ISignalGenerator
    {
        double GetYValue(double pTime);
    }

Then declare the template of the interface  implementer.
The following code should be store as a string

  public class SignalGeneratorT%ClassName% : ISignalGenerator
{
public double GetYValue(double pTime)
{
return %UserCode%;
}
}
The program responsibility to switch the %ClassName% with the name of the function given by the user or generated randomly.
And to switch the %UserCode% with the phrase the user entered.

The following code is used to generate a .net assembly by the code , a list of reference assemblies and the programing language.

 public static System.Reflection.Assembly CompileTemplatedCode (string Code, string Language, params string[] ReferencedAssemblies)
{

Debug.Assert (System.String.IsNullOrEmpty(Code) == false );
            Debug.Asser(System.CodeDom.Compiler.CodeDomProvider.IsDefinedLanguage(Language))
using (System.CodeDom.Compiler.CodeDomProvider cdp =
System.CodeDom.Compiler.CodeDomProvider.CreateProvider(Language))
{
System.CodeDom.Compiler.CompilerParameters cp =
System.CodeDom.Compiler.CodeDomProvider.GetCompilerInfo
(Language).CreateDefaultCompilerParameters();

cp.ReferencedAssemblies.Add("System.dll");

if (ReferencedAssemblies != null)
{
cp.ReferencedAssemblies.AddRange(ReferencedAssemblies);
}
                cp.GenerateInMemory = true;

System.CodeDom.Compiler.CompilerResults cr =
cdp.CompileAssemblyFromSource(cp,
Code
);
if (cr.Errors.HasErrors)
{

System.Exception err = new System.Exception("Compilation failure" + cr.Errors[0].ErrorText);
err.Data["Errors"] = cr.Errors;
err.Data["Output"] = cr.Output;
throw (err);
}

return (cr.CompiledAssembly);
}
}

Note that a reference to the assembly that declare the ISignalGenerator interface should be include in the method assemblies list.

The current assembly cannot be referenced implicitly by the generated code but should be added to the assemblies list explicitly .
After call to generate the code and checking that there are no errors an assembly reference is returned .
The following code generate and instance of the class
Assembly theAssembly = CompileTemplatedCode (pTheTemplatedSource, "C#", "MyExtraAssembly");
ISignalGenerator theISignalGenerator = (ISignalGenerator)theAssembly.CreateInstance(
<TheClassName>, false, BindingFlags.CreateInstance,
null, null, null, null);
 
Note:
The class name should be unique for every generation phase.
The generated assembly residents in memory until the hosting application is shut down this may cause a memory leak issue if a a lot of classes are generated in a working session.

אין תגובות:

הוסף רשומת תגובה