Sunday, October 9, 2011

To GAC or not to GAC - Is that the question?


(Note:  GAC is the acronym for the Global Assembly Cache in a Windows environment.)

You can actually have this both ways, with a few caveats.

Using the GAC is a sensible (and preferable) solution for cases where any of the following are true:

  1. The assembly must be COM-accessible.
  2. The assembly is stable.

That’s the good news.  The bad news is that once you start putting things into the GAC, you are bound by some pretty strict rules about what’s ‘in-bounds’.  By using the GAC you limit your flexibility by definition.  This is not necessarily bad, but when you are building assemblies for the GAC, you should keep in mind where you plan to put them.

The following must all be true:

  • The DLL must be signed - that is, it must have a strong  name.
  • The DLL does not have any special binding policies.
    • Essentially, no assembly-specific class or method decorations.
  • The DLL does not have any AppDomain security policies.
    • Essentially, no AppDomain security class or method decorations.
  • The DLL does not call back into the AppDomain.
    • This deserves a little explanation.  Anything that is in the GAC can ‘see’ anything else in the GAC.  Anything outside the GAC can ‘see’ anything inside the GAC.  Anything inside the GAC CANNOT ‘see’ anything outside the GAC.  The AppDomain boundary is a one-way mirror.
    • This means that any DLLs that are called by an assembly in the GAC must also be in the GAC.  
    • This also means that developers should avoid any kind of dynamic DLL loading when writing code for the GAC because that could cause it to break at runtime at unexpected times for obscure reasons.
    • Delegates should be OK, but it is reasonable to expect that using them will cause a performance hit above what is already expected from using a delegate.

For any assemblies in the GAC, static structures and methods have a new abstraction layer between them and the application, which makes them respond more slowly.  Thus, if an assembly is considered for inclusion in the GAC, the developer has to consider the trade-off that will be implicitly made between using less memory and more CPU power when statics are involved.  Old-timers will recognize this as a bow to the old re-entrancy issues with COM modules.

Finally, an assembly loaded into memory from the GAC can only be unloaded when its loading domain is stopped - the operating system.  That means a reboot is required.  If a new version of a DLL is installed in the GAC, a system restart is probably not actually necessary, but even if everything starts using the new DLL, the old one remains loaded if it was already loaded.  It won’t be cleaned up until the next system restart.

That introduces a second issue:  If you promote a version change to the GAC and everything seems to work fine, you can't assume that you're out of the woods just yet.  You won't actually know for certain until after the next system reboot.  That's the trap.  It would be best if you were to just do the reboot at the earliest possible convenience and get it over with, but if you don't then SCHEDULE IT SOON.

What you do not want is a phone call at 3 AM when a system has been rebooted for some other reason and everything breaks, but you're too groggy to remember that you never tested that DLL after a reboot.  You could wind up with a five-alarm all-night tail-chaser that could have been resolved in eight seconds if you'd just done that one extra step when you should have.

Of course, if its a production system, you might not be able to do a reboot right at this instant - you might have to pick an appropriate time.  Just be certain that you don't let it slide for too long and forget about it.

Benefits:
Now that all of the legalese is out of the way, the next question is, “What’s in it for me?”

  1. Memory efficiency.  Code that is run from the GAC generally has only one code segment system-wide.
  2. Responsiveness.  Code loaded from the GAC hits the JIT compiler when it is first loaded, and after that it’s native.
  3. Version control.  Everything is using the same code - literally.
  4. Disk efficiency.   Anything loaded from the GAC doesn’t have to be in any /bin folder.

General Considerations:
You should establish (ahead of time) a formal versioning protocol.  There can actually be multiple DLLs in the GAC that can in principle be differentiated by version.    I’ve never directly done that, but that’s what the documentation states.  It would be worth a few experiments to confirm prior to relying on it.

  • The application of GAC versioning changed between .NET version 1.0 and version 1.1.  Any DLLs older than version 1.1 will have to be updated.  Typically, this is just a recompile.
  • If you take care of the versioning protocol up front, you can avoid most or all of the issues that might eventually show up.  That basically means standardizing all of the interfaces and ensuring ongoing backward compatibility of the interfaces across all versions.  This is more of a management issue than a coding issue.  Essentially, changes to existing interface implementations will have to be subject to review.
    • The danger here can be mitigated to a certain degree by maintaining multiple versions in the GAC and propagating changes through policies.  That allows specific application-level fall-backs to be put in place that can be employed as a short- to mid-term ‘safety net’ when promoting changes to DLLs in the GAC.
  • The same review group would have to be tasked with reviewing and approving the addition or removal of any interfaces from any DLLs that are to be placed in the GAC.  It would then be the responsibility of that group to ensure that no dependencies got broken by such changes.
    • The primary concern here is avoid letting trivia get propagated up the code chain.  If a new function or interface is required on a one-shot basis, it should be treated as a one-shot until it proves to be something more generally useful.

If you have an application on a system that uses a DLL slightly different from the GAC version, the version in the GAC can be overridden by placing a version with the same name-space in the local application and providing a redirect through the web.config or app.config, providing that the fully qualified name is different (that is, a different version and/or key) and specified precisely. That is the Official Microsoft Line.  There are easier ways to get the same thing done:

  • Employ descendant classes in a different name-space.
  • For classes that are sealed, use wrapper classes.

The success of the approach comes down to effective management and self-discipline.  Management of the name-space, versioning, and interfaces to ensure consistency, and self-discipline to avoid getting sloppy in the GAC.  For your core code, it is probably a good idea to the extent that it can be applied.  In any case you should review any code contemplated for movement into the GAC with an eye toward finding any potential issues that might complicate such a move.

COM Issues:
If you're using the GAC to resolve COM accessibility issues, you have a few extra things to worry about.  A C# assembly placed in the GAC that has to be COM-accessible has certain peculiar constraints regarding its construction.  The short list:

  • Every class must have an empty constructor.
  • No method can be overloaded.
  • Statics are FORBIDDEN.

Copyright ©2013 by David Wright. All rights reserved.