What’s in a Strong Name
Sunday December 04 2011 - ascom, software, explanation, best-practice, astronomy
Developers of .NET assemblies soon come to the decision of whether or not to strong-name their assemblies. There are various pros and cons but I focus here on the requirements for ASCOM components authored in .NET, these components must publish an ASCOM interface via COM Interop.
For purposes of COM Interop, the location of a .NET assembly and whether or not it is strong-named affects the visibility of the COM interfaces. The reason for this is bound up with the workings of mscoree.dll (the .NET COM loader) and fusion.dll (the .NET component responsible for locating and binding to assemblies at runtime). When COM activates a .NET assembly, the process goes something like this:
- COM attempts to activate the COM interface (typically from a ProgID).
- The ProgID is resolved to a COM Class ID. The CLSID\{guid} entry in the registry is where the COM Interop details are kept.
- The default value in the CLSID\{guid} key contains the filename of the loader. For .NET, this is ‘mscoree.dll’, for Python it might be something like ‘python27loader.dll’.
- COM loads mscoree.dll and passes in some configuration data.
- mscoree.dll calls fusion.dll to locate and load the .NET assembly.
- mscoree.dll uses reflection to construct a ‘runtime callable wrapper’ that marshal calls and data between the worlds of COM and .NET
- The COM application makes IDispatch calls to the runtime callable wrapper, which marshals any data over to the .NET assembly and marshals the results back.
- When the COM reference is released by the COM application, mscoree.dll deletes the runtime callable wrappers and the .NET objects are garbage collected.
This UML sequence diagram illustrates the approximate process:
The important thing here is what happens when mscoree.dll calls fusion.dll to locate and load the assembly. Fusion.dll looks for an assembly as follows:
- In the Global Assembly Cache
- In the path specified (if any)
- In the directory of the calling application, and any subdirectories
So we have four distinctly different situations.
- Assembly is installed in GAC (and therefore must be strong-named). Fusion can always find assemblies in the GAC so the COM interface is globally available to all COM applications. The fact that fusion.dll looks in the GAC first has important implications; assemblies in the GAC always take priority over other copies.
- Assembly is in a specified folder. For this to work, the assembly must have been registered for COM Interop using the /codebase option to RegAsm. This creates a CodeBase registry value that holds the fully qualified path to the .NET assembly. At runtime, mscoree.dll passes the CodeBase value to fusion.dll as a hint to tell it where to locate the .NET assembly. Therefore, fusion.dll can always locate an assembly that has been registered with a CodeBase value.
- Assembly is in the same folder as the COM application (or a subdirectory thereof).
- Assembly is not in the GAC, is not in the Application directory and was not registered with the /codebase option. Fusion will not be able to locate this assembly at runtime since no-one has told it where to look.
In the case of ASCOM, the convention has been to put the driver executable files within the %CommonProgramFiles% folder, which is normally C:\Program Files\Common Files\ASCOM. So considering the ‘four situations’ above, clearly item 1 does not apply because the driver will not be in the GAC1. Item 3 also does not apply, because we don’t know in advance which COM application will use our driver. Situation 2 then is the appropriate choice for an ASCOM driver. We want only one copy to be present on the system and we want all COM applications to be able to find it.
Note that this requires use of the /codebase option in RegAsm, to instruct it to create the CodeBase registry value. This requires the assembly to be strong named. Thus, ASCOM drivers must be strong named.
Situation 4 results in the dreaded “Failed to load component” error from COM. Getting the COM registration right is arguably one of the most difficult challenges for ASCOM developers who use .NET languages. Here is a typical registry layout for a .NET based in-process ASCOM driver:
[HKEY_CLASSES_ROOT\CLSID\{12229C31-E7D6-49E8-9C5D-5D7FF05C3BFE}]
@="ASCOM.Simulator.Camera"[HKEY_CLASSES_ROOT\CLSID\{12229C31-E7D6-49E8-9C5D-5D7FF05C3BFE}\Implemented Categories]
[HKEY_CLASSES_ROOT\CLSID\{12229C31-E7D6-49E8-9C5D-5D7FF05C3BFE}\Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}]
[HKEY_CLASSES_ROOT\CLSID\{12229C31-E7D6-49E8-9C5D-5D7FF05C3BFE}\InprocServer32]
@="mscoree.dll"
"ThreadingModel"="Both"
"Class"="ASCOM.Simulator.Camera"
"Assembly"="ASCOM.Simulator.Camera, Version=6.0.0.0, Culture=neutral, PublicKeyToken=565de7938946fba7"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="file:///C:/Program Files (x86)/Common Files/ASCOM/Camera/ASCOM.Simulator.Camera/ASCOM.Simulator.Camera.DLL"[HKEY_CLASSES_ROOT\CLSID\{12229C31-E7D6-49E8-9C5D-5D7FF05C3BFE}\InprocServer32\6.0.0.0]
"Class"="ASCOM.Simulator.Camera"
"Assembly"="ASCOM.Simulator.Camera, Version=6.0.0.0, Culture=neutral, PublicKeyToken=565de7938946fba7"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="file:///C:/Program Files (x86)/Common Files/ASCOM/Camera/ASCOM.Simulator.Camera/ASCOM.Simulator.Camera.DLL"[HKEY_CLASSES_ROOT\CLSID\{12229C31-E7D6-49E8-9C5D-5D7FF05C3BFE}\ProgId]
@="ASCOM.Simulator.Camera"
On a 64-bit system, the above registry information must appear in both the 32-bit registry area and the 64-bit registry area.
1 This is by convention only. There is in fact no reason why an ASCOM driver shouldn’t go in the Global Assembly Cache, although installing an assembly in the GAC still requires a strong name.