Search code examples
.netreflectiongac.net-assembly

.Net - Does a managed executable obtain a snapshot of GAC at start up?


I somehow ran into the issue when I was working on an application that installs an assembly to GAC on demand (on click of a button), and tries to Assembly.Load it at the click of a next button or tries to invoke a method from that newly GAC-ed assembly, but fails. Problem and repro steps are below.

Problem and Repro Steps: Create a new class library similar to the following named FooGreet.dll and strong name it. Let’s drag it to the assembly folder later.

namespace FooGreet
{
    public class Greeting
    {
        public string SayHello()
        {
            return "Hello";
        }
    }
}

Create a console application to load the above class library, similar to the following code.

namespace FooGreetCaller
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Press Enter to load assembly..");
            while (true)
            {
                string input = Console.ReadLine();
                if (input == "q" || input == "Q")
                    break;
                try
                {
                    System.Reflection.Assembly.Load("FooGreet, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=3f330bcf59df56c9");
                    Console.WriteLine("Assembly load success.");
                }
                catch (Exception eX)
                {
                    Console.WriteLine(eX.Message + Environment.NewLine + eX.StackTrace);
                }
            }
        }
    }
}
  1. Run the console applition:  Run an instance of the console application. Try to load the FooGreet.dll, you will get an exception with “Could not load file or assembly….”, no matter how many times you load. Don’t close the console window.
  2. Install FooGreet.dll to GAC:  Drag FooGreet.dll to GAC, on the same instance of the console application, try to load the FooGreet.dll again, you still get the same exception; which I call an anomaly because the FooGreet.dll is in the GAC, and the console application should have recognized it. Shouldn't it have?
  3. Run a second instance of the console application:  Run a second instance of the console application. Try to load the FooGreet.dll, now the assembly is loaded fine.
  4. Uninstall FooGreet.dll from GAC:  Go to GAC, uninstall FooGreet.dll. Try loading the FooGreet.dll again in the second instance of the console application. The assembly is loaded successfully. Ideally, Shouldn’t it have thrown an exception like “Could not load file or assembly….”?

Question: Questions are at step 2 and 4. And from the happenings at step 2 and step 4, it looks like when an application loads, it takes a snapshot of the GAC (at least with the default AppDomain, I haven't tried creating and loading a new AppDomain). Is this true?

(In my app, as a workaround, I restart the application after I register the particular assembly to GAC with

System.EnterpriseServices.Internal.Publish().GacInstall(targetAssemblyName);

I am on a WinForms app and I am doing the restart in the Form_Load which causes the Main Form to flash for a fraction of a second.. which I dont like)


Solution

  • Assembly Binder actually uses caching. Any assembly which is requested in code, Assembly Binder follows its process to load the assembly. The result of assembly binding is cached by the Assembly Binder. When the same assembly is requested again, Assembly Binder looks in the cache and if the assembly was loaded correctly, reference to the loaded assembly is returned, if the assembly bind failed, exception is returned.

    Another point to note that Binding Context is also used in caching. Assembly.Load, Assembly.LoadFrom and Assembly.Load(byte[]), these 3 methods uses 3 different binding contexts and caching is stored separately for each load type process. The recommended method however is Assembly.Load.

    To verify all of this, you can enable fusion on your machine and see the binding results in Assembly Binding Log Viewer.