2

After hours spent on this subject (and also dozens of pages visited) i am forced to ask for a help. I have seen a lot of posts on this subject, but i couldn't fix problem i get.

Basically, i want to do very simple thing: load assembly from my folder to application.


Here is question in short (all other details are explained in rest of text) I want to use Assembly.LoadFrom method to load my assembly (same assembly file is referenced with project, but with CopyLocal: false), but although assembly is loaded when method using it is invoked, program tries to load assembly from default locations. If it is found, then 2 same assemblies are loaded, and program uses last one, if it is not found then FileNotFoundException is raised. But, when i use same idea in Autocad plugin, everything works and exception is not raised, although files are not found.


I have test solution with 2 simple projects: TestExe (console application) and TestDll(dll library). I want to use types from TestDll in TestExe so i've added reference to TestDll(i am not referencing TestDll project, but file on specified location), but i want to load TestDll manually in the runtime. Why this is needed is explained at the end of text, with Autocad example.

As far as i understand, Assembly.LoadFrom method can be used for this purpose. So basic idea is: Load TestDll before method in TestExe is using it, so when method is called we have already loaded assembly. This way, whether referenced dll exists in default directories or not, we already have assembly loaded and it is going to be used.

From MSDN:

If an assembly with the same identity is already loaded, LoadFrom returns the loaded assembly even if a different path was specified.

So i understand that if i load dll once, every next load of same assembly (also from other location) will know that it is already added, so first one will be used.


Project: TestExe

//File: Program.cs
using System;
namespace TestExe
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(new ClassExe().ExeMethod());
        }
    }    
}
//File: ClassExe.cs
namespace TestExe
{
    class ClassExe
    {
        public string ExeMethod()
        {
            return new TestDll.ClassDll().DllMethod();
        }
    }
}

Project TestDll

using System.Reflection;

namespace TestDll
{
    public class ClassDll
    {
        public string DllMethod()
        {
            return Assembly.GetExecutingAssembly().Location;
        }
    }
}

As you can see, task is simple: display location of called assembly.

Lets say TestDll.dll is copied to application folder Extern\TestDll.dll.

If i set property CopyLocal: false of reference to TestDll in TestExe project, program fails with FileNotFoundException. (1) Is it because assembly is searched only in default directories (application dir and TestDll\TestDll.dll)?

Then i tried to load assembly manually, before using it:

static void Main(string[] args)
    {
        Assembly.LoadFrom(@"Extern\TestDll.dll");
        Console.WriteLine(new ClassExe().ExeMethod());
    }

But i get same FileNotFoundException, although TestDll has been loaded enter image description here

When i set attribute CopyLocal: true, then program works. But, it loads TestDll.dll again, from default location, and ignores assembly which is already loaded. enter image description here

The only way i managed program to work how i wanted (to be used assembly Extern\TestDll.dll) is with using AppDomain.AssemblyResolve event.


I have Autocad plugin which uses 10 different dlls, from different solutions. So i have plugin.dll which references 10 dll files across Documents folder, and set CopyLocal: true. But, when deployed to customer, i keep only plugin.dll in application folder, and all other dlls are in Libraries subdirectory. In static constructor of plugin class, i put Assembly.LoadFrom to be loaded from Libraries subdirectory, and everything works fine.

Sorry for long post but i wanted to explain it with details.

And thanks for any feedback :)

miki
  • 380
  • 6
  • 15
  • Not sure about the AutoCAD plugin at the end, are you developing an in-process or external (out-of-process) EXE? – Augusto Goncalves Jul 24 '15 at 14:52
  • @AugustoGoncalves In-process exe. Everything is done on thread of exe, say simplest console application. – miki Jul 24 '15 at 16:18
  • when I say "in-process" I mean a DLL that is NETLOADed inside AutoCAD and can use both .NET and COM APIs. Now a EXE (out-of-process) will run on a separate thread and invoke AutoCAD via COM API (Interop). That said, when you say "in-process" EXE, it doesn't make sense. – Augusto Goncalves Jul 24 '15 at 16:57
  • @AugustoGoncalves Aha, sorry. This example project is independent from ACAD, i just created it to test dll load. For ACAD i create DLL which is loaded with NETLOAD command, no exe involved there. – miki Jul 24 '15 at 17:17
  • And are you using AutoCAD APIs here? If not, I'll let other help... – Augusto Goncalves Jul 24 '15 at 17:58
  • @AugustoGoncalves Not exactly. I've posted ACAD only as working reference, sorry if it misleading. Can you explain me, or is there some detailed info, how autocad is loading dll? Is it using Assembly.Load, LoadFrom, or something else? Thank you for answers :) – miki Jul 24 '15 at 18:56
  • Are you trying to load a DLL into AutoCAD (aka standard plugin) or to start an .exe and then start AutoCAD ([process.start()](https://msdn.microsoft.com/en-us/library/system.diagnostics.process.start%28v=vs.110%29.aspx)) from it and then call some of the COM-interop interfaces of AutoCAD from there (the .exe)? – Ognyan Dimitrov Jul 24 '15 at 19:42
  • @OgnyanDimitrov No, ACAD is running, i call NETLOAD command and load my dll. Then, in static constructor of MyCommands class i do Assembly.LoadFrom of referenced dlls from my main dll, and everything works. But what is confusing me is that same thing is not working in standalone test ConsoleApplication. I use same Assembly.LoadFrom, but for some reason it is ignoring loaded assembly, and tries to load it again from default locations. – miki Jul 24 '15 at 19:50
  • 2
    AutoCAD will basically to an Assembly.Load and some additional verification. Back to your problem, you may want to not load yourself, but let the system try to load, then you hook into Assembly Resolve event and load it. I did that once, it's an easy way to trick the .NET engine. – Augusto Goncalves Jul 24 '15 at 22:59
  • Can you explain why it is required to start an ,exe and then instruct AutoCAD from outside to load a .dll? I have plugin with more than 10 aside dlls and it loads correctly everything. @AugustoGoncalves suggested the correct approach. – Ognyan Dimitrov Jul 30 '15 at 11:43
  • @OgnyanDimitrov .exe in this example does not have any connection to autocad. It should display problem of loading same dll twice, ignoring the first one loaded and using the second one. If second one is not found, then i got an error, although one dll is already loaded. This has something with AppDomain, but i don't know what. – miki Jul 31 '15 at 14:58
  • @OgnyanDimitrov In autocad everything works well. Before program tries to load referenced dll i load it manually, and it works. So i am confused why it is not working in .exe example, why the first loaded dll (manually loaded) is ignored. With AssemblyResolve both examples are working fine, so i have working solution. Thanks for all help :) – miki Jul 31 '15 at 15:02

2 Answers2

1

You can try by checking the load context of an assembly. Here is an example and here is an article.

The basics are that AutoCad loads its assemblies and plugin assemblies in the same AppDomain where the process of acad.exe is running therefore they are visible to all other assemblies that are loaded because they are references of a plug-in dll. But when you load a dll in acad process it will not be visible for the .exe and vice versa - they both run in different app domains and do not see each other.

Community
  • 1
  • 1
Ognyan Dimitrov
  • 6,026
  • 1
  • 48
  • 70
  • Thanks for the answer :) In that article i have found the only (for me) reasonable explanation. For **Neither** context it says: *"Nothing can bind to this assembly unless you've subscribed to the AssemblyResolve event."* So, if this is the case, i only have to find explanation why it is loaded in Neither context, when i am using LoadFrom method... – miki Aug 02 '15 at 01:08
1

For all interested in this topic, i have found the answer.

I will take look back once more: idea was to reference dll from project and use it in code, but later when application is deployed to load same dll manually. So loading referenced dll will fail, but before that, dll will be loaded manually, so everything should work. This wasn't the case, because although dll was loaded manually, it still searched for referenced one, and that made problems.

In this extensive article about CLR Binder, this paragraph explains my problem:

Now, let's talk about the location of the assembly, while identifying if the assembly loaded via LoadFrom() is the same as the Assembly loaded via Load(). Even if the types in two assemblies are identical, if the two assemblies are loaded from different paths, they are not considered identical as far as loader contexts are concerned. This leads to situations where the same assembly is loaded repeatedly in the same application domain, but into different contexts (Load and LoadFrom) and a type in the assembly in the Load context will not be allowed to be the same type in the LoadFrom context (even if they are the same assemblies as far as the assembly identities are concerned).

Thanks to everyone for your time and help.

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
miki
  • 380
  • 6
  • 15