Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use CodeDOM to create and load an assembly in an AppDomain?

I am working on a project that will use CodeDOM to create a class that evaluates a user-defined expression, creates an assembly for the class, and loads the assembly. Since there can be a fair number of user-defined expressions, I would like to first create an AppDomain, execute the CodeDOM creation/loading and executing for the assembly within that AppDomain, and then unload the AppDomain.

I've searched quite a bit, and found many examples of how to load an existing assembly into an AppDomain, but I can't seem to find one that shows me how to create the assembly from within the AppDomain.

This example (DynamicCode) creates an assembly using CodeDOM, and then loads it into an AppDomain, however, the author is generating the assembly to disk. I would prefer to generate the assembly in memory, so that I do not have to manage the cleanup of the generated assemblies. (even though this does create a .dll in a temp folder).

Can anyone point me to an example of how to do this?

Any help would be greatly appreciated.

I've included some excerpts from my code so you can all get a feel for what I have so far:

private string CreateSource()
{
    CodeCompileUnit codeUnit = new CodeCompileUnit();
    CodeNamespace codeNamespace = new CodeNamespace(Namespace);
    CodeTypeDeclaration codeClass = new CodeTypeDeclaration
    {
        Name = "ExpressionEvaluator",
        IsClass = true,
        TypeAttributes = TypeAttributes.Public | TypeAttributes.Sealed
    };

    codeNamespace.Types.Add(codeClass);
    codeUnit.Namespaces.Add(codeNamespace);

    AddMethods(codeClass);

    string result = GenerateSourceCode(codeUnit);

    return result.ToString();
}

private CompilerResults CompileSource(string source)
{
    using (CodeDomProvider provider = new CSharpCodeProvider())
    {
        CompilerParameters parameters = CreateCompilerParameters();
        CompilerResults result = CompileCode(provider, parameters, source);

        return result;
    }
}

private static CompilerParameters CreateCompilerParameters()
{
    CompilerParameters result = new CompilerParameters
    {
        CompilerOptions = "/target:library",
        GenerateExecutable = false,
        GenerateInMemory = true
    };

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

    return result;
}

private object RunEvaluator(CompilerResults compilerResults)
{
    object result = null;
    Assembly assembly = compilerResults.CompiledAssembly;

    if (assembly != null)
    {
        string className = "ExpressionEvaluator";
        object instance = assembly.CreateInstance("Lab.ExpressionEvaluator");

        Module[] modules = assembly.GetModules(false);

        Type type = (from t in modules[0].GetTypes()
                     where t.Name == className
                     select t).FirstOrDefault();

        MethodInfo method = (from m in type.GetMethods()
                             where m.Name == "Evaluate"
                             select m).FirstOrDefault();

        result = method.Invoke(instance, null);
    }
    else
    {
        throw new Exception("Unable to load Evaluator assembly");
    }

    return result;
}

I believe these code snippets show the basic functionality of my project. Now all I need to do is wrap it in its own AppDomain.

like image 729
Welton v3.61 Avatar asked Aug 18 '10 14:08

Welton v3.61


2 Answers

I found the answer that I was looking for at http://www.softwareinteractions.com/blog/2010/2/7/loading-and-unloading-net-assemblies.html. He has a nice article detailing the creation of an AppDomain and loading an assembly as a plugin. I followed his example and was able to create an AppDomain, create a proxy for my ExpressionEvaluator class factory and successfully call it and receive results.

like image 161
Welton v3.61 Avatar answered Oct 01 '22 03:10

Welton v3.61


Chicken and egg problem. You need a little bootstrapper assembly that you can load in the new AppDomain. With a well-known class that loads the CodeDom generated assembly and gets it going.

Doing this with GenerateInMemory is pretty pointless, you would have to serialize that into the new AppDomain. That's just a bunch of overhead, might as well load it from disk, it is there anyway. And it is already in memory. The file system cache's memory. Loading it will be very fast since it doesn't actually have to be read off the disk.

like image 29
Hans Passant Avatar answered Oct 01 '22 02:10

Hans Passant