How To Avoid Strong-Name Signing Your ASCOM Drivers

Tuesday April 08 2014 - , ,

Back in 2011, I wrote a blog post titled “What’s in a Strong Name”, in which I made the assertion that ASCOM drivers MUST be strong-name signed. This is the case for a COM component (which ASCOM drivers are) that you don’t want to put in the Global Assembly Cache, but must nevertheless be shared between multiple applications. The reason for the strong name is that RegAsm.exe (the utility that performs COM Interop registration) _requires_ a strong name in order to set the CodeBase value, which helps the CLR find the assembly when it is invoked from COM. The mechanics are somewhat intricate and my original post goes into some of the details.

However, it seems that, as long as one doesn’t rely on RegAsm (or the programmatic equivalent in the RegistrationServices class), i.e. if the COM registry entries are installed by another means, such as using a WiX installer, then strong-name signing is not actually a requirement. WiX is quite happy to install the registry keys without checking for a strong name, and the CLR is quite happy to load and use the unsigned assembly.

So, at the simplest level, strong-naming is not required as long as you insert your own registry entries within your setup process. Since this is actually what Microsoft recommends (they strongly discourage any form of ‘self-registration’) then this is a happy outcome. According to this MSDN article

Installation package authors are strongly advised against using self registration. Instead they should register modules by authoring one or more tables provided by the installer for this purpose. For more information, see Registry Tables Group. Many of the benefits of having a central installer service are lost with self registration because self-registration routines tend to hide critical configuration information.

The article then launches into a long list of reasons why self-registration is a bad idea, but perhaps the most important is that, in the event that something goes wrong during an install or uninstall operation, roll-back is nearly impossible and the system can be left in an inconsistent state. Installations should be transactional, that is, they should either succeed completely, or leave the system untouched, as if they had never been run. Programmatic installs that use self-registration make it very difficult to achieve that.

What’s the Benefit?

The main benefit from this is that strong-naming introduces some complexities for development and debugging and you quickly discover that once one assembly is strong-named, then anything that it uses has to be strong-named too. The knock-on effect can get quite irritating, for example you might find that a third party library that you want to use is ruled out because there is no signed version available. Strong naming tends to make everything just that little bit more complicated than it needs to be and if you can make life easier by avoiding it, then that is a win.

There’s a rub though (isn’t there always?). The CLR will not be able to find any helper assemblies that your driver uses. The assembly probing process goes something like this:

mscoree.dll calls fusion.dll to locate and load the assembly. Fusion.dll looks for an assembly as follows:

  1. In the Global Assembly Cache
  2. In the path specified (if any)
  3. In the directory of the calling application, and any subdirectories

In the case where the driver assembly is strong-named, there is some ‘magic sauce’ that makes everything somehow just work. When the assembly isn’t strong named, the magic sauce isn’t there and your driver can no longer find its helper assemblies at runtime. Apps will start to throw ‘FileNotFoundException’ as Fusion.dll looks in the application’s directory instead of the driver’s directory.

There’s a solution though! Classes can hook into the AppDomain.AssemblyResolve event and receive a notification whenever the CLR is trying to locate an assembly. This gives us an opportunity to find the assembly ourselves, load it and return the loaded assembly to the CLR. The cardinal rule is that you shouldn’t resolve any assemblies that you don’t know about, so basically you should only resolve assemblies that are part of the driver. Assuming that the support assemblies are in the same folder as the driver, then the event handler should be something like this:

   1: Assembly ResolveSupportAssemblies(object sender, ResolveEventArgs args)
   2:     {
   3:     if (!args.Name.StartsWith("TA."))
   4:         {
   5:         return null; // Don't resolve anything we didn't write.
   6:         }
   7:     var me = Assembly.GetExecutingAssembly();
   8:     var here = me.Location;
   9:     var myDirectory = Path.GetDirectoryName(here);
  10:     var commaPosition = args.Name.IndexOf(',');
  11:     var targetName = args.Name.Head(commaPosition);
  12:     var targetDll = targetName + ".dll";
  13:     var target = Path.Combine(myDirectory, targetDll);
  14:     try
  15:         {
  16:         var resolved = Assembly.LoadFrom(target);
  17:         return resolved;
  18:         }
  19:     catch (Exception ex)
  20:         {
  21:         return null; // Let the app raise its own error.
  22:         }
  23:     }

Easy! Wire up the event in your driver’s constructor, using a line like this:

   1: AppDomain.CurrentDomain.AssemblyResolve += ResolveSupportAssemblies;

Job done. Your driver (or other COM component) will now be able to find its helper assemblies, even without strong-naming.