【问题标题】:Are there reasons why DLL redirection won't work other than the presence of a manifest?除了存在清单之外,还有其他原因导致 DLL 重定向不起作用吗?
【发布时间】:2011-03-24 22:21:05
【问题描述】:

我们有一个使用 Crystal Reports XI 生成打印报表的旧版 VB6 应用程序。我们通过经验发现,如果 Crystal Reports 打印引擎选择了错误版本的 usp10.dll(Windows Uniscribe 库),它就会崩溃。

一位客户的 Windows 7 机器(运行 Windows 7 Enterprise,32 位)一直存在打印问题。但是,我们还有一些其他客户在运行不同版本的 Windows 7 时没有遇到任何问题。

在其中一台出现打印问题的机器上,我注意到文件夹 C:\Program Files\Common Files\Microsoft Shared\Office10\ 中有旧版本的 usp10.dll(与 Crystal Reports XI 不兼容)。我不确定是什么应用程序安装了这些文件,因为客户没有安装 Office 2002(所以我假设另一个应用程序安装了它们)。但是,我暂时重命名了该文件,并且我们的应用程序能够正确打印,因此我们的应用程序似乎最初加载了该版本的文件,这导致了崩溃。

崩溃仅发生在用户尝试打印报告的那一刻。我们的应用程序直接依赖于 craxdrt.dll(Crystal Reports ActiveX 设计器运行时库)和 crviewer.dll(Crystal ActiveX 报表查看器库),无论是否发生崩溃我们直接通过 craxdrt.dll 或通过 Report Viewer 控件进行打印。

过去,我们通过将已知良好版本的 usp10.dll 复制到我们的应用程序目录并创建 .local 文件来启用 D​​LL 重定向来解决此问题.在客户站点,我尝试了这个,还尝试了为我们的 EXE 创建一个 .local 文件夹并将 usp10.dll 放在那里的替代方法,但两种方法都没有在我连接的机器上工作。

我确实注意到 usp10.dll 是 Windows 中的“已知”DLL(它在 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\KnownDLLs 中有一个条目),但我在另一台 Windows 7 机器上测试了我们的应用程序(运行专业版, 32-bit) 这里也将 DLL 列为注册表中的已知 DLL,通过使用 Dependency Walker,我可以看到重定向在该计算机上工作。这有点令人困惑,因为 Microsoft documentation 声明无法重定向已知 DLL。此外,正如我在问题标题中所暗示的那样,我们的主 EXE 不使用清单文件(Microsoft 文档指出存在嵌入式或独立清单会禁用 DLL 重定向)。

所以,我的问题是,是否有任何其他原因导致 DLL 重定向可以在某些机器上工作而不在其他机器上工作,这与 Windows 7 和 Windows XP 之间的差异有什么关系吗?我曾考虑删除KnownDLLs 注册表项中的所有内容,但由于重定向是在具有相同KnownDLLs 集合的机器上运行的,我不确定这是否会真正解决问题,我不知道如果我不需要,想删除那个键。我还没有机会再次连接到客户的机器以运行 Dependency Walker,但我不确定我是否能够解释它的日志(即使在它所在的机器上)工作时,我看到很多 LoadLibrary 调用 usp10.dll 指向重定向文件夹以外的文件夹,但有些调用显然被重定向了,所以我不确定这也意味着什么)。


编辑:我还应该提到,我们检查过的每台计算机在System32 文件夹中也有另一个usp10.dll 副本。查看 Chris 的 answer 和拉里·奥斯特曼 (Larry Osterman) 的 this blog post 对已知 DLL 如何工作的更多解释,我意识到这可能根本不是问题的原因,因为我们的程序没有加载 @987654331 的副本@ 即在 System32 文件夹中。


