Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DLL hell with SQLite

Tags:

c#

.net

sqlite

dll

Some of our users are getting an issue with the version of sqlite.interop.dll that is being loaded at runtime, and it's a real head scratcher.

Background: A WPF application built for AnyCPU, deployed with SQlite .NET and sqlite.interop.dll version 1.0.89. We deploy both x86 and x64 dlls and employ the delay loading included with SQLite. This has been fine until recently, when we started getting a few support issues from users that had - typically - recently purchased new Dell machines. It seems that there is an older version of sqlite.interop.dll (v.1.0.80) that, somehow, is getting loaded in preference to the one we ship. The error we get is a missing entry point, 'sqlite3_changes_interop'.

What we have tried:

  1. Changing the setup to simply copy the appropriate dll (x86/64) to the same directory as the executable during installtion (i.e. no separate x86/x64 folders). This means we no longer use the delay loading, as the correct dll is available in the executable directory (although we haven't explicitly disabled the delay loading mechanism in sqlite.net). This doesn't fix the problem..

  2. Explicitly loading sqlite.interop.dll when the application first loads. Again, this does not seem to fix the issue.

It seems that the ordering of dll loading locations has been changing somewhat in recent years, and I may not have a good handle on it. I always assumed that a dll in the executable directory would get first preference, and that a dll that had been explicitly loaded would prevent the same dll being reloaded during the application lifetime, so for the life of my I cannot understand what is going on here.

Can anyone shed any light on what might be happening here? The problem is further compounded by the fact that I simply cannot reproduce the problem locally - e.g. by putting the wrong version of the dll in my system path etc. Which makes me think that maybe the GAC might be coming into play?

Really stuck on this one, so any help would be great.

Also - as a final resort - I might consider reverting to the same 1.0.80 version, so that we don't get this issue. Does anyone know where we could source older versions of sqlite.net and sqlite.interop.dll?

Edit - some additional information:

The clash is caused by a copy of sqlite.interop.dll version 1.0.80 that is installed with Dell Backup and Recovery. This is installed on all new Dell machines, and users that install our software on such a machine all experience this issue. This Dell software also uses System.Data.SQLite.dll.

The correct version of sqlite.interop.dll is located in the same directory as our executable, and everything I understand about dll loading suggests that this should be loaded in preference.

Although we have not yet been able to reproduce the issue locally, it appears that the bad version of interop.dll is not on the path. Furthermore, the Dell backup utility runs automatically on start-up. Does anyone know of any possible mechanism by which this might be hooking into dll load requests and serving the wrong file?

The current line of thinking is that we might build our own System.Data.SQLite.dll and change the interop loading code to a specifically named version (e.g. sqlite.interop.1.0.89.dll). Not a nice solution going forwards, but..

like image 886
Matt Avatar asked Jun 14 '15 19:06

Matt


3 Answers

Our app has the same problem. As you mentioned, the problem is that Dell Backup and Recovery installs a shell extension that uses old versions of several popular dlls. They play hell with any app that launches file dialogs and also uses those libraries, because shell extensions load their dlls into your AppDomain. The only solution we have so far is to tell the users to uninstall Dell Backup and Recovery.

If you force your app to load the correct library as dymanoid mentioned, then your app will crash when it displays a file dialog (because the shell extension will crash). If you don't do that, then your app will crash when it tries to read from its database.

Interestingly, Dell Backup and Recovery is a repeat offender; it also breaks QT5 in the same way. The recommended solution from the QT guys is to compile your QT library under a different name with the -qtnamespace [name] option. We might be able to rig something like that with system.data.sqlite, but then we'd have to compile our own version.

Microsoft is aware of the problem, but has declined to fix it.

I wish the Dell guys had implemented their shell extension like this.

Portroit Pro, SONAR, and AutoDesk's solution to this problem is also to uninstall Dell Backup and Recovery.

A typical stack trace of the problem looks this in our application:

System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt. 
at System.Data.SQLite.UnsafeNativeMethods.sqlite3_open_interop(Byte[] utf8Filename, Int32 flags, IntPtr& db) 
at System.Data.SQLite.SQLite3.Open(String strFilename, SQLiteConnectionFlags connectionFlags, SQLiteOpenFlagsEnum openFlags, Int32 maxPoolSize, Boolean usePool) 
at System.Data.SQLite.SQLiteConnection.Open() 
at STCommonShellIntegration.DataShellManagement.CreateNewConnection(SQLiteConnection& newConnection) 
at STCommonShellIntegration.DataShellManagement.InitConfiguration(Dictionary`2 targetSettings) 
at DBROverlayIcon.DBRBackupOverlayIcon.initComponent()

So in answer to Track's comment, if you want to detect this particular problem and give the users some special notice, you could do something like this:

AppDomain.CurrentDomain.UnhandledException += UEHandler;
//...
[HandleProcessCorruptedStateExceptions] //access violation
static void UEHandler(object sender, UnhandledExceptionEventArgs e){
  var ex = e.ExceptionObject as Exception;
  if( ex.ToString().Contains( "DBROverlayIcon" ){
    //show some dialog here telling users to uninstall DBaR
  }
}
like image 141
jcox Avatar answered Nov 11 '22 06:11

jcox


SQLite.Interop.dll is loaded in a tricky way.
By using any reflector you can inspect UnsafeNativeMethods.Initialize() method in System.Data.SQLite.dll by yourself.
Some notes to demonstrate, that it is possible to get something interesting by reflection(1.0.89 version):

  • If SQLite.Interop.dll is placed in base directory - it will be loaded
  • PreLoadSQLite_BaseDirectory and PreLoadSQLite_UseAssemblyDirectory environment variables can affect loading process
  • SQLite.Interop.dll can be searched in predifined subfolders(x86, x64, Win32, Itanium, WinCE)
  • Trace.WriteLine is called to inform selected path(not always)

Source code is also available.

like image 21
FireAlkazar Avatar answered Nov 11 '22 05:11

FireAlkazar


We are dealing with this exact issue and the solution we found is to use the bundled package from the System.Data.SQlite website rather than the package from nuget: https://system.data.sqlite.org/index.html/doc/trunk/www/downloads.wiki

The bundled dll has both the managed and unmanaged assemblies merged so there is no need for dynamically loading the correct Sqlite.Interop.dll so you don't have the issue of conflicting versions in the appdomain.

When using the bundled assembly you need to include your own logic in your application's installer to decide which dll to copy (x86 or x64).

We haven't had any more issues with conflicting versions since using the bundled assembly.

like image 1
bvadala Avatar answered Nov 11 '22 05:11

bvadala