9

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 */
}
Community
  • 1
  • 1
Aage Torleif
  • 1,907
  • 1
  • 20
  • 37
  • 3
    The GenerateInMemory property is an illusion, there currently is no CodeDom compiler that runs in-process. You always get a file, it simply gets loaded automatically when you set GenerateInMemory = true. Don't use it if you want to load the code into another appdomain. – Hans Passant Jul 21 '14 at 18:22
  • Oh god that's upsetting. Guess I'll tinker with an alternative. – Aage Torleif Jul 21 '14 at 18:28
  • Why are you trying to load up a new AppDomain? Do you need the ability to unload it later? – Philip Pittle Jul 21 '14 at 18:39
  • I may be misled with using AppDomain, so as I understand it it provides a layer of isolation. What that means to me is... 1. lets say my code being compiled and run remotely wants to read a file, I want to apply a security policy that says what files are okay. 2. I don't want someone to write "break out" code. So I guess I believe this to be a sandbox of sorts. It's all really early envelopment. More proof of concept then working model. __I can do entirely without it.__ – Aage Torleif Jul 21 '14 at 18:43

1 Answers1

8

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'
   }
Community
  • 1
  • 1
Philip Pittle
  • 11,821
  • 8
  • 59
  • 123
  • I've been looking at this, just a BTW. It seems a lot like the step I've started from. Meaning I've already been able to do this when I have precursor knowledge. It's the doing it on any arbitrary C# source, and doing it without creating files, which seems unavoidable, which is fine. I have been looking this over though thanks for your answer. The `Activator.CreateInstance(type, constructorArgs);` looks like what I want next. – Aage Torleif Jul 21 '14 at 20:43
  • Yes, you were close. The `Activator` will actually create instances of a `Type` and you can use `dynamic` to call methods on the returned instance. For example, if you know your source class implements an interface, you can then call those methods. – Philip Pittle Jul 21 '14 at 20:45
  • I was assuming I could use something like GetMethods() to enumerate them. http://msdn.microsoft.com/en-us/library/system.type.getmethods(v=vs.110).aspx – Aage Torleif Jul 21 '14 at 20:48
  • 1
    You can of course use reflection. If it helps, here is my full CompilerResult extensions class. It has helper methods to call a method using reflection: https://github.com/ppittle/pMixins/blob/master/pMixins.Tests.Common/Extensions/CompilerResultsExtensions.cs – Philip Pittle Jul 21 '14 at 20:54
  • This definitely lead me in the right direction, It's not "done", but its given me enough to work with where I know how to do it now. Thanks Philip. – Aage Torleif Jul 22 '14 at 14:59
  • Good to hear! When you get the final solution, it would be nice if you could post it or link to it on here. – Philip Pittle Jul 22 '14 at 15:13