编辑#2:在我的 VB6 开发机器 (Windows XP SP3) 上玩了一些 Dependency Walker 之后,打印一直有效,我能够收集一些信息。我在 Dependency Walker 中分析了我们的应用程序并将其设置为记录完整路径名,看起来 Crystal Reports 依赖项之一(另一个 Crystal Reports DLL)试图加载 usp10.dll 来自多个(硬编码)路径,然后放弃并仅通过文件名询问。事实证明,它首先尝试从 Crystal Reports bin 文件夹加载它,然后尝试从 C:\Program Files\Common Files\Microsoft Shared\Office10\usp10.dll 加载它。如果在任一位置都找不到它,它只会向 Windows 询问usp10.dll(它将获取System32 中的那个)。但即使这样也不一致。有时它会在 Office10 文件夹中请求文件,然后似乎忽略了它找不到文件的事实,而其他时候有一系列 LoadLibrary 调用看起来像Crystal Reports 代码正在积极寻找不同位置的文件的备用副本。更令人困惑的是,至少有一个 Crystal Reports 组件看起来实际上在加载时依赖于 usp10.dll,因此该组件似乎总是在 System32 中获取副本。

我仍然不是 100% 清楚 为什么.local 重定向在这种情况下在此客户的计算机上不起作用,但我认为这部分解释了为什么这个特定客户遇到问题,因为所有有问题的计算机都有一个Office10 文件夹,其中包含一个明显不兼容的usp10.dll 版本。

但是,再一次,我仍然有一个基本问题:如果这些组件在这么多不同的地方寻找这个文件,我怎么能保证它们都将使用 相同复制?

【问题讨论】:

  • 在出现故障的计算机上 - system32 中是否有 usp10.dll 的副本?我可以将这种情况理解为: 1. 因为 usp10.dll 被列为已知 dll,我们禁用了 .local 重定向器。 2. 如果此时usp10.dll不在system32中,当系统尝试为某个进程加载KnownDLLs时,找不到usp10.dll,后续尝试使用显式路径加载不兼容版本成功。解决这个问题应该是:如果丢失,请在 system32 中安装一个副本。
  • @Chris:失败的计算机除了 Office10 文件夹中的副本之外,还具有 system32 中的 usp10.dll 副本。据我所知,system32中的那个没问题,Office10文件夹中的那个和Crystal不兼容,因为如果我把那个重命名为usp10.dll_,比如Crystal Reports突然开始工作。令人困惑的是,USP10.dll 列为故障计算机上的已知 DLL,但 Crystal Reports 结合了对 usp10.dll 的隐式和显式依赖,我认为这使事情变得更加复杂.
  • 根据我刚刚找到的另一个 MSDN 页面,KnownDLLs 注册表项仅影响隐式加载的 DLL,而不影响使用 LoadLibrary 加载的 DLL。根据Dependency Walker(在我的Windows 7虚拟机上运行程序),一些Crystal组件加载System32版本(因为它们对usp10.dll有加载时依赖,即隐式链接),一些LoadLibrary调用也加载System32副本也是如此,但其他 LoadLibrary 调用会继续在 Office10 中加载副本。它将两个 DLL 物理加载到进程中(LoadLibrary 返回 2 个不同的句柄)。
  • 对于上述测试,我通过创建一个虚拟的 Office10 文件夹并将 usp10.dll 的副本放在那里(并将原始文件留在 system32 中)来模拟客户环境。在这种情况下,DLL 总是被加载两次。更糟糕的是,至少有一个使用usp10.dll 的Crystal DLL 似乎调用了DLL 的每个 副本中的函数。所以当Office10 DLL实际上是一个不同的版本时,它仍然会导致Crystal崩溃,因为它仍然会使用那个版本的DLL进行某些函数调用,即使它已经在system32中加载了那个......
  • 另外,在 Windows XP 上,LoadLibrary 记录了 C:\Program Files\Common Files\Business Objects\3.0\bin\usp10.dll 的调用,所以我想我可以通过将 usp10.dll 的副本放在该文件夹中来解决问题。但是,当在 Windows 7 上运行相同的测试时,没有一个 LoadLibrary 调用在该文件夹中查找(它们在 Office10 文件夹中查找)。这个文件夹不在我的 XP 机器上的 PATH 中,所以我不知道为什么它在 XP 中而不是在 Windows 7 中,否则我认为把它放在那个文件夹中可以解决这个问题。跨度>

标签: windows windows-7 dll


【解决方案1】:

我刚刚在运行 Server 2008 R2 的 TS 上遇到了类似的问题。来自事件日志的错误:

