4

I'm writing a project that dynamically compiles and executes c# code. The problem is that sometimes I want the code to call another DLL (for the sake of this sample I called it "ANOTHER.DLL"). It works fine in .Net 4.5, but fails in .Net Core and I can't figure out why. Any help is appreciated!

Code compiles successfully, but gives an error when the method is executed. Error is:

FileNotFoundException: Could not load file or assembly 'ANOTHER, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.

The ANOTHER.dll is located in the same /bin/debug folder, and is definitely accessible (code compiles!)

I noticed I can fix the issue by adding reference to ANOTHER.DLL to the project, but it defeats the purpose of dynamic compilation.

I tried this in .Net Core 2.0 - 3.1

ANOTHER.DLL is .Net Standard 2.0 (but same result with .Net Standard 2.1, or .Net Framework). Also tried various versions of Microsoft.CodeAnalysis package, all giving me same error.

var eval = new Evaluator();

string code = @"
    using System;
    namespace RoslynCompileSample
    {
        public class Test
        {
            public string Hello{
            get {
                //return ""Hello"";
                
                var c = new ANOTHER.Class1();
                return c.HelloWorld();
                }
            }
        }
    }";

SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);

var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
List < MetadataReference > references = new List < MetadataReference > ();

references.Add(MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location));

string ReferenceList = "";
ReferenceList += "netstandard.dll\n";
ReferenceList += "System.Runtime.dll\n";
ReferenceList += "ANOTHER.dll\n";

string[] assemblies = ReferenceList.Split('\n');
foreach(string a in assemblies) {
  if (File.Exists(Path.Combine(assemblyPath, a.Trim()))) {
    references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, a.Trim())));
  }
  else if (File.Exists(a.Trim())) {
    string currDirectory = Directory.GetCurrentDirectory();
    references.Add(MetadataReference.CreateFromFile(Path.Combine(currDirectory, a.Trim())));
  }
  else {
    string exepath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
    if (File.Exists(Path.Combine(exepath, a.Trim()))) {
      references.Add(MetadataReference.CreateFromFile(Path.Combine(exepath, a.Trim())));
    }
  }
}

CSharpCompilation compilation = CSharpCompilation.Create("assembly", syntaxTrees: new[] {
  syntaxTree
},
references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release));

Assembly assembly;
using(var ms = new MemoryStream()) {
  EmitResult result = compilation.Emit(ms);

  ms.Seek(0, SeekOrigin.Begin);
  assembly = Assembly.Load(ms.ToArray());
}

var type = assembly.GetType("RoslynCompileSample.Test");

var prop = type.GetProperties();
var all = prop.Where(x =>x.Name == "Hello");
var info = all.FirstOrDefault(x =>x.DeclaringType == type) ? ?all.First();

var method = info.GetGetMethod();

object obj;
obj = assembly.CreateInstance("RoslynCompileSample.Test");

object r = method.Invoke(obj, new object[] {}); // this is where the error occurs
Insomniac
  • 639
  • 3
  • 19
Andrew
  • 43
  • 3
  • had `Bad IL format` exception with the code above, but managed to reproduce in this gist. the only string to change is `ANOTHER.dll` location https://gist.github.com/pwrigshi/ab228f96bb65f57c5db6a61314a5a250 – Yehor Androsov Oct 15 '20 at 08:45
  • Thank you for code formatting and gist! It definitely became more readable. `Bad IL format` means the code is not compiling. You can check `result.Diagnostics` to see the errors, but seems you already figured it out. Any ideas what is going on? Is it a bug in the `Microsoft.CodeAnalysis?` :-) – Andrew Oct 15 '20 at 13:59

1 Answers1

7

Solution is based on my gist

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            string code = @"
    using System;
    namespace RoslynCompileSample
    {
        public class Test
        {
            public string Hello{
            get {
                //return ""Hello"";
                
                var c = new ANOTHER.Class1();
                return c.HelloWorld();
                }
            }
        }
    }";
            var tree = SyntaxFactory.ParseSyntaxTree(code);
            string fileName = "mylib.dll";

            var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
            List<MetadataReference> references = new List<MetadataReference>();

            references.Add(MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location));
            references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "netstandard.dll")));
            references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")));
            references.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
            var anotherDLLReference = MetadataReference.CreateFromFile(@"C:\Users\jjjjjjjjjjjj\source\repos\ConsoleApp2\ANOTHER\bin\Debug\netcoreapp3.1\ANOTHER.dll");

            references.Add(anotherDLLReference);
            var compilation = CSharpCompilation.Create(fileName)
              .WithOptions(
                new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
              .AddReferences(references)
              .AddSyntaxTrees(tree);
            string path = Path.Combine(Directory.GetCurrentDirectory(), fileName);
            EmitResult compilationResult = compilation.Emit(path);
            if (compilationResult.Success)
            {
                // Load the assembly
                Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);

                var type = assembly.GetType("RoslynCompileSample.Test");

                var prop = type.GetProperties();
                var all = prop.Where(x => x.Name == "Hello");
                var info = all.FirstOrDefault(x => x.DeclaringType == type) ?? all.First();

                var method = info.GetGetMethod();

                object obj;
                obj = assembly.CreateInstance("RoslynCompileSample.Test");

                object r = method.Invoke(obj, new object[] { });
            }

        }
    }
}

To be fair, I have 0 idea how it works, since I am not familiar with working with assemblies on this level, but somehow I managed to get rid of exception.

Firstly, I checked AssemblyLoadContext.Default in the debugger. I noticed that reference to "ANOTHER.dll" is missing (although we previously added it) enter image description here

Then I added AssemblyLoadContext.Default.LoadFromAssemblyPath(@"path to my ANOTHER.dll");. And when I checked it again - ANOTHER.dll was there.

enter image description here

Finally, we can see our hello world message

enter image description here

So the code I added is basically one line

// Load the assembly
Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);

var a = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\Users\jjjjjjjjjjjj\source\repos\ConsoleApp2\ANOTHER\bin\Debug\netcoreapp3.1\ANOTHER.dll");

var type = assembly.GetType("RoslynCompileSample.Test");

This works with both ANOTHER.dll targeting Standard 2.0 and .NET Core 3.1

Would be nice if someone smart actually told how it works.

Yehor Androsov
  • 4,885
  • 2
  • 23
  • 40
  • 4
    You are life saver! Thank you so much! I ended up adding `AssemblyLoadContext.Default.LoadFromAssemblyPath();` to every assembly that is added to reference list (i.e. loading them to current context, while adding to reference list for compilation). Also checked, and it works for .Net Framework DLLs as well, just need to add reference to `mscorlib.dll` – Andrew Oct 15 '20 at 18:31
  • 1
    You saved my whole month !! Thank you very much (-; – AmirHosseinMp02 Oct 11 '22 at 09:48