Short version :如何通过Internet Explorer将MFC ActiveX控件加载到网页中,保证其关联的DLL是从其自己的目录加载的,而不是选择可能已经加载到进程中的同名DLL?
Long version, with gory detail :我有一个使用一组DLL的应用程序 myapp.exe
: one.dll
, two.dll
和 three.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
,而不是加载版本2 one.dll
. 这是失败的,因为 mycontrol.ocx
的版本2使用版本1中没有的 one.dll
版本2中的新API .
How do I stop it doing that? 如果 one.dll
尚未加载,Windows会查找 c:\Program Files\MyApp\Version 2
,一切都会好的 . (而且我无法为每个版本的软件重命名我的所有模块!)
Failed solution #1 :我给了 mycontrol.ocx
一个清单,为所有DLL指定 <file ...>
元素,但这不起作用 . one.dll
从 Version 2
目录加载,但 two.dll
没有 . 我假设这是因为 two.dll
正在加载 one.dll
引用它,而 one.dll
没有这样的清单 .
Failed solution #2 :所以我在 all 中添加了一个清单,每个清单都列出了 <file ...>
元素中的依赖关系 . 但这完全破坏了 myapp.exe
,因为MFC现在将每个DLL视为拥有自己的激活上下文并且这是不正确的 - 整个过程(在 myapp.exe
的情况下)应该有一个MFC上下文或控件的整个实例(在案件 mycontrol.exe
) . 我不能为每个模块提供新的激活上下文;我需要一个整个 mycontrol.ocx
及其附带的DLL .
我更改了所有清单以使用相同的 name=
属性,希望这会将它们全部放在同一个上下文中,但这没有任何效果 .
Failed solution #3 :我给 myapp.exe
一个表示 <file name="mycontrol.ocx"/>
的清单,希望强制该应用程序的版本1使用控件的版本1,这没关系 . 但这失败了因为当Internet Explorer加载 mycontrol.ocx
时,它使用 mycontrol.ocx
版本2的完整路径调用 LoadLibrary
,并且当使用完整路径加载模块时,清单重定向不起作用 .
Something I can't do :我可以't change the code that loads the control, because it'的Internet Explorer这样做(通过MIME类型) .
我们将非常感激地提出任何解决方案,建议或简单的同情信息 .
3 回答
尝试的一个选项可能是将OCX的DLL依赖项指定为延迟加载,然后编写您自己的延迟加载辅助函数,该函数使用LoadLibraryEx()确保从您要使用的路径加载DLL . 我在过去已经尝试过这个并使用可执行文件,但我不明白为什么它不适用于OCX .
MSDN在using the delay loading helper function上有很好的文档 . 在延迟加载辅助函数中,您希望专注于"dliNotePreLoadLibrary"情况,并返回使用LoadLibraryEx()获得的正确DLL的HMODULE .
顺便说一句,最后我没有使用这种方法:我只是确保所有的DLL都有版本号 . 最后,这是最简单的方法......
知道一下DLL地狱,我对“MFC激活上下文”一无所知 . 这就是为什么你上面的#2想法看起来很稳固的原因 .
但是,如果这不起作用,我会尝试和调查三种可能的解决方案:
疯狂的想法 . 当您在注册表中注册“mycontrol.ocx”作为与application / myapp关联时,您目前已通过它的FULL路径注册(c:\ program files \ app \ version2 \ mycontrol.ocx“) . 只需将其注册为”mycontrol.ocx“,不指定目录 . 如果幸运,IE控件将”LoadLibrary (“mycontrol.ocx”)并从与EXE相同的目录中找到它 . 如果您确实需要mycontrol.ocx在IE的独立实例中加载,这当然会中断 . 但也许您可以为外部页面使用备用MIME类型(或com guid)来直接加载控件 .
在应用程序的安装中,将EXE放在与DLL不同的目录中 . 然后使用the "AppPath" registry key将特定的DLL目录虚拟添加到应用程序的路径中 . 唯一的问题是我认为您不能使用完全限定的路径设置AppPath密钥名称 . 但我知道你可以拥有一个EXE名称,其中包含一个指向不同名称的EXE的完全限定路径 . 所以我们可以"virtually rename" "myapp.exe"的第二个实例"myapp2.exe" . 在桌面快捷方式和开始菜单入口点中,它们都会启动"myapp2.exe",但会重定向到"version2\app\myapp.exe"
Here's how I solved this in the end:
我将
mycontrol.ocx
分成两部分,mycontrol.dll
实现了功能,最小的mycontrol.ocx
实现了注册逻辑,并充当真实模块的垫片 . 垫片mycontrol.ocx
没有DLL依赖项 . 这是它的作用:如果启动进程的可执行文件旁边有
mycontrol.dll
,则必须是新版本myapp.exe
,并且shim加载mycontrol.dll
并反映DllGetClassObject
和DllCanUnloadNow
调用 .如果可执行文件旁边有
mycontrol.ocx
但没有mycontrol.dll
,则必须是旧版本myapp.exe
,因此垫片会加载mycontrol.ocx
并重定向到该版本 .如果两个模块都不存在,则必须是Web浏览器或其他一些ActiveX主机,因此填充程序会加载与自身位于同一目录中的
mycontrol.dll
,并重定向到该模块 .它比现实生活中的复杂一点,但结果是每个人都加载了他们期望加载的代码,而且一切正常 .