Using assemblyresolve to handle missing assemblies in C#
Asked Answered
C

1

0

I am following Method 3 in http://support.microsoft.com/kb/837908 to load assemblies dynamically in C#. However, the code is not working for me. In the following section of the code, the author loads the missing assembly only if the name of missing assembly is one of the assemblies referenced by the application.

When I run this under debug, the function is getting called, but the missing assembly is not in any of those referenced assemblies, and so it is not set in my case. Any ideas why is this occurring? I am not sure if that DLL is C# or native C++. Could this be because C++ dlls cannot be loaded this way? Then, why is this function getting invoked for a missing C++ assembly? Any explanations appreciated. If this doesn't work for C++ assemblies referenced from C#, what are alternatives?

private 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:\\Myassemblies\\"+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;          
}
Corrigan answered 28/10, 2012 at 21:19 Comment(2)
I have used it to load assemblies (dll's) that I know exists in a different folder. You can either return a reference to an already loaded assembly or you can load a new dll and return the loaded assembly. I wonder why it needs to Resolve assemblies that is already loaded, but it does.Extricate
But I am confused why the missing assembly is NOT part of referenced assemblies in my case. Because this function gets called when the CLR loads a referenced assembly and fails. So why is that missing assembly not in referenced assemblies of executing assembly?Corrigan
I
1

The term "assembly" does not mean just any DLL; it means a DLL created with and for .NET. It is also often called "managed code" which roughly means that you use the .NET garbage collector rather than a conventional C++ heap for managing memory. (I am simplifying. There are also "mixed mode assemblies" which do get resolved this way although they use a mix of managed and unmanaged code. And "managed" means quite a bit more than just memory management.)

It does not matter whether your referenced assembly is written in C++/CLI, or in C#. C++/CLI is often confused with C++, but it is actually another language with extra facilities to take advantage of the managed environment. If you compile something C++'ish with the /clr switch, it's C++/CLI rather than C++.

There are three gotchas with the referenced KB article that you need to understand.

  1. "Method 3" in the article does not take indirectly referenced assemblies into account. That is, assemblies referenced from assemblies referenced from your main assembly. This is probably biting you right now.

  2. The article fails to mention that it is critical that you do as little as possible in the body of the method that registers your handler, or the jitter will resolve some assemblies before your handler is registered - even if the code lines responsible for that are below the registration in the same method. This is because JIT compilation precedes execution.

  3. If you deal with C++/CLI code, odds are that you also need to load some unmanaged C++ DLLs that the former link against. It is done using the traditional Windows DLL search order and outside of your resolver. But this is not your problem as long as you see the handler entered for a particular DLL. A normal kind of C or C++ DLL that has nothing to do with .NET will not be seen in the handler at all.

Ironist answered 28/10, 2012 at 21:46 Comment(7)
Thanks for explaining. Very helpful. Question - How do I manually register some assemblies with my application? I can do this beginning of application and avoid these events being called. 2) Your point 3 is right. The C++/CLR Dlls invoke native unmanaged Dlls. But those are registered using regsvr32 utlity. I guess if they are not registered, I would have to use DLLImport to register them.Corrigan
@Corrigan - 1) Assembly.LoadFrom, enumerate GetReferencedAssemblies, load those too, and so on. 2) regsvr32 does not "register" the DLL file, it just loads the DLL once and gives it a chance to register itself whatever the DLL itself chooses that to mean (COM components, mostly). If your DLL is a COM server, fine. But then you aren't really referencing or loading it as a DLL from managed code. You are referencing that same COM component; let COM plumbing connect to it; and it is quite possible that the DLL will be loaded by ComSysApp service into a process different from yours.Ironist
Yes the problem is I can load the assemblies but it requires registering, otherwise the runtime will invoke the event.Corrigan
Also, does this approach do any version checking against the loaded assembly?Corrigan
@Corrigan - Inside AssemblyResolve event, you can loop through ResolveEventArgs.RequestingAssembly.GetReferencedAssemblies to get at version information. However, this does not work for COM references or when using LoadFrom.Ironist
Thanks a lot. I've another question. Suppose that the assembly that I load at runtime references other C++/CLR assemblies and other C++ unmanaged DLLs. Where can I place them? If I place them in the same shared directory that I keep my loaded assembly, will they be picked up from there automatically? At the moment, I put everything in application directory and it works fine but I am looking to separate the shared assemblies and dlls to a shared directory with minimal risk and effort.Corrigan
@Corrigan - If you keep them by the main executable, they will be found just fine. Or you can use the PATH variable or you can call SetDllDirectory to extend the search to other directories. This is useful if you have complicated chains of DLLs and cannot afford to keep everything in one place as most people do.Ironist

© 2022 - 2024 — McMap. All rights reserved.