Log Name: Application
Source: Application Error
Date: 5/23/2012 10:32:37 AM
Event ID: 1000
Task Category: (100)
Level: Error
Keywords: Classic
User: N/A
Computer: 
Description:
Faulting application name: crw32.exe, version: 11.0.0.1282, time stamp: 0x422d5c77
Faulting module name: usp10.dll, version: 1.420.2600.5969, time stamp: 0x4bc88269
Exception code: 0xc0000005
Fault offset: 0x00014ee4
Faulting process id: 0x1744
Faulting application start time: 0x01cd38f8ce57fbd5
Faulting application path: C:\Program Files (x86)\Business Objects\Crystal Reports 11\crw32.exe
Faulting module path: C:\Program Files (x86)\Common Files\Microsoft Shared\Office10\usp10.dll

我尝试将 usp10.dll 从 \Windows\System32 复制到 C:\Program Files (x86)\Common Files\Business Objects\3.0\bin。然后运行 ​​crdeploy.reg 文件,但由于服务器上的 64 位操作系统,必须手动更新 regkey 以包含 (x86)。 Crystal Reports 应用程序仍然无法识别 DLL,它一直在 \Office10 文件夹中查找。所以我只是重命名了该文件夹中的 DLL 副本,然后再次从 \System32 复制。这就像一个魅力,而且 DLL 上的文件版本与 Mike Spros 发布的完全相同。

