0
votes

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.

3
#2 above makes the most sense, but I don't know what you mean by "MFC now treats each DLL as it's own activation context and that's not right". What is an Activation Context? Why does it matter?selbie

3 Answers

1
votes

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 ...

1
votes

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-version myapp.exe and the shim loads that mycontrol.dll and reflects DllGetClassObject and DllCanUnloadNow calls to it.

  • If there's a mycontrol.ocx but no mycontrol.dll next to the executable, this must be an old-version myapp.exe, so the shim loads up that mycontrol.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.

0
votes

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:

  1. 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.

  2. 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;
    
  3. Use Windows Side by Side installation and appropriate manifests in your EXEs and DLLS.