Prevent MFC ActiveX control from using DLLs that are already loaded into the process
Short version: How can an MFC ActiveX control loaded into a web page by Internet Explorer guarantee that its associated DLLs are loaded from its own directory, rather than picking up identically-named DLLs that might already be loaded into the process?
Long version, with gory detail: I have an application myapp.exe
that uses a set of DLLs: one.dll
, two.dll
and three.dll
.
The same DLLs are also used by an MFC ActiveX control that exposes some of the same functionality as myapp.exe
, so there's a mycontrol.ocx
that also links with those DLLs. The ActiveX control is associated with the application/myapp
MIME type, so that IE will use it to display documents created with myapp.exe
.
It's possible to have both versions 1 and 2 of myapp.exe
installed, but only the most recent version of mycontrol.ocx
(version 2) is associated with the application/myapp
MIME type:
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
Here's where it it gets difficult: myapp.exe
has an embedded Internet Explorer control for displaying web content. You can run version 1 of myapp.exe
, point that embedded Internet Explorer at an application/myapp
document, and IE will load up version 2 of mycontrol.ocx
to view it. This ought to be fine, but it's not:
What happens is that Windows loads mycontrol.ocx
, sees that it has a dependency on one.dll
and that there's already a one.dll
in the process, and points the import table of mycontrol.ocx
with the already-loaded (version 1) one.dll
, rather than loading version 2 of one.dll
. This fails because version 2 of mycontrol.ocx
uses new APIs in version 2 of one.dll
that weren't there in version 1.
How do I stop it doing that? If one.dll
weren't already loaded, Windows would look in c:\Program Files\MyApp\Version 2
for it, and all would be well. (And开发者_运维知识库 I can't rename all my modules for every version of the software!)
Failed solution #1: I gave mycontrol.ocx
a manifest that specifies <file ...>
elements for all the DLLs, but that doesn't work. one.dll
gets loaded from the Version 2
directory, but two.dll
doesn't. I assume this is because two.dll
is being loaded as a result of one.dll
referencing it, and one.dll
doesn't have such a manifest.
Failed solution #2: So I added a manifest to all the DLLs, each listing its dependncies in <file ...>
elements. But that broke myapp.exe
completely, because MFC now treats each DLL as having its own activation context and that's not right - there should be one MFC context for the whole process (in the case of myapp.exe
) or the whole instance of the control (in the case of mycontrol.exe
). I can't have a new activation context for each module; I need one for the whole of mycontrol.ocx
and its attendant DLLs.
I changed all the manifests to use the same name=
attribute, in the hope that that would put them all into the same context, but that had no effect.
Failed solution #3: I gave myapp.exe
a manifest that said <file name="mycontrol.ocx"/>
in the hope of forcing version 1 of the app to use version 1 of the control, which would be fine. But that fails because when Internet Explorer loads mycontrol.ocx
it calls LoadLibrary
with the full path to version 2 of mycontrol.ocx
, and manifest redirection doesn't work when modules are loaded using a full path.
Something I can't do: I can't change the code that loads the control, because it's Internet Explorer doing that (via the MIME type).
Any solutions, suggestions or simple messages of sympathy will be gratefully received.
One option to try might be to specify the OCX's DLL dependencies as delay loaded, and then write your own delay-load helper function that used LoadLibraryEx() to be certain of loading the DLLs from the path you want to use. I have experimented with this in the past and got it to work with an executable, but I don't see why it wouldn't work with an OCX.
MSDN has decent documentation on using the delay loading helper function. In the delay load helper function you want to concentrate on the "dliNotePreLoadLibrary" case, and return the HMODULE of the right DLL that you've got with LoadLibraryEx().
By the way, in the end I didn't use this approach: I just made sure all the DLLs have the version number in them. In the end, it's the easiest way ...
Here's how I solved this in the end:
I split mycontrol.ocx
into two pieces, mycontrol.dll
that implements the functionality, and a minimal mycontrol.ocx
that implements the registration logic and acts as a shim to the real module. The shim mycontrol.ocx
has no DLL dependencies. Here's what it does:
If there's a
mycontrol.dll
next to the executable that started the process, this must be a new-versionmyapp.exe
and the shim loads thatmycontrol.dll
and reflectsDllGetClassObject
andDllCanUnloadNow
calls to it.If there's a
mycontrol.ocx
but nomycontrol.dll
next to the executable, this must be an old-versionmyapp.exe
, so the shim loads up thatmycontrol.ocx
and redirects to that.If neither module exists, this must be a web browser or some other ActiveX host, so the shim loads up the
mycontrol.dll
that's in the same directory as itself, and redirects to that.
It's a bit more complex than that in real life, but the result is that everybody loads the code they were expecting to load, and it all works.
Knowing a bit about DLL hell, I don't know anything about "MFC Activation Context". That's why your #2 idea above seems solid.
But if that doesn't work you, three possible solutions I would try and investigate:
Crazy idea. When you register "mycontrol.ocx" in the registry as being associated with application/myapp, you currently have it registered by it's FULL path (c:\program files\app\version2\mycontrol.ocx"). Just register it as "mycontrol.ocx" with no directory specified. If you're lucky, IE control will "LoadLibrary("mycontrol.ocx") and find it from the same directory as your EXE. This of course breaks if you actually need mycontrol.ocx to load within a standalone instance of IE. But perhaps you could have an alternate MIME type (or com guid) for external pages to load the control directly.
In your application's installation, put the EXE in a different directory than the DLLs. Then use the "AppPath" registry key to virtually add the specific DLL directory to the path of the app. The only problem is that I don't think you can set an AppPath key name with a fully qualified path. But I do know you can have an EXE name with a fully qualified path to a differently named EXE. So we can "virtually rename" the second instance of "myapp.exe" as "myapp2.exe". In your desktop shortcuts and start menu entry points, they all launch "myapp2.exe", but that gets redirected to "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;
Use Windows Side by Side installation and appropriate manifests in your EXEs and DLLS.
精彩评论