The Test method in the code below succeeds in a console test app, but fails when I call it from within an Arcmap add-in, throwing a ReflectionTypeLoadException with a loader exception saying:
Could not load file or assembly 'KompilerLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
KompilerLib is a windows class library (3.5) project and this is the only file in the project (one interface and one class).
Is there something special I need to do if I'm using CodeDom.Compiler inside an add-in?
using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom.Compiler;
using System.Diagnostics;
using System.IO;
namespace KompilerLib
{
public interface IKalkulation
{
string Test();
}
public class Kompiler
{
private const string TYPENAME = "Kalk.Kalkulation";
public IKalkulation Kompile(string language, List<string> references, string source)
{
CodeDomProvider provider = CodeDomProvider.CreateProvider(language);
var parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = false;
foreach (string reference in references)
{
parameters.ReferencedAssemblies.Add(reference);
}
var results = provider.CompileAssemblyFromSource(parameters, source);
if (results.Errors == null || results.Errors.Count == 0)
{
Debug.Print(results.CompiledAssembly.ReflectionOnly.ToString());
foreach (Type t in results.CompiledAssembly.GetTypes())
Debug.Print(t.Name);
var type = results.CompiledAssembly.GetType(TYPENAME);
if (type == null)
throw new Exception("type not found: " + TYPENAME);
object o = Activator.CreateInstance(type);
if (o == null)
throw new Exception("unable to createinstance");
var kalkulation = o as IKalkulation;
if (kalkulation == null)
throw new Exception("unable to cast to IKalkulation");
return kalkulation;
}
else
{
StringBuilder sb = new StringBuilder();
foreach (CompilerError err in results.Errors)
sb.AppendLine(err.ErrorText);
throw new Exception(sb.ToString());
}
}
public static string Test()
{
var kompiler = new Kompiler();
var list = new List<string>();
list.Add("System.dll");
string path = kompiler.GetType().Assembly.Location;
if (!File.Exists(path))
throw new Exception("file not found " + path);
list.Add(path);
string source = GetSource();
var kalk = kompiler.Kompile("CSharp", list, source);
return kalk.Test();
}
private static string GetSource()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("using KompilerLib; ");
sb.AppendLine("namespace Kalk ");
sb.AppendLine("{ ");
sb.AppendLine(" public class Kalkulation : IKalkulation ");
sb.AppendLine(" { ");
sb.AppendLine(" public string Test() ");
sb.AppendLine(" { ");
sb.AppendLine(" return \"Hello World\"; ");
sb.AppendLine(" } ");
sb.AppendLine(" } ");
sb.AppendLine("} ");
return sb.ToString();
}
}
}
Update
Here's the fix using AssemblyResolve, thanks to blah238. I'm still curious why this is a problem within arcmap.exe, but not within my console tester exe. Also seems like the easiest assembly to resolve should be the one the compiler is running within.
public IKalkulation Kompile(string language, List<string> references, string source)
{
CodeDomProvider provider = CodeDomProvider.CreateProvider(language);
var parameters = new CompilerParameters();
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = false;
foreach (string reference in references)
{
parameters.ReferencedAssemblies.Add(reference);
}
// this sure seems like a hack ...
AppDomain.CurrentDomain.AssemblyResolve += (s,args) =>
{
if (args.Name == this.GetType().Assembly.FullName)
return this.GetType().Assembly;
else
return null;
};
var results = provider.CompileAssemblyFromSource(parameters, source);
if (results.Errors == null || results.Errors.Count == 0)
{
var type = results.CompiledAssembly.GetType(TYPENAME);
if (type == null)
throw new Exception("type not found: " + TYPENAME);
object o = Activator.CreateInstance(type);
if (o == null)
throw new Exception("unable to createinstance");
var kalkulation = o as IKalkulation;
if (kalkulation == null)
throw new Exception("unable to cast to IKalkulation");
return kalkulation;
}
else
{
StringBuilder sb = new StringBuilder();
foreach (CompilerError err in results.Errors)
sb.AppendLine(err.ErrorText);
throw new Exception(sb.ToString());
}
}