【问题标题】:Prevent MFC ActiveX control from using DLLs that are already loaded into the process防止 MFC ActiveX 控件使用已加载到进程中的 DLL
【发布时间】:2011-07-08 23:09:43
【问题描述】:

短版:通过 Internet Explorer 加载到网页中的 MFC ActiveX 控件如何保证其关联的 DLL 是从其自己的目录加载的,而不是选择可能已经存在的同名 DLL被加载到进程中?

长版,带有血淋淋的细节:我有一个应用程序 myapp.exe,它使用一组 DLL:one.dlltwo.dllthree.dll

MFC ActiveX 控件也使用相同的 DLL,该控件公开一些与 myapp.exe 相同的功能,因此有一个 mycontrol.ocx 也与这些 DLL 链接。 ActiveX 控件与application/myapp MIME 类型相关联,因此IE 将使用它来显示使用myapp.exe 创建的文档。

可以同时安装 myapp.exe 的版本 1 和 2,但只有最新版本的 mycontrol.ocx(版本 2)与 application/myapp MIME 类型相关联:

c:\Program Files\MyApp\Version 1\
                                 myapp.exe
                                 mycontrol.ocx
                                 one.dll
                                 two.dll
                                 three.dll
c:\Program Files\MyApp\Version 2\
                                 myapp.exe
                                 mycontrol.ocx  <-- registered with the MIME type
                                 one.dll
                                 two.dll
                                 three.dll

这就是它变得困难的地方:myapp.exe 具有用于显示 Web 内容的嵌入式 Internet Explorer 控件。您可以运行myapp.exe 的版本1,将嵌入的Internet Explorer 指向application/myapp 文档,IE 将加载mycontrol.ocx 的版本2 来查看它。这应该没问题,但不是:

Windows 加载mycontrol.ocx,发现它依赖于one.dll,并且在这个过程中已经有一个one.dll,并将mycontrol.ocx的导入表指向已经加载的(版本 1) one.dll,而不是加载 one.dll 的版本 2。这失败了,因为 mycontrol.ocx 的版本 2 使用了 one.dll 的版本 2 中的新 API,而版本 1 中没有。

我该如何阻止它这样做?如果one.dll 尚未加载,Windows 会在c:\Program Files\MyApp\Version 2 中查找它,一切都会好起来的。 (而且我不能为每个版本的软件重命名我的所有模块!)

失败的解决方案 #1:我给了 mycontrol.ocx 一个清单,为所有 DLL 指定了 &lt;file ...&gt; 元素,但这不起作用。 one.dllVersion 2 目录加载,但 two.dll 没有。我认为这是因为 two.dll 正在加载,因为 one.dll 引用它,而 one.dll 没有这样的清单。

失败的解决方案 #2:所以我向 所有 DLL 添加了一个清单,每个 DLL 都在 &lt;file ...&gt; 元素中列出了它的依赖关系。但这完全破坏了myapp.exe,因为 MFC 现在将每个 DLL 视为具有自己的激活上下文,这是不对的 - 整个进程(在 myapp.exe 的情况下)或整个实例都应该有一个 MFC 上下文控制(在mycontrol.exe的情况下)。我不能为每个模块都有一个新的激活上下文;我需要一个用于整个mycontrol.ocx 及其附带的 DLL。

我将所有清单更改为使用相同的 name= 属性,希望这会将它们全部置于相同的上下文中,但这没有任何效果。

失败的解决方案#3:我给了myapp.exe一个清单,上面写着&lt;file name="mycontrol.ocx"/&gt;,希望强制版本1的应用程序使用版本1的控件,这很好。但这会失败,因为当 Internet Explorer 加载 mycontrol.ocx 时,它会使用 mycontrol.ocx 的第 2 版的完整路径调用 LoadLibrary,并且当使用完整路径加载模块时,清单重定向不起作用。

我不能做的事情:我不能更改加载控件的代码,因为是 Internet Explorer 做的(通过 MIME 类型)。

我们将不胜感激地收到任何解决方案、建议或简单的同情信息。

【问题讨论】:

  • #2 上面最有意义,但我不知道您所说的“MFC 现在将每个 DLL 视为它自己的激活上下文,这是不对的”。什么是激活上下文?为什么重要?

标签: windows dll mfc activex manifest


【解决方案1】:

尝试的一个选项可能是将 OCX 的 DLL 依赖项指定为延迟加载,然后编写您自己的延迟加载辅助函数,该函数使用 LoadLibraryEx() 来确定从您要使用的路径加载 DLL。我过去曾对此进行过试验,并让它与可执行文件一起使用,但我不明白为什么它不能与 OCX 一起使用。

MSDN 在using the delay loading helper function 上有不错的文档。在延迟加载辅助函数中,您希望专注于“dliNotePreLoadLibrary”案例,并返回您使用 LoadLibraryEx() 获得的正确 DLL 的 HMODULE。

顺便说一句,最后我没有使用这种方法:我只是确保所有 DLL 中都有版本号。最后,这是最简单的方法...

【讨论】:

  • +1 是一个非常好的主意,但遗憾的是我不能使用它。我的代码广泛使用了导出的 C++ 类,其中one.dll 使用从two.dll 导出的TwoClass。这使得one.dlltwo.dll 导入数据符号TwoClass::vftable,因此当您指定/delayload:two.dll 时链接会失败,因为导入数据符号会排除延迟加载。 (有趣的是,它在 Debug 版本中工作 - 那里的 vtables 必须不同。如果我能找到一种方法让 Release 版本将 vtables 与 Debug 版本相同,我可能没问题,但我不能。)
  • 哎呀,真恶心。可耻的是 MS 还没有想出一个明智的方法来延迟 C++ 类的加载。我可以建议的唯一剩下的事情是分解 OCX 逻辑,以便尽可能在第三个 DLL 中可以将 LoadLibraryEx()d 放入进程中:-(
  • 查看我的回答,了解我最终是如何解决这个问题的,其中确实涉及“分解 OCX 逻辑,以便尽可能多地位于第三个 DLL 中” - 谢谢!
【解决方案2】:

对DLL地狱有点了解,对“MFC Activation Context”一无所知。这就是为什么您上面的 #2 想法似乎很可靠。

但如果这对你不起作用,我会尝试调查三种可能的解决方案:

  1. 疯狂的想法。当您在注册表中将“mycontrol.ocx”注册为与 application/myapp 关联时,您当前已通过其完整路径(c:\program files\app\version2\mycontrol.ocx)注册它。只需将其注册为“ mycontrol.ocx" 没有指定目录。如果幸运的话,IE 控件将 "LoadLibrary("mycontrol.ocx") 并从与您的 EXE 相同的目录中找到它。如果您确实需要 mycontrol.ocx 在独立的 IE 实例中加载,这当然会中断。但也许您可以为外部页面使用备用 MIME 类型(或 com guid)来直接加载控件。

  2. 在您的应用程序安装中,将 EXE 放在与 DLL 不同的目录中。然后使用the "AppPath" registry key将特定的DLL目录虚拟添加到应用程序的路径中。唯一的问题是我认为您不能使用完全限定的路径设置 AppPath 键名。但我知道您可以拥有一个 EXE 名称,其中包含指向不同名称 EXE 的完全限定路径。所以我们可以将“myapp.exe”的第二个实例“虚拟地重命名”为“myapp2.exe”。在您的桌面快捷方式和开始菜单入口点中,它们都会启动“myapp2.exe”,但会被重定向到“version2\app\myapp.exe”

    c:\Program Files\MyApp\Version 1\app\
                                          myapp.exe
    c:\program files\MyApp\Version 1\dll\
                                          mycontrol.ocx
                                          one.dll
                                          two.dll
                                          three.dll
    
    HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\myapp.exe
          (default)=c:\program files\MyApp\Version1\app\myapp.exe (type == REG_EXPAND_SZ)
          Path=c:\program files\MyApp\Version1\dll;c:\program files\MyApp\Version1\app;
    
    
    
    c:\program files\MyApp\Version 2\app\
                                         myapp.exe
    c:\program files\MyApp\Version 2\dll\
                                          mycontrol.ocx
                                          one.dll
                                          two.dll
                                          three.dll
    
    
    // NOTICE THE VIRTUAL RENAME TO MYAPP2 in the line below
    HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\myapp2.exe 
          (default)=c:\program files\MyApp\Version 2\app\myapp.exe (type == REG_EXPAND_SZ)
          Path=c:\program files\MyApp\Version 2\dll;c:\program files\MyApp\Version 2\app;
    
  3. 在您的 EXE 和 DLLS 中使用 Windows Side by Side installation 和适当的清单。

【讨论】:

  • 关于 MFC:MFC 保存着一堆全局状态,这些状态需要在 EXE 和组成进程的 DLL 之间共享。将清单添加到 DLL 似乎使 MFC 将它们视为独立的,为它们中的每一个维护单独的“全局”状态,从而破坏任何依赖于共享状态的代码。
  • #1:遗憾的是,我确实需要控件才能在独立的 IE 中工作,并且“外部页面”和应用程序加载的页面之间没有区别。 #2:再次遗憾的是,在 Windows 7 之前,应用程序路径需要管理员权限才能修改,并且该应用程序需要可由非管理员用户安装。 #3:嗯,是的,这就是我想要做的。 :-)
  • 如果你这样做了。实现上面的#1,但不要在设置期间或在 HKLM 中执行此操作。当应用程序启动时,让它将 mycontrol.ocx 的 COM 注册 GUID 设置为指向“mycontrol.ocx”而没有特定路径 - 使用 HKCU\Software\Microsoft\Classes\CLSID。这将覆盖全局注册。当应用退出时,它会恢复注册表。
【解决方案3】:

我最后是这样解决这个问题的:

我将mycontrol.ocx 分成两部分,实现功能的mycontrol.dll 和实现注册逻辑并充当真实模块的垫片的最小mycontrol.ocx。 shim mycontrol.ocx 没有 DLL 依赖项。它的作用如下:

    1234563它。
  • 如果可执行文件旁边有 mycontrol.ocx 但没有 mycontrol.dll,则它必须是旧版本的 myapp.exe,因此 shim 会加载 mycontrol.ocx 并重定向到它。

  • 1234563 /li>

这比现实生活中要复杂一些,但结果是每个人都加载了他们期望加载的代码,并且一切正常。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-15
    • 2015-12-22
    • 1970-01-01
    • 2012-07-26
    • 2011-01-10
    • 1970-01-01
    • 1970-01-01
    • 2011-12-04
    相关资源
    最近更新 更多