none
Trouble Loading Assemblies (.dll)

    Question

  • Hi Forum,

    I have two .dll files that need to be used which are both outside of the application directory.  Using ResolveEventHandler, I can load one of the .dll files at runtime.  However, the second .dll file does not load.  I tried using two event handles methods and the code only seems to take the first one and forget about the second one.  What is the solution for this?

    Exmaple:

    DLL 1 - C:\apps\referenceFiles\dll1.dll

    DLL 2 - C:\apps\appReferenceFiles\dll2.dll

            private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
            {
                //This handler is called only when the common language runtime tries to bind to the assembly and fails.
    
                //Retrieve the list of referenced assemblies in an array of AssemblyName.
                Assembly MyAssembly, objExecutingAssemblies;
                string strTempAssmbPath = "";
    
                objExecutingAssemblies = Assembly.GetExecutingAssembly();
                AssemblyName[] arrReferencedAssmbNames = objExecutingAssemblies.GetReferencedAssemblies();
    
                //Loop through the array of referenced assembly names.
                foreach (AssemblyName strAssmbName in arrReferencedAssmbNames)
                {
                    //Check for the assembly names that have raised the "AssemblyResolve" event.
                    if (strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
                    {
                        //Build the path of the assembly from where it has to be loaded.
                        strTempAssmbPath = "C:\\export\\apps\\referenceFiles\\" + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";
                        break;
                    }
    
                }
                //Load the assembly from the specified path. 
                MyAssembly = Assembly.LoadFrom(strTempAssmbPath);
    
                //Return the loaded assembly.
                return MyAssembly;
            }
    
            private static Assembly MyResolveEventHandler2(object sender, ResolveEventArgs args)
            {
                //This handler is called only when the common language runtime tries to bind to the assembly and fails.
    
                //Retrieve the list of referenced assemblies in an array of AssemblyName.
                Assembly MyAssembly, objExecutingAssemblies;
                string strTempAssmbPath = "";
    
                objExecutingAssemblies = Assembly.GetExecutingAssembly();
                AssemblyName[] arrReferencedAssmbNames = objExecutingAssemblies.GetReferencedAssemblies();
    
                //Loop through the array of referenced assembly names.
                foreach (AssemblyName strAssmbName in arrReferencedAssmbNames)
                {
                    //Check for the assembly names that have raised the "AssemblyResolve" event.
                    if (strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
                    {
                        //Build the path of the assembly from where it has to be loaded.
                        strTempAssmbPath = "C:\\export\\apps\\appReferenceFiles\\" + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";
                        break;
                    }
    
                }
                //Load the assembly from the specified path. 
                MyAssembly = Assembly.LoadFrom(strTempAssmbPath);
    
                //Return the loaded assembly.
                return MyAssembly;
            }
    
    //---------------------
    //Later in the code just prior to when the methods are called
    //---------------------
    
                AppDomain currentDomain = AppDomain.CurrentDomain;
                currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);
                currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler2);


    Auburn University Student IT/MIS Intern War Eagle!


    Monday, May 1, 2017 1:59 PM

