Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# CompilerResults GenerateInMemory?

I've been following this StackOverflow question. Its the closest thing I can find, but not quite.

Let me explain what my end goal is before my question, I'm making a compiler platform that's web enabled, because of that, I want to do everything in memory (make no files), so I want to be able to compile code, and then be able to reference the objects in the class I just compiled for arbitrary testing. I know how it unsafe it sounds, so input in regard to security is welcome.

My question is how do I compile C# source to memory, and create an instance of that class?

Currently I'm at a step where I can generate valid .dll's and import and use it inside VisualStudio by hand.

My next 2 steps are:

  • load the assembly automatically (This is what im asking here)
    • This would mean i no longer have to give a path to the dll, and get address the enclosed class's members by hand
  • arbitrarily reference it's members
    • This mean's I can create an interface to the class without precursor knowledge of its members, kind of like how a foreach loop works on key value pairs.

To attempt this entirely in memory I've tried this. (source then explanation)

private object sourceToObj(string source) {
  string source = "...";  /*my class*/
  CSharpCodeProvider pro = new CSharpCodeProvider();

  CompilerParameters params = new CompilerParameters();
    params.GenerateInMemory = true;
    params.GenerateExecutable = false; 
    params.ReferencedAssemblies.Add("System.dll");

  CompilerResults res = pro.CompileAssemblyFromSource( params, source );

  Assembly DOTasm = res.CompiledAssembly;

  AppDomain domain = AppDomain.CreateDomain( "thisdomain" );
    domain.load( DOTasm , /*???*/ );
    domain.CreateInstanceAndUnwrap( DOTasm .FullName, /*???*/ );


  return /*???*/;
}

Finally, as this point in the code I'd hope to return some object I can call a property of. So calling object obj = new sourceToObj(source).class(); or something would be possible.

Going down this path, which may indeed be the wrong path leaves me with 3 unknowns.

  • What is a System.Security.Policy.Evidence assemblySecurity object.
  • What is the proper parameter for AppDomain.CreateInstanceAndUnwrap()
  • How then do i return this as an object?

Of course this method could be wrong, it's based off the link above which is close, but no turkey.


Edit: After more research I wanted to include an example of a source file.

namespace testNS {
  public partial class i18Rule {
     private string i18_Name;
     private string i18_Value;
     public void setName(string s) {
         i18_name = s;
     }
     /* Other obvious functions */
  };
};

I believe I made a little bit of progress and went onto the second clause of my question, how to create an instance of it.

I went ahead and used an AppDomain to contain my assembly. I also went the route of writing to disk and reading it int a byte array as done in this question i happened upon Compile c# on the fly.

/* not the final method, see Philips answer for tryLoadCompiledType which validates this works */
private void sourceToUnitTest(string source, callBack CB) {
    var pro = new CSharpCodeProvider();

    var DOMref = AppDomain.CurrentDomain.GetAssemblies()
            .Where(obj => !obj.IsDynamic) 
            .Select(obj => obj.Location)
            .ToArray();

    var Cparams = new CompilerParameters( DOMref );
        Cparams.OutputAssembly = "SOURCE.DLL";

        CompilerResults res = pro.CompileAssemblyFromSource(Cparams, source);

        Assembly asm = res.CompiledAssembly;

        Type[] allTypes =  res.CompiledAssembly.GetTypes();

        foreach (Type t in allTypes)
        {
            TryLoadCompiledType(res, t.ToString());
            Debug.WriteLine(t.ToString());
        }


        /* I don't return I do something with each type here */
}
like image 628
Aage Torleif Avatar asked Jul 21 '14 18:07

Aage Torleif


1 Answers

How do I compile C# source to memory, and create an instance of that class?

I faced a similar issue when I wanted to take source code as input and compile and execute it. This is what I came up with after reading Is it possible to dynamically compile and execute C# code fragments?:

public CompilerResults CompileSource(string sourceCode)
{
        var csc = new CSharpCodeProvider(
            new Dictionary<string, string>() { { "CompilerVersion", "v4.0" } });

        var referencedAssemblies =
                AppDomain.CurrentDomain.GetAssemblies()
                .Where(a => !a.FullName.StartsWith("mscorlib", StringComparison.InvariantCultureIgnoreCase))
                .Where(a => !a.IsDynamic) //necessary because a dynamic assembly will throw and exception when calling a.Location
                .Select(a => a.Location)
                .ToArray();

        var parameters = new CompilerParameters(
            referencedAssemblies);

        return csc.CompileAssemblyFromSource(parameters,
            sourceCode);
 }

Then I have a helper function:

 public static object TryLoadCompiledType(this CompilerResults compilerResults, string typeName, params object[] constructorArgs)
    {
        if (compilerResults.Errors.HasErrors)
        {
            Log.Warn("Can not TryLoadCompiledType because CompilerResults.HasErrors");
            return null;
        }

        var type = compilerResults.CompiledAssembly.GetType(typeName);

        if (null == type)
        {
            Log.Warn("Compiled Assembly does not contain a type [" + typeName + "]");
            return null;
        }

        return Activator.CreateInstance(type, constructorArgs);
    }

So to put it together

   public void Example(){
       dynamic instance = 
            CompileSource("namespace Test{public class DynamicCompile{ /*method*/}}")
            .TryLoadCompiledType("Test.DynamicCompile");

        //can now call methods on 'instance'
   }
like image 73
Philip Pittle Avatar answered Oct 08 '22 10:10

Philip Pittle