Take the 2-minute tour ×
Geographic Information Systems Stack Exchange is a question and answer site for cartographers, geographers and GIS professionals. It's 100% free, no registration required.

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());
    }
}
share|improve this question
    
Yeah I don't fully understand why it's needed either, but at least it works (for now). My guess is ESRI is doing something non-standard with their add-in system so the standard .NET assembly resolving behavior breaks down under certain conditions. From what I have gathered, ESRI is using Assembly.LoadFrom to load add-in assemblies manually instead of setting up a separate AppDomain for each add-in so that their probing path can be set individually and letting the .NET runtime load it normally. The side effect of this is that the probing path is incorrect for subsequent assembly loads. –  blah238 Oct 9 '11 at 23:10
    
I am not sure why the same assembly would need to be loaded more than once though, except that it might be for security purposes. –  blah238 Oct 9 '11 at 23:10
add comment

1 Answer

up vote 2 down vote accepted

I have run into similar issues when using custom .NET ConfigurationSection's and when using binary (de)serialization. This problem is discussed in the comments of this question: ESRI ArcMap Add-in with app.settings not recognizing app.config changes

The problem seems to be that some assemblies referenced by add-ins are not able to be resolved correctly because the add-in application domain is not separate from the main application domain and so the .NET runtime looks in the main application's assembly probing path and doesn't find them. The fix is to use Assembly.LoadFrom and handle the AppDomain.AssemblyResolve event.

This was the fix that worked for me with the custom ConfigurationSection issue. I hope it works in your case as well!

share|improve this answer
    
Thanks! I've updated my question with how your suggestion fixed things. –  Kirk Kuykendall Oct 9 '11 at 21:52
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.