All replies

  • Do not hook up multiple AssemblyResolve events. One is sufficient and gets called whenever any assembly cannot be resolved. I'm very confused by the behavior of your handler. The assembly that could not be found is passed as an argument. Enumerating the list of referenced assemblies from your binary isn't necessary or even recommended. Specifically it would completely ignore dependencies of assemblies you are referencing. You should simply try to load the assembly that it could not find. Since you want to search multiple locations you'd want to enumerate through the preferred search order that you want to check. For each one check to see if the requested assembly exists using File.Exists. If it exists then try to load it. If it doesn't exist then move to the next path. If you it cannot be found at all then the load process fails.

    Note also that you're using the LoadFrom to load the assembly. LoadFrom doesn't use the same assembly context as regular assemblies. You might consider using Load with the overload that accepts the assembly binary instead. This will load in the standard context. I'm not sure if this is necessarily causing your problem or not but traditionally LoadFrom causes issues.

    Michael Taylor
    http://www.michaeltaylorp3.net

    Monday, May 1, 2017 2:39 PM
    Moderator
  • So, something like this?

            private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
            {
                //This handler is called only when the common language runtime tries to bind to the assembly and fails.
    
                //Retrieve the list of referenced assemblies in an array of AssemblyName.
                Assembly MyAssembly, objExecutingAssemblies;
                string strTempAssmbPath = "";
    
                objExecutingAssemblies = Assembly.GetExecutingAssembly();
                AssemblyName[] arrReferencedAssmbNames = objExecutingAssemblies.GetReferencedAssemblies();
    
                //Loop through the array of referenced assembly names.
                foreach (AssemblyName strAssmbName in arrReferencedAssmbNames)
                {
                    //Check for the assembly names that have raised the "AssemblyResolve" event.
                    if (strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
                    {
                        //Build the path of the assembly from where it has to be loaded.
                        strTempAssmbPath = "C:\\export\\apps\\referenceFiles\\" + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";
                        break;
                    }
    
                    if (strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
                    {
                        //Build the path of the assembly from where it has to be loaded.
                        strTempAssmbPath = "C:\\export\\apps\\appReferenceFiles\\" + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";
                        break;
                    }
    
                }
                //Load the assembly from the specified path. 
                MyAssembly = Assembly.LoadFrom(strTempAssmbPath);
    
                //Return the loaded assembly.
                return MyAssembly;

    Or, like this?

            Assembly dll1 = Assembly.Load("C:\\export\\apps\\referenceFiles\\dll1.dll");
            Assembly dll2 = Assembly.Load("C:\\export\\apps\\appReferenceFiles\\dll2.dll");


    Auburn University Student IT/MIS Intern War Eagle!

    Monday, May 1, 2017 2:59 PM
  • I'd say something like this (not compiled):

    static Assembly OnResolveAssembly ( object sender, ResolveEventArgs e )
    {
        //Get the assembly that could not be found
        var name = $"{new AssemblyName(e.Name).Name}.dll";
    
        //The search paths
        var searchPaths = new[]
        {
            @"C:\Temp\Assemblies1",
            @"C:\Temp\Assemblies2"
        };
    
        //Start searching            
        var domain = AppDomain.CurrentDomain;
        foreach (var searchPath in searchPaths)
        {
            var fullPath = Path.Combine(searchPath, name);
            if (File.Exists(fullPath))
            {
                //Try and load it
                try
                {
                    //Don't call Load here otherwise you'll get a stack overflow
                    //Can use LoadFile or LoadFrom because the assembly will still be loaded into the calling context anyway
                    return Assembly.LoadFile(fullPath);
                } catch (FileLoadException)
                {
                    //Assembly was found but failed to load, this is probably fatal...
                    throw;
                } catch (FileNotFoundException)
                {
                    //Keep looking
                } catch (BadImageFormatException)
                {
                    //Keep looking
                };
            };
        };
    
        return null;
    }

    The name is going to be the assembly name so we convert it to the filename equivalent. Then we define the search paths (would probably come from a config file or something). Then we enumerate the search paths. For each path we generate the absolute path that we will be looking for. If the file exists at that path then we try to load it using Assembly.LoadFile. Refer to the MSDN article on this. Specifically you cannot use any Load methods that require the name because you'll get into a recursive loop. Additionally the resolve event is called such that the returned assembly is actually loaded into the context of the original call so you can use either LoadFile or LoadFrom and it'll still be loaded into the correct context.

    LoadFile can throw a couple of exceptions. Most mean it isn't what you're looking for so we ignore the exception and keep looking. However FileLoadException means the assembly was found but could not be loaded. This probably indicates an error so we're explicitly throwing the exception here. You can change that if needed.

    If the assembly isn't found then return null so it fails as normal. Note that all this code is only necessary if you are trying to store assemblies outside the app's path and not in the GAC. Assemblies in the GAC will be found correctly. Assemblies in a child path of the app path can be found using probing. These are the preferred approaches when possible.

    Monday, May 1, 2017 3:47 PM
    Moderator
  • That did not work for me, but I did look into just installing the missing dlls in the GAC.  So far, that has worked but I am still testing.  What are your thoughts on adding the missing dll into the GAC? 

    Auburn University Student IT/MIS Intern War Eagle!

    Monday, May 1, 2017 4:52 PM
  • "That did not work for me,"

    Why not?

    "What are your thoughts on adding the missing dll into the GAC? "

    Shared assemblies should be stored in the GAC so they can be accessed by different applications. However there are some things to be aware of before going this route.

     - All GAC'ed assemblies have to be strongly named. Any dependencies a strongly named assembly has also has to be strongly named.

     - GAC'ed assemblies are versioned so if an app builds against 1.0 of your assembly but only 2.0 is installed in the GAC then it fails. You have to start adding binding redirects to work around it.

     - Installing to the GAC requires admin privileges. It is a shared registration so all apps will have access to it.

    My personal recommendation is to only GAC assemblies that are shared by multiple applications and represents truly shared code that I want to version anyway.

    Monday, May 1, 2017 5:16 PM
    Moderator
  • Well, when I implemented that in the code, still only the first missing DLL file is caught.  The same thing was happening to me when I was using the other ResolveEventHandler.  So when I called the method to fix the missing DLL at runtime in the code, still only the first dll was found but not the second one.  It seems like after the ResolveEventHandler is called the first time, then the second time is ignored somehow.

    /*In this scenario, the second event handler is missed or ignored.  If I comment out the first one, then the first dll file is not found.  If I comment out the second one, then the second dll is not found, which is the current issue I am facing.*/
    
    AppDomain currentDomain = AppDomain.CurrentDomain;
    currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);
    currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler2);


    Auburn University Student IT/MIS Intern War Eagle!

    Monday, May 1, 2017 7:32 PM
  • "It seems like after the ResolveEventHandler is called the first time, then the second time is ignored somehow."

    That's not how it works. Are you using the exact code I gave you or the code you originally posted? Your originally posted code won't work because, again, you're trying to match the assembly being resolved to what your current assembly is referencing and that is likely to be wrong. Try setting a breakpoint at the beginning of the resolve and see if it calls it again. You should also turn on the fusion log because it is also likely that the second assembly you are expecting to get called for isn't being loaded yet.

    If this is still a problem then please provide a detailed explanation of the relationship between the assemblies that you're trying to load via the resolve handler and what your app is doing to trigger the loading of these assemblies.

    Monday, May 1, 2017 7:43 PM
    Moderator