Po man’s patchworkin’

If you had come of age before Component Object Model as I have, the concept of COM registration seemed rather odd at first glance. Why is your system limited to exactly one application extension which is forced to be backwards-compatible, when, for years, other operating systems have enjoyed the ability to have many versions of the same shared library and an established probing precedence to find the appropriate library file? This has landed us Windows users in deep doo-doo when we try to upgrade software that uses shared components (so-called DLL Hell).

Thankfully, the advent of Microsoft .NET had done a lot to solve this problem. Not only can the same dll exist in multiple locations, and a predictable probing behaviour has been established, but we can even override the assembly binding by creating an assembly config file. This feature can be leveraged for an easy way to ‘patch’ errant software, provided that the new assembly has the same public methods and objects available as the old one.

I had recently been in a situation where I could put this into practice. Our schema comparison keeps all of its’ logic in a shared assembly: RedGate.SQLCompare.Engine.dll. This shared component had been updated to support a certain SQL Server 2005 feature that I won’t name, suffice to say that it wouldn’t require a change to the User Interface to support it. Because the UI and the Engine were of the same major version, it seemed a perfect time to exercise my assembly binding prowess. Here is the path to dll heaven when you need to upgrade a single dll in a piece of software:

Step 1: Identify the assembly name of the dll that you want to replace. The assembly name is normally the same as the file name without the extension, but this is not a given. Tools such as .NET Reflector will tell you the assembly name, and if you’re really hard-core, you can code up something using Reflection: Assembly.LoadFile(@”c:file.dll”).FullName. When you configure the assemblyIdentity (below) you will need to know the assembly name.

Step 2: Copy the dll, and any dependent dlls, into a subfolder in the calling application’s APPATH. This is very important because .NET will not allow you to bind to an assembly originating from a folder outside of the application’s “home” folder, presumably for security reasons. I have created a folder called SQL Compare Engine 63 underneath the program’s folder for this reason.

Step 3: Open Notepad and create a file named after the executable that is going to load your dll, appending “.config” to the end of the filename. In my case, the file will be called RedGate.SQLCompare.UI.exe.config. The contents of this file will be in XML, the bindingRedirect element is the magic that allows me to use the new dll in place of the old one.

<?

xml version=”1.0″ encoding=”utf-8″?>
<configuration>
   <runtime>
      <assemblyBinding xmlns=”urn:schemas-microsoft-com:asm.v1″>
         <dependentAssembly>
            <assemblyIdentity name=”RedGate.SQLCompare.Engine” publicKeyToken=”7f465a1c156d4d57″ culture=”neutral” />
            <bindingRedirect oldVersion=”6.2.1.36″ newVersion=”6.3.0.123″ />
            <codeBase version=”6.3.0.123″ href=”file://c:/Program files/red gate/SQL Compare 6/sql compare engine 63/RedGate.SQLCompare.Engine.dll” />
         </dependentAssembly>
      </runtime>
   <startup>
      <supportedRuntime version=”v2.0.50727″/>
   </startup>
</configuration>

You may notice a few things about the contents of this file that I haven’t mentioned. First of all, in addition to the elements that identify the assembly that I want to configure, I have added the codeBase directive. This will instruct .NET to look in the sql compare engine 63 folder that I had created to hold the new version of the dll because I feel a lot safer not overwriting the shipped SQL Compare dll. I have also added the supportedRuntime tag. This may be important if the application that you were patching was built against the .NET 1.x runtime and the replacement dll was built against 2.0. If the whole shebang loads into the 2.0 runtime, then there shouldn’t really be any incompatibilities (with the requisite exceptions, of course!)

An additional complication is introduced if the replacement dll relies on updated dependant dlls. Just because we have redirected the RedGate.SQLCompare.Engine.dll doesn’t mean that the runtime will probe the SQL Compare Engine 63 for dependencies. One solution could be to add a probing path inside the assemblyBinding element:

<

probing privatePath=”c:program filesred gatesql compare 6sql compare Engine 63″/>

However, this doesn’t seem to work for me and my guess is that it’s because codeBase always overrides probing. To solve this problem, I got a list of all dependent assemblies from .NET Reflector for RedGate.SQLCompare.Engine and added the file locations for all dependencies using codeBase to make the complete config file:

<?xml version=”1.0″ encoding=”utf-8″?>
<configuration>
   <runtime>
      <assemblyBinding xmlns=”urn:schemas-microsoft-com:asm.v1″>
         <dependentAssembly>
            <assemblyIdentity name=”RedGate.SQLCompare.Engine” publicKeyToken=”7f465a1c156d4d57″ culture=”neutral” />
            <bindingRedirect oldVersion=”6.2.1.36″ newVersion=”6.3.0.123″ />
            <codeBase version=”6.3.0.123″ href=”file://c:/Program files/red gate/SQL Compare 6/sql compare engine 63/RedGate.SQLCompare.Engine.dll” />
         </dependentAssembly>
         <dependentAssembly>
            <assemblyIdentity name=”RedGate.SQL.Shared” publicKeyToken=”7f465a1c156d4d57″ culture=”neutral” />
            <codeBase version=”6.2.1.30″ href=”file://c:/Program files/red gate/SQL Compare 6/sql compare engine 63/RedGate.SQL.Shared.dll” />
         </dependentAssembly>
         <dependentAssembly>
            <assemblyIdentity name=”RedGate.SQLCompare.ASTParser” publicKeyToken=”7f465a1c156d4d57″ culture=”neutral” />
            <codeBase version=”6.3.0.123″ href=”file://c:/Program files/red gate/SQL Compare 6/sql compare engine 63/RedGate.SQLCompare.ASTParser.dll” />
         </dependentAssembly>
         <dependentAssembly>
            <assemblyIdentity name=”RedGate.SQLCompare.Rewriter” publicKeyToken=”7f465a1c156d4d57″ culture=”neutral” />
            <codeBase version=”6.3.0.123″ href=”file://c:/Program files/red gate/SQL Compare 6/sql compare engine 63/RedGate.SQLCompare.Rewriter.dll” />
         </dependentAssembly>
      </runtime>
   <startup>
      <supportedRuntime version=”v2.0.50727″/>
   </startup>
</configuration>

This seems to work a treat! The only drawback is that your new dll may be incompatible with the application because public methods may have been deprecated or some other undesirable behaviour.