SQLite引发的动态链接库(DLL)问题

22

我们一些用户在运行时遇到了sqlite.interop.dll版本加载问题,这是一个真正的棘手问题。

背景: 一个AnyCPU的WPF应用程序,使用了SQlite .NET和sqlite.interop.dll 1.0.89版本进行部署。我们部署了x86和x64的dll,并使用了SQLite提供的延迟加载功能。 一直使用得很好,但最近我们开始从一些用户那里收到支持问题,其中大多数人通常购买了新的戴尔计算机。看起来有一个旧版本的sqlite.interop.dll(v.1.0.80)被“某种方式”优先加载,导致了缺少入口点“sqlite3_changes_interop”的错误。

我们尝试过:

  1. 更改设置,只需在安装期间将适当的dll(x86/64)复制到可执行文件的同一目录中(即没有单独的x86 / x64文件夹)。这意味着我们不再使用延迟加载,因为正确的dll在可执行目录中可用(尽管我们没有显式禁用sqlite.net中的延迟加载机制)。但这并没有解决问题。

  2. 在应用程序首次加载时显式加载sqlite.interop.dll。同样,这似乎也没有解决问题。

看起来DLL加载位置的顺序在最近几年已经发生了一些变化,而我可能没有很好地掌握它。我一直认为可执行目录中的dll会优先加载,并且已经显式加载过的dll将防止在应用程序生命周期内重新加载相同的dll,所以我无法理解这里到底是怎么回事。

有没有人能够解释一下这里可能发生了什么?问题进一步加剧的是我根本无法在本地复现该问题 - 例如通过在系统路径中放置错误版本的dll等。这让我想到GAC也可能会起作用?

我们真的卡在这个问题上了,任何帮助都将是非常感激的。

此外,作为最后的手段,我可能会考虑回到相同的1.0.80版本,以避免出现此问题。有人知道我们可以从哪里获取旧版的sqlite.net和sqlite.interop.dll吗?

编辑 - 一些附加信息:

冲突是由Dell Backup and Recovery安装的sqlite.interop.dll版本1.0.80引起的。这个软件安装在所有新的戴尔机器上,安装我们的软件的用户都会遇到这个问题。这个Dell软件也使用System.Data.SQLite.dll。

正确的sqlite.interop.dll版本位于与我们的可执行文件相同的目录中,我所了解的关于dll加载的一切都表明这应该是首选加载的。

虽然我们尚未能够在本地重现此问题,但似乎错误的interop.dll版本不在路径上。此外,Dell备份实用程序会在启动时自动运行。有人知道可能通过什么机制来挂钩dll加载请求并提供错误的文件吗?

当前的想法是,我们可能会构建自己的System.Data.SQLite.dll,并将interop加载代码更改为具有特定名称的版本(例如sqlite.interop.1.0.89.dll)。这对于未来来说并不是一个好的解决方案,但是..


难道不是因为戴尔程序已经在运行,你的DLL请求已经被驻留的程序满足了(除非你明确指定一个版本)?如果停止戴尔程序(和/或禁用它并重新启动),事情是否能正常运作? - TripeHound
我本来以为这种并列问题已经不会再出现了(在W7/8上),但似乎确实发生了类似的情况。我要求用户进入安全模式并报告问题是否消失,作为诊断工具。 - Matt
作为后续 - 在另一个已加载并当前正在使用先前版本sqlite.interop.dll的应用程序的简单测试案例中,这不会影响我们的真实应用程序加载的版本(我已经测试过了)。所以还有其他问题。 - Matt
6
也许有点晚了,但请看这个问题。当SQLite.Interop.dll被注册为一个外壳扩展(在Windows资源管理器中),它将被加载到你的AppDomain中。答案中描述了一种解决方法(使用app.config)。 - dymanoid
谢谢dymanoid - 我认为你可能有所发现!目前还无法确认,但这似乎与一些用户报告相一致(例如,错误并不总是发生,用户确实需要首先打开文件浏览器来指定数据库文件名)。您有什么想法可以如何安装Sqlite作为shell扩展,以便我可以测试这个问题? - Matt
1
这正是我之前遇到的问题,并且可以通过我的答案来“修复”。出现问题的程序也是戴尔备份和恢复。 - kjbartel
3个回答

14
我们的应用程序也遇到了同样的问题。正如您所提到的,问题在于Dell Backup and Recovery安装了一个使用几个流行dll的旧版本的shell扩展程序。它们会对任何启动文件对话框并且还使用这些库的应用程序造成麻烦,因为shell扩展程序会将它们的dll加载到您的AppDomain中。到目前为止,我们唯一的解决方案是告诉用户卸载Dell Backup and Recovery。
如果像dymanoid所提到的那样强制使您的应用程序加载正确的库,那么当它显示文件对话框时,您的应用程序将崩溃(因为shell扩展程序将崩溃)。如果您不这样做,那么当它尝试从其数据库中读取时,您的应用程序将崩溃。
有趣的是,Dell备份和恢复软件已经成为了惯犯; 它也会以同样的方式破坏QT5。 QT团队的推荐解决方案是使用-qtnamespace [name]选项编译您的QT库,并将其命名为不同的名称。我们可以尝试在system.data.sqlite中设置类似的东西,但那样我们就必须编译自己的版本。
Microsoft已经意识到问题,但拒绝修复它。
我希望Dell的人们能够像这样实现他们的Shell扩展程序Portroit Pro, SONARAutoDesk解决此问题的方法也是卸载Dell Backup and Recovery。在我们的应用程序中,此问题的典型堆栈跟踪如下:
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()

回答Track的评论,如果您想检测这个特定问题并向用户提供一些特殊提示,您可以执行以下操作:

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
  }
}

谢谢 - 我接受这个答案以提高可见性,虽然我必须要向dymanoid表示敬意,他在评论中也提到了这个问题。我们已经得出了与您相同的结论,现在请求用户卸载Dell应用程序,但实际上我们已经有一段时间没有收到关于此问题的支持请求了,所以“眼不见为净”。我能想到的唯一解决方案 - 除非Microsoft采取措施修复这个恶心的问题 - 就是重新编译SQLite.NET以使用一个不同命名的interop dll。 - Matt
这正是今天早上导致我们软件在现场崩溃的确切问题。幸运的是,我们的软件安装了未处理异常过滤器,保存了完整的转储文件(是的)。在windbg中打开转储文件,lm命令显示有两个System_Data_SQLite模块,另一个名称为System_Data_SQLite_73d20000,lm v m System_Data_SQLite_73d20000显示它的绝对路径:C:\Program Files (x86)\Dell Backup and Recovery\Components\Shell\System.Data.SQLite.dll,最终将我带到了谷歌和这里。 - zhaorufei
谢谢!我怎样可以用C#判断计算机上是否安装了'Dell Backup and Recovery'? - Track
1
@Track 我不认为我想要在注册表或程序文件中寻找它,但堆栈跟踪是相当明显的。我已经更新了我的答案。不确定那是否是您想要的,但它至少应该帮助人们搜索这个问题。 - jcox

3

SQLite.Interop.dll以一种巧妙的方式加载。
通过使用任何反编译器,您都可以自行检查中的UnsafeNativeMethods.Initialize()方法。
以下是一些说明,以证明可以通过反射(1.0.89版本)获得有趣的内容:

  • 如果将SQLite.Interop.dll放置在基目录中,则会被加载
  • PreLoadSQLite_BaseDirectory和PreLoadSQLite_UseAssemblyDirectory环境变量可能会影响加载过程
  • SQLite.Interop.dll可以在预定义的子文件夹(x86、x64、Win32、Itanium、WinCE)中进行搜索
  • Trace.WriteLine被调用以通知所选路径(并非总是如此)

源代码也可用。


感谢您抽出时间回答 - 我很感激。不幸的是,我已经按照这些步骤进行了尝试,并且实际上也已经跟踪了处理interop.dll加载的源代码,但是我所看到的一切都表明我们的安装应该可以工作(即我们在可执行文件的同一目录中具有正确的interop dll,但仍然加载了错误的版本..)。我已经更新了问题并提供了更多细节。 - Matt
你尝试过检查融合日志以查看运行时如何解析引用吗? - Bradley Uffner
Fusion能否帮助显式加载本地dll?我(几乎)确定正在加载正确的.NET程序集,因为如果加载旧程序集,则不会出现未解析的外部问题-因此.NET程序集和本地Interop dll之间存在不匹配,该dll在运行时使用LoadLibrary()加载。 - Matt
@Matt,你尝试过设置跟踪侦听器以记录已加载的程序集路径吗?无论如何,这是一个非常有趣的问题,知道具体发生了什么会很好。 - FireAlkazar
@FireAlkazar - 目前的主要困难是我无法在本地重现此问题(我尝试在几台机器上安装戴尔实用程序,但失败了)。因此,我需要依靠(有点不满意的)用户提供信息。我希望今天能够找出是否仅停止戴尔备份服务就可以解决问题,但是相关用户已经卸载了它(戴尔应用程序),这至少解决了问题,但对诊断没有太大帮助。 - Matt

1
我们正在处理这个确切的问题,我们找到的解决方案是使用System.Data.SQlite网站上的捆绑包,而不是nuget上的包: https://system.data.sqlite.org/index.html/doc/trunk/www/downloads.wiki 捆绑的dll将托管和非托管程序集合并在一起,因此无需动态加载正确的Sqlite.Interop.dll,因此您不会遇到应用程序域中版本冲突的问题。
当使用捆绑的程序集时,您需要在应用程序安装程序中包含自己的逻辑来决定要复制哪个dll(x86还是x64)。
自从使用捆绑的程序集以来,我们没有遇到任何版本冲突的问题。

只是想澄清一下,您说的是“混合模式”程序集吗?这些程序集包含了所有所需的内容,在单个 DLL 中,而且不需要在全局程序集缓存中安装任何内容吗?如果是真的,那听起来就是一个简单的解决方案 - 谢谢! - Matt
没错。您仍然需要下载单独的x86和x64程序集以针对不同的平台,但您不需要在全局程序集缓存中安装任何内容。 - bvadala

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接