Retargeting Assemblies for SQLite
If you read my previous post about dealing with SQLite and SQL CE, then you know that I am on a mission to get unit tests working correctly with a SQLite database. I decided to see if I could get “assembly retargeting” working. First, what is “retargeting”?
.NET allows assemblies to be “redirected” in a sense when the platform is different. This is how the .NET Compact Framework actually works. All .NET CF assemblies are retargetable. In your application, you are referencing assemblies such as System.dll. However, ever notice that .NET Compact Framework application will run on a desktop? That’s because the runtime is silently retargeting them to the full assemblies, like System. This is key to getting the application working on different platforms. Under all of the covers, .NET relies heavily on platform invoke to do the heavy lifting in the WinForms world. However, the P/Invoke calls between a Windows Mobile application and a workstation are wildly different. This is why redirection is needed to work on multiple assemblies.
How can we get this to work for SQLite?
There are a few things you need to get retargeting to work. You need two assemblies, one for the Compact Framework and one for the full framework. I am using the managed wrapper provided by PHX software, which happens to be open source as well. They have ZIPs of both full and compact assemblies.
The second catch is they need to have the same strong name. Oddly, these two managed wrappers use a different strong name key depending on the platform, but since it’s open source it’s very easy to download, synchronize the keys, and recompile.
The final bit is putting the assemblies in a place the application will look for them without causing problems. In your project, add a reference to the Compact assembly. Leave this for now.
Open a command prompt with administrative permissions and use gacutil to install the Full one.
Finally, you need to make sure the unmanaged libraries are ending up in your project output directory for both compact and full. There are 5 files:
All of these are needed. The best bet is to include them in your project with compile action to “None” and "”Copy if newer” for copying.
All things done right…Tada! It works, but what is really going on? What we are really doing is tricking the .NET Framework into loading the full framework assembly instead of the compact assembly. This has to do with how assemblies are resolved. Without going over a lot of details, assemblies are resolved in this order:
- The GAC
- The probe path of the current application as specified by the AppDomain (like the bin)
That’s the very brief description, and a bit simplified. For the real down-and-dirty, check out this MSDN article. When the runtime looks in the GAC, it finds the assembly we installed. The assembly hash isn’t the same, but since it was retargetable and the strong name keys matched, it loaded it from the GAC anyway and didn’t even bother looking at the one we actually referenced. When it is deployed to a mobile device, it is not in the GAC, so it loads the one we referenced which should have been copied to the output. For the platform invoke, since we copied both the Win32 and the ARM libraries, platform invoke resolved their location from the deployed directory.
Neat stuff, but seemed harder than it could have been.
For what it’s worth, this is also how SQL CE is working. However it does it’s magic by putting the assembly in the GAC for the desktop version and the compact version.