【问题标题】:DLL hell with SQLiteSQLite 的 DLL 地狱
【发布时间】:2015-08-30 06:47: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 的副本引起的。这安装在所有新的戴尔机器上,在这种机器上安装我们的软件的用户都会遇到这个问题。此戴尔软件还使用 System.Data.SQLite.dll。

正确版本的 sqlite.interop.dll 与我们的可执行文件位于同一目录中,我对 dll 加载的所有了解都表明应该优先加载它。

虽然我们尚未能够在本地重现该问题,但似乎错误版本的 interop.dll 不在路径上。此外,戴尔备份实用程序在启动时自动运行。有谁知道这可能与 dll 加载请求挂钩并提供错误文件的任何可能机制?

目前的思路是我们可以构建自己的 System.Data.SQLite.dll 并将互操作加载代码更改为特定命名的版本(例如 sqlite.interop.1.0.89.dll)。不是一个很好的解决方案,但是..

【问题讨论】:

  • 难道不是——因为戴尔程序已经在运行——你对 DLL 的请求已经被已经驻留的那个满足了(除非你特别指定了一个版本)?如果您停止戴尔程序(和/或禁用它并重新启动),一切正常吗?
  • 我没有认为这些类型的并行问题不再存在(W7/8),但似乎正在发生类似的事情。我已要求用户以安全模式启动并报告问题是否消失 - 仅作为诊断工具。
  • 作为后续 - 当然在简单的测试案例中,有另一个应用程序已经加载并且当前正在使用之前版本的 sqlite.interop.dll,这不会影响我们真正的应用程序的哪个版本负载(我已经测试过了)。所以还有更多的事情发生..
  • 也许为时已晚,但请看this 问题。当 SQLite.Interop.dll 注册为 shell 扩展(在 Windows 资源管理器中)时,它将被加载到您的 AppDomain。答案中描述了一种解决方法(使用 app.config)。
  • 这正是我遇到的问题,可以用my answer“修复”前面的问题。问题程序也是戴尔备份和恢复。

标签: c# .net sqlite dll


【解决方案1】:

我们的应用也有同样的问题。正如您所提到的,问题在于戴尔备份和恢复安装了一个外壳扩展,该扩展使用了几个流行 dll 的旧版本。他们与任何启动文件对话框并使用这些库的应用程序玩得很糟糕,因为外壳扩展将它们的 dll 加载到您的 AppDomain 中。到目前为止,我们唯一的解决方案是告诉用户卸载 Dell Backup and Recovery。

如果你 force your app to load the correct library 提到了 dymanoid,那么你的应用会在显示文件对话框时崩溃(因为 shell 扩展会崩溃)。如果您不这样做,那么当您的应用尝试从其数据库中读取数据时,它将会崩溃。

有趣的是,戴尔备份和恢复是屡犯;它也以同样的方式breaks QT5。 QT 人员的recommended solution 是使用 -qtnamespace [name] 选项以不同的名称编译您的 QT 库。我们或许可以使用 system.data.sqlite 来安装类似的东西,但是我们必须编译我们自己的版本。

Microsoft 是 aware of the problem,但拒绝修复它。

我希望戴尔人已经实现了他们的外壳扩展 like this

Portroit ProSONARAutoDesk 解决此问题的方法也是卸载 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 表示敬意,他也在评论中提到了这个问题。我们得出了和你一样的结论,现在要求用户卸载戴尔应用程序,但事实上我们已经有一段时间没有关于这个问题的支持请求了,所以“眼不见,心不烦”。我能想到的唯一解决方案——除非微软采取措施来解决这个讨厌的问题——是重新编译 SQLite.NET 以使用不同命名的互操作 dll。
  • 这是导致我们的软件今天早上在现场崩溃的确切问题。幸运的是,我们的软件安装了未处理的异常过滤器,它保存了一个完整的转储(是的)。在windbg中打开dump文件,lm命令显示有两个System_Data_SQLite模块,另一个名字是System_Data_SQLite_73d20000,lm vm System_Data_SQLite_73d20000显示它的绝对路径:C:\Program Files (x86)\Dell Backup and Recovery\Components\Shell\System .Data.SQLite.dll ,最终将我带到谷歌和这里。
  • 谢谢!如果计算机上安装了“Dell Backup and Recovery”,我如何确定使用 C#?
  • @Track 我不认为我想通过注册表或程序文件寻找它,但堆栈跟踪相当独特。我已经相应地更新了我的答案。不确定这是否是您所追求的,但它至少应该可以帮助人们用谷歌搜索这个问题。
【解决方案2】:

SQLite.Interop.dll 以一种棘手的方式加载。
通过使用任何反射器,您可以自己检查 System.Data.SQLite.dll 中的 UnsafeNativeMethods.Initialize() 方法。
一些说明,可以通过反射得到一些有趣的东西(1.0.89版本):

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

Source code is also available.

【讨论】:

  • 感谢您抽出宝贵时间回答 - 我很感激。不幸的是,我已经完成了这些步骤,实际上还逐步完成了处理 interop.dll 负载的源代码,我看到的所有内容都表明我们的安装应该可以工作(即我们有正确的 interop dll 在与可执行文件相同的目录,但仍然加载了错误的版本..)。我已经用更多细节更新了这个问题。
  • 您是否尝试过检查 Fusion 日志以了解运行时如何尝试解析引用?
  • Fusion 能否帮助显式加载本机 dll?我(几乎)确定正在加载正确的 .NET 程序集,因为如果正在加载较旧的程序集,我们将不会得到未解析的外部程序 - 因此 .NET 程序集和本机互操作 dll 之间存在不匹配,即在运行时使用 LoadLibrary() 加载。
  • @Matt 您是否尝试设置跟踪侦听器来记录加载的程序集路径?无论如何,这是一个非常有趣的问题,很高兴知道到底发生了什么。
  • @FireAlkazar - 目前最大的困难是我无法在本地重现该问题(我尝试在多台机器上安装戴尔实用程序,但失败了)。因此,我依靠(有些不满的)用户获取信息。我希望今天能知道简单地停止戴尔备份服务是否可以解决问题,但有问题的用户继续卸载它(戴尔应用程序),这至少解决了问题 - 但没有帮助与诊断有关。
【解决方案3】:

我们正在处理这个确切的问题,我们找到的解决方案是使用来自 System.Data.SQlite 网站的捆绑包,而不是来自 nuget 的包: https://system.data.sqlite.org/index.html/doc/trunk/www/downloads.wiki

捆绑的 dll 合并了托管和非托管程序集,因此无需动态加载正确的 Sqlite.Interop.dll,因此您不会遇到 appdomain 中版本冲突的问题。

使用捆绑程序集时,您需要在应用程序的安装程序中包含自己的逻辑,以决定要复制哪个 dll(x86 或 x64)。

自从使用捆绑的程序集以来,我们没有遇到任何版本冲突的问题。

【讨论】:

  • 澄清一下,您说的是“混合模式”程序集?这些包含所需的一切,在一个单独的 DLL 中,并且不需要在 GAC 中安装任何东西?如果属实,这听起来很简单 - 谢谢!
  • 是的,没错。您仍然需要下载单独的 x86 和 x64 程序集以针对不同的平台,但您不需要在 GAC 中安装任何东西
猜你喜欢
  • 1970-01-01
  • 2016-05-29
  • 1970-01-01
  • 1970-01-01
  • 2011-01-22
  • 2019-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多