【讨论】:

    【解决方案2】:

    拥有 Windows 不知道的额外副本对我来说似乎不是一个好主意(它将如何更新?)

    为什么你不能打电话给LoadLibrary("usp10.dll")自己作为你在启动时做的第一件事?

    【讨论】:

    • 我同意拥有两个 DLL 副本并不是最理想的解决方案,但我认为即使 System32 中的那个得到更新,它仍然可以工作,因为我认为任何更新的版本随 Windows 提供,或者更新仍应与 Crystal Reports 兼容。 Office10 附带的版本(1.405.2416.1)似乎是唯一不支持 Crystal 的版本。到目前为止,没有客户在应用 Windows 服务包或更新后遇到打印问题。
    • 我不认为在我们的程序中调用LoadLibrary 将解决Crystal 在Windows 7 中加载两个DLL 的问题。由于usp10.dll 是一个已知的DLL 并且craxdrt.dll(它是一个Crystal 组件)隐式链接到usp10.dll,Windows 将始终为该组件加载System32 中的副本。使用LoadLibrary 的其他组件将从Crystal 首先在其中找到文件的任何文件夹加载DLL(即,它将在注册表中指定的CommonFiles 文件夹中查找,然后是Office10 文件夹,然后是System32) .
    • 其实,我想我明白你的意思了:来自LoadLibrary 文档(msdn.microsoft.com/en-us/library/ms684175%28VS.85%29.aspx):如果 lpFileName 不包含路径并且有多个加载的模块具有相同的基本名称和扩展名,该函数返回首先加载的模块的句柄。 因此,通过首先调用 LoadLibrary("usp10.dll"),它应该强制每隔一个 LoadLibrary 调用返回相同的句柄,而不是加载 DLL第二次。这是你的建议吗?
    • 我认为这种方法的问题在于 Crystal Reports 在调用 LoadLibrary 时使用了完整路径,所以如果我正确阅读了文档,这意味着它不会 返回与原始 LoadLibrary("usp10.dll") 相同的句柄(因为文档说它仅在“lpFileName包含路径”时返回现有句柄)。还是我误会了什么?
    【解决方案3】:

    解决方案实际上很简单,但我花了一段时间才弄清楚它为什么起作用。

    在客户机器上,我将 usp10.dll 从 C:\Windows\System32(已知良好的版本)复制到文件夹C:\Program Files\Common Files\Business Objects\3.0\bin(安装了大部分 Crystal 组件)。然后我运行了一个 crdeploy.reg 文件,该文件已经存在于 bin 文件夹中:此文件将 HKEY_LOCAL_MACHINE\SOFTWARE\Business Objects\Suite 11.0\Crystal Reports 键添加到注册表并将值 CommonFiles 设置为 C:\Program Files\Common Files\Business Objects\3.0\bin

    由于今天早些时候我无法连接到客户的计算机,因此我在 Windows 7 虚拟机上对该问题进行了更多测试。就像我在编辑中提到的那样,在这台计算机上,Crystal Reports 从未在C:\Program Files\Common Files\Business Objects\3.0\bin 目录中查找usp10.dll,因此它会立即尝试将副本加载到C:\Program Files\Common Files\Microsoft Shared\Office10 文件夹中。

    事实证明,当 Crystal Reports 调用 LoadLibrary 时,它会检查以下文件夹中的 usp10.dll

    • 如果注册表中存在HKEY_LOCAL_MACHINE\SOFTWARE\Business Objects\Suite 11.0\Crystal Reports\CommonFiles,它将使用该路径调用LoadLibrary

    • 如果注册表项不存在,或者该文件夹中不存在 usp10.dll,Crystal Reports 将调用LoadLibrary,并以C:\Program Files\Common Files\Microsoft Shared\Office10\usp10.dll 作为路径。 p>

    • 1234563 p>

    因此,在我的测试 Windows 7 机器上,我没有设置 CommonFiles 注册表项,因此 Crystal Reports 始终加载 Office10 中的 usp10.dll 版本文件夹,即使在将 usp10.dll 的副本放入 C:\Program Files\Common Files\Business Objects\3.0\bin 之后也是如此。一旦我将注册表项设置为指向正确的位置,Crystal Reports 就会加载正确版本的文件。

    在客户的机器上,注册表已经将CommonFiles 路径设置为正确的文件夹,但我们应用程序的安装程序没有将 usp10.dll 安装到该文件夹​​,所以它仍然是拿起Office10文件夹中的副本。

    提供给客户的最终解决方法非常简单:

    1. usp10.dll的版本从System32复制到C:\Program Files\Common Files\Business Objects\3.0\bin

    2. 运行bin文件夹中的crdeploy.reg文件,确保CommonFiles注册表项存在并指向C:\Program Files\Common Files\Business Objects\3.0\bin

    我原本以为将 usp10.dll 的副本放入 bin 文件夹可以解决客户机器上的问题,但正如我所说,这在我的 Windows 7 上不起作用测试机因为我缺少CommonFiles这个注册表项,所以犹豫着考虑下发修复。

    另外,为了帮助其他遇到此问题的人,所涉及的 usp10.dll 版本为:

    • 1.405.2416.1:这是Office10 文件夹中的版本,也是导致 Crystal Reports 崩溃的版本。打印报表时,Crystal Reports 调用 usp10.dll 中的某个函数时会发生访问冲突(我没有原始堆栈跟踪,但我认为它是 ScriptApplyDigitSubstitution 函数) .

    • 1.626.7600.16385:这是一个已知的良好版本,可与 Crystal Reports 一起正常工作。这个版本好像是Windows 7默认安装的那个。

    还有其他版本,例如默认安装在 Windows XP 中的 System32 文件夹中,也可以与 Crystal Reports 一起正常工作。

    【讨论】:

      【解决方案4】:

      我的第一个想法是:2002/2003 年将 .manifest 文件及其暗示的所有内容添加到 Windows XP 中。 为什么@#$% 你的应用程序 - 以及你的应用程序为此使用的库 - 不使用这项技术来解决这个小小的“dll-hell”。 正是他们被开发来解决的场景。

      接下来,我很确定“KnownDlls”仅涵盖操作系统在 System32 中实际找到的 dll。在随机路径位置(如在 Office2002 文件夹中)查找 dll 我希望至少会通过一些内部完整性检查(is-the-dll-a-real-KnownDlls-candidate)测试。并且在系统文件夹之后搜索 PATH,所以当在 ...\Office10\ 中找到一个 usp10.dll 时,它不能是一个真正已知的 dll(根据定义)。

      接下来,我还确定 .local 文件没有按照您的想法执行。 .local 文件的文档根本没有意义,因为它真正说的是,在应用 .local 文件之后对 dll 的搜索顺序正是 dll 通常的默认搜索顺序 - exe 文件夹总是在系统文件夹之前搜索无论如何。

      .local 真正产生潜在差异的唯一时间是应用程序在调用 loadLibrary 时使用显式路径加载 dll(或使用更改后的搜索路径标志到 LoadLibrary 或使用 SetDllSearchDirectory API)。执行加载的 exe 或 dll 选择非常特定的 dll 的所有情况 - 应用程序作者希望以某种方式覆盖该 dll。 .local 文件不能(无论如何在我看来)更改任何仅由其名称指定的 dll 文件的搜索行为。

      因此,如果将 usp10.dll 安装到 system32 中,它可能会作为 KnownDll 被拾取然后使用 - 尽管您的本地副本(和 .local 文件)。 如果它位于路径上的其他位置,则无论如何都应首先使用 exe 文件夹中的副本 - (假设 LoadLibrary("usp10.dll") 是加载 dll 的方式)。

      即使您竭尽全力创建程序集以包含已知良好的 usp10.dll,然后使您的应用程序依赖于它,我仍然认为传递给 LoadLibrary 的完全限定路径会破坏任何搜索逻辑根本 - 包括在依赖程序集列表中查找 dll。

      所以,你的问题让我很困惑。使用的usp10.dll应该是

      • system32 中的那个(如果存在)(因为 KnownDlls 覆盖了你的 exe 文件夹中的那个)
      • 应用程序文件夹中的那个,因为如果 KnownDlls 未命中,搜索逻辑总是会首先找到这个。

      除非 usp10.dll 实际上是一个 com dll,或者注册表中有一个完全限定的路径供消费者用来加载它,在这种情况下,加载的路径应该是:

      • 您的应用程序文件夹中的那个,因为这似乎是 .local 文件可能适用的一种情况。

      鉴于 KnownDLLs 中 DLL 的存在抑制了 .local 的功能,并且 Crystal 报告 dll 是病态的......

      我只能建议:

      此线程包含一个名为 PatchIAT 的函数 - 将其导入您的代码中。

      在 Crystal Reports dll 中使用任何功能之前(这会导致他们去寻找 usp10.dll) - 调用 LoadLibrary 以获取 dll 的句柄,然后在句柄上调用 PatchIAT,将 dll 的调用重定向到 LoadLibrary您的 EXE 实现的功能。

      在您的 EXE 的 LoadLibraryThunk 过程中,将任何调用传递给系统的 LoadLibrary,除非它为指向 usp10.dll 的显式路径 - 在这些调用中 - 返回错误代码。

      Disable antialiasing for a specific GDI device context

      【讨论】:

      • 仅供参考:usp10.dll 是“Uniscribe Unicode 脚本处理器”。它存在于 system32 目录中,并且明确在 KnownDlls 列表中。
      • @Oleg:是的,我应该提到我测试的两台机器实际上在不同文件夹中都有多个 usp10.dll 副本,包括 System32 中的副本,但在客户的机器上如果没有.local 文件或文件夹,它会继续从Office10 加载DLL,尽管如此。但是,如果我没有使用.local文件,它将从Office11文件夹(该机器没有Office10文件夹)中提取,并且随着.local 文件的存在,它将从应用程序的目录中加载它。这对我来说毫无意义,因此提出了这个问题。
      • @Chris:澄清一下,usp10.dll 只是一个标准 DLL,而不是 COM DLL。实际上是 Crystal Reports 加载它,所以我不清楚它是如何加载 DLL 的,尽管它可以解释为什么 Dependency Walker 说它有时会从 2 个不同的地方加载 DLL(我猜它使用完整的加载DLL时有时是路径,有时只是名称?)。不过,奇怪的是.local 重定向实际上已经在其他客户站点上解决了这个问题,我很可能完全误解了它应该如何实际工作。
      • AFAIK,其他客户网站在System32 中也有usp10.dll 的副本。在这些客户遇到打印问题的情况下,它总是因为在随机的OfficeXX 文件夹中有一个旧版本的usp10.dll,并在我们的应用程序目录中放置了一个.local 文件和将已知良好的 DLL 副本放在那里可以解决问题。听起来.local 文件可能是多余的,因为它似乎无论如何都应该在应用程序目录中获取 DLL。
      • 至于使用清单文件,我也考虑过该解决方案,但无论出于何种原因,我也无法使其工作(我可能做错了)。整个情况令人困惑的部分是,我们已经为其他客户解决了同样的问题,但在这个特定的客户站点上却没有。
      猜你喜欢
      • 1970-01-01
      • 2012-07-20
      • 2017-09-11
      • 2019-10-14
      • 2015-12-08
      • 2023-02-09
      • 2014-02-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多