How to Load an Assembly to AppDomain with all references recursively?

144,171

Solution 1

You need to invoke CreateInstanceAndUnwrap before your proxy object will execute in the foreign application domain.

 class Program
{
    static void Main(string[] args)
    {
        AppDomainSetup domaininfo = new AppDomainSetup();
        domaininfo.ApplicationBase = System.Environment.CurrentDirectory;
        Evidence adevidence = AppDomain.CurrentDomain.Evidence;
        AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo);

        Type type = typeof(Proxy);
        var value = (Proxy)domain.CreateInstanceAndUnwrap(
            type.Assembly.FullName,
            type.FullName);

        var assembly = value.GetAssembly(args[0]);
        // AppDomain.Unload(domain);
    }
}

public class Proxy : MarshalByRefObject
{
    public Assembly GetAssembly(string assemblyPath)
    {
        try
        {
            return Assembly.LoadFile(assemblyPath);
        }
        catch (Exception)
        {
            return null;
            // throw new InvalidOperationException(ex);
        }
    }
}

Also, note that if you use LoadFrom you'll likely get a FileNotFound exception because the Assembly resolver will attempt to find the assembly you're loading in the GAC or the current application's bin folder. Use LoadFile to load an arbitrary assembly file instead--but note that if you do this you'll need to load any dependencies yourself.

Solution 2

Once you pass the assembly instance back to the caller domain, the caller domain will try to load it! This is why you get the exception. This happens in your last line of code:

domain.Load(AssemblyName.GetAssemblyName(path));

Thus, whatever you want to do with the assembly, should be done in a proxy class - a class which inherit MarshalByRefObject.

Take in count that the caller domain and the new created domain should both have access to the proxy class assembly. If your issue is not too complicated, consider leaving the ApplicationBase folder unchanged, so it will be same as the caller domain folder (the new domain will only load Assemblies it needs).

In simple code:

public void DoStuffInOtherDomain()
{
    const string assemblyPath = @"[AsmPath]";
    var newDomain = AppDomain.CreateDomain("newDomain");
    var asmLoaderProxy = (ProxyDomain)newDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(ProxyDomain).FullName);

    asmLoaderProxy.GetAssembly(assemblyPath);
}

class ProxyDomain : MarshalByRefObject
{
    public void GetAssembly(string AssemblyPath)
    {
        try
        {
            Assembly.LoadFrom(AssemblyPath);
            //If you want to do anything further to that assembly, you need to do it here.
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException(ex.Message, ex);
        }
    }
}

If you do need to load the assemblies from a folder which is different than you current app domain folder, create the new app domain with specific dlls search path folder.

For example, the app domain creation line from the above code should be replaced with:

var dllsSearchPath = @"[dlls search path for new app domain]";
AppDomain newDomain = AppDomain.CreateDomain("newDomain", new Evidence(), dllsSearchPath, "", true);

This way, all the dlls will automaically be resolved from dllsSearchPath.

Solution 3

http://support.microsoft.com/kb/837908/en-us

C# version:

Create a moderator class and inherit it from MarshalByRefObject:

class ProxyDomain : MarshalByRefObject
{
    public Assembly GetAssembly(string assemblyPath)
    {
        try
        {
            return Assembly.LoadFrom(assemblyPath);
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException(ex.Message);
        }
    }
}

call from client site

ProxyDomain pd = new ProxyDomain();
Assembly assembly = pd.GetAssembly(assemblyFilePath);

Solution 4

On your new AppDomain, try setting an AssemblyResolve event handler. That event gets called when a dependency is missing.

Solution 5

You need to handle the AppDomain.AssemblyResolve or AppDomain.ReflectionOnlyAssemblyResolve events (depending on which load you're doing) in case the referenced assembly is not in the GAC or on the CLR's probing path.

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

Share:
144,171

Related videos on Youtube

abatishchev
Author by

abatishchev

This is my GUID. There are many like it but this one is mine. My GUID is my best friend. It is my life. I must master it as I must master my life. Without me, my GUID is useless. Without my GUID I am useless.

Updated on July 08, 2022

Comments

  • abatishchev
    abatishchev almost 2 years

    I want to load to a new AppDomain some assembly which has a complex references tree (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll)

    As far as I understood, when an assembly is being loaded to AppDomain, its references would not be loaded automatically, and I have to load them manually. So when I do:

    string dir = @"SomePath"; // different from AppDomain.CurrentDomain.BaseDirectory
    string path = System.IO.Path.Combine(dir, "MyDll.dll");
    
    AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
    setup.ApplicationBase = dir;
    AppDomain domain = AppDomain.CreateDomain("SomeAppDomain", null, setup);
    
    domain.Load(AssemblyName.GetAssemblyName(path));
    

    and got FileNotFoundException:

    Could not load file or assembly 'MyDll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

    I think the key part is one of its dependencies.

    Ok, I do next before domain.Load(AssemblyName.GetAssemblyName(path));

    foreach (AssemblyName refAsmName in Assembly.ReflectionOnlyLoadFrom(path).GetReferencedAssemblies())
    {
        domain.Load(refAsmName);
    }
    

    But got FileNotFoundException again, on another (referenced) assembly.

    How to load all references recursively?

    Do I have to create references tree before loading root assembly? How to get an assembly's references without loading it?

    • Mick
      Mick over 9 years
      I've loaded assemblies like this many times before, I've never had to manually load all it's references. I'm not sure the premise of this question is correct.
  • abatishchev
    abatishchev about 15 years
    So I have to indicate requested assembly manually? Even it is in new AppDomain's AppBase ? Is there a way not to do that?
  • Tri Q Tran
    Tri Q Tran almost 13 years
    How is this solution put into context of creating a new AppDomain, can someone explain?
  • Christoph Meißner
    Christoph Meißner almost 12 years
    A MarshalByRefObject can be passed around appdomains. So I would guess that Assembly.LoadFrom tries to load the assembly in a new appdomain, what is only possible, if the calling object could be passed between those appdomains. This is also called remoting as described here: msdn.microsoft.com/en-us/library/…
  • NoWar
    NoWar over 11 years
    Nice solution I am going to put link to it here. stackoverflow.com/questions/12427323/…
  • Jduv
    Jduv over 11 years
    This doesn't work. If you execute the code and check the AppDomain.CurrentDomain.GetAssemblies() you'll see that the target assembly you're attempting to load is loaded into the current application domain and not the proxy one.
  • user1004959
    user1004959 over 11 years
    It doesn't. Actually, you get an exception on the line you're registering this event on the new AppDomain. You've to register this event on the current AppDomain.
  • Jduv
    Jduv about 11 years
    Check out the code I wrote to solve this problem: github.com/jduv/AppDomainToolkit. Specifically, look at the LoadAssemblyWithReferences method in this class: github.com/jduv/AppDomainToolkit/blob/master/AppDomainToolki‌​t/…
  • Aaronaught
    Aaronaught over 10 years
    This is complete nonsense. Inheriting from MarshalByRefObject doesn't magically make it load in every other AppDomain, it just tells the .NET framework to create a transparent remoting proxy instead of using serialization when you unwrap the reference from one AppDomain in another AppDomain (the typical way being the CreateInstanceAndUnwrap method). Can't believe this answer has over 30 upvotes; the code here just a pointlessly roundabout way of calling Assembly.LoadFrom.
  • Aaronaught
    Aaronaught over 10 years
    I've found that although this works most of the time, in some cases you actually still need to attach a handler to the AppDomain.CurrentDomain.AssemblyResolve event as described in this MSDN answer. In my case, I was trying to hook into the SpecRun deployment running under MSTest, but I think it applies to many situations in which your code might not run from the "primary" AppDomain - VS extensions, MSTest, etc.
  • Jduv
    Jduv over 10 years
    Ah interesting. I'll look into that and see if I can make that slightly easier to work with via ADT. Sorry that code's been slightly dead for a while now--we all have day jobs :).
  • Philip Daniels
    Philip Daniels almost 10 years
    @Jduv Would upvote your comment about 100 times if I could. Your library helped me solve a seemingly unsolvable problem I was having with dynamic assembly loading under MSBuild. You should promote it to an answer!
  • Mick
    Mick over 9 years
    Yes it looks like complete nonsense, yet it has 28 up votes and is marked as the answer. The link supplied doesn't even mention MarshalByRefObject. Quite bizarre. If this actually does anything I'd love someone to explain how
  • Dennis Kassel
    Dennis Kassel about 9 years
    Why do I have to load the assembly by using a proxy class? What is the difference compared to loading it using Assembly.LoadFrom(string). I am interested in the technical details, from the CLR's perspective. I would be very grateful if you could provide an answer.
  • Nir
    Nir about 9 years
    You use the proxy class in order to avoid the new assembly from being loaded in to your caller domain. If you'll use Assembly.LoadFrom(string), the caller domain will try to load the new assembly references and won't find them because it doesn't search for assemblies in the "[AsmPath]". (msdn.microsoft.com/en-us/library/yx7xezcf%28v=vs.110%29.asp‌​x)
  • ArthurVard
    ArthurVard about 8 years
    It works for me, I could load assembly from different folder and it works, before I use Load method it failed. if someone can explain how it exactly work and when we should not use this approach will be great.
  • Igor Bendrup
    Igor Bendrup over 7 years
    @Jduv are you sure that assembly variable will reference assembly from "MyDomain"? I think by var assembly = value.GetAssembly(args[0]); you will load your args[0] into both domains and assembly variable will reference copy from the main application domain
  • Joon w K
    Joon w K over 6 years
    @Jduv, I got error and the message I got is "Unable to cast transparent proxy to type "Proxy" when I applied the code in VSIX command handler. Could you let me know why I can not get same result as you have. Thanks in advance.
  • user2126375
    user2126375 about 6 years
    It does if class is inherited from MarshalByRefObject. It does not if class is marked only with [Serializable] attribute.
  • abatishchev
    abatishchev about 6 years
    Hi, if I remember correctly, the core issue was to how to load all dependencies recursively, hence the question. Please test your code by changing HelloWorld to return a class of type Foo, FooAssembly which has a property of type Bar, BarAssembly, i.e 3 assemblies total. Would it continue to work?
  • SimperT
    SimperT about 6 years
    Yes, need proper directory enumerated in the assembly probing stage. AppDomain has a ApplicationBase , however, I did not test it. Also config files you can specify assembly probing directories such as a app.config which a dll can use as well just set to copy in properties. Also, if you have control over the building of the assembly wishing to load in separate app domain, references can get a HintPath that specify were to look for it. If all that failed I would result to subscribing to the new AppDomains AssemblyResolve event and manually load the assemblies.Tons of example for that.
  • Ivandro Jao
    Ivandro Jao over 5 years
    @IgorBendrup you are right assembly = value.GetAssembly(args[0]) will make sure type where the return type assembly exits in both domain
  • Owen Ivory
    Owen Ivory about 5 years
    Some small typos in the code, and I have to admit I didn't believe it would work, but this was a life saver for me. Thanks a ton.