IEProcess.h - IE inproc APIs

IE8 beta1 introduced Loosely Coupled IE (LCIE) which gives tab a level of process isolation from the frame: https://blogs.msdn.com/ie/archive/2008/03/11/ie8-and-loosely-coupled-ie-lcie.aspx

For the most part this works pretty well.  But certain binary extensions (activeX objecst, BHOs, etc) that are already deployed have assumptions about window hierarchy and process model.  To get this to work the IE team has been diligently testing extensions and finding ways to fix them.  For the most part we do this by using the Detours library to redirect Windows APIs to private wrappers.  We have been using this technique since we added tabs.  For tabs we were just avoiding tabs contending for window modality by calling functions like MessageBox().  Our hook would effectively created a queue of modal dialogs avoiding confusing dialogs from hidden tabs, etc. 

But not all problems can’t be fixed by just detouring an existing API this way.  A reasonable example is when an application has a custom modal dialog manager.  This means that our hooks on DialogBoxParam() and related functions won’t be hit.  Other hooking options are complex and fragile so we opted to expose the "right" way for external developers.  IEProcess.h exposes functions for helping with this: https://msdn.microsoft.com/en-us/library/cc994365(VS.85).aspx

This API was designed with the idea that many components that are loaded in IE can actually be loaded elsewhere as well.  So instead of linking to IE dlls directly, some level of indirection is required.  This avoids the cost of loading a DLL when it won’t be used, and allows the IE dev team a little flexibility for how we expose these APIs.  First a caller detects whether any IEProcess exports with the following code:

     HMODULE ieModule = IEProcess:GetProcessModule(); 
    //    if ieModule != NULL then there are exports available

After retrieving the module, the methods are grouped similar to COM interfaces, but are static, and exist for the lifetime of the process.  This is similar to doing GetProcAddress() for a set of functions:

     static const IETabWindowExports* ieExports = GetTabWindowExports(ieModule);
    //   ieExports contains the methods we need to call

Now if you look here https://msdn.microsoft.com/en-us/library/cc994365(VS.85).aspx again, you will see how we implemented our detour of DialogBoxParamW(), and this can be fairly easily replicated to a custom dialog manager.  WaitForTabWindow() queues your modal call with the tab; when it returns, its your turn.  Then take the lock with AcquireModalDialogLockAndParent(), which will create an appropriate top level window if required for security boundary reasons.  Then show the modal dialog, and call ReleaseModalDialogLockAndParent() when done.  It’s that easy!

This is super straightforward if you always run in IE, but looks a little awkward to handle the error conditions if you run outside IE.  So I wrote a wrapper class that does pass through logic when running outside of IE.  It has an H and CPP file.  This is how MSHTML does its IE synchronization for HTML Dialogs, which have a custom dialog manager.  The header is below, and the CPP is attached.

 #pragma once

/// 
/// Helper class that does delay load binding to the IEProcess
/// export tables with fallback logic for easy pass through.
/// 
class IEProcessHelper
{
public:

    /// 
    /// Detect whether per-process exports are enabled for this process.
    /// This does it's binding on demand and the result is cached.
    /// 
    static bool IsIEProcess();    

    /// 
    /// Retrieve the tab window related exports for this process.
    /// If they aren't available it returns a table of fallbacks 
    /// with pass through logic so that this never returns NULL.
    /// This does it's binding on demand and the result is cached.
    /// 
    static const IETabWindowExports* TabWindow();

private:
    
    static HRESULT WaitForTabWindowNotImpl(
        __in    bool    allowUnknownThread,  
        __in    HWND    hwndParentProposed,  
        __out   HWND*   phwndParentActual);  

    static HRESULT AcquireModalDialogLockAndParentNotImpl(
        __in        HWND    hwndParentProposed,  
        __out       HWND*   phwndParentActual,
        __deref_out HANDLE* phModalDialogLock);

    static void ReleaseModalDialogLockAndParentNotImpl(
        __in_opt HANDLE hModalDialogLock);

private:
    static const IETabWindowExports _TabWindowExportsNotImpl;
    static const IETabWindowExports* _TabWindowExports;
    static HMODULE _hmoduleIEProcess;
    static bool    _checkedIEProcess;
};

This simplifies our detour so that the following is the new calling pattern, and doesn’t require any kind of fallback logic.

 INT_PTR WINAPI Detour_DialogBoxParamW(
        HINSTANCE hInstance, LPCWSTR lpTemplateName, 
        HWND hwndParent, DLGPROC lpDialogFunc, LPARAM dwInitParam)
{
    INT_PTR nRes = -1;

    HRESULT hr = IEProcessHelper::TabWindow()->WaitForTabWindow(
            false, hwndParent, &hwndParent);
    if (SUCCEEDED(hr))
    {
        HANDLE hModalDialogLock;
        hr = IEProcessHelper::TabWindow()->AcquireModalDialogLockAndParent(
                hwndParent, &hwndParent, &hModalDialogLock);
        if (SUCCEEDED(hr))    
        {
            nRes = Real_DialogBoxParamW(hInstance, lpTemplateName, 
                    hwndParent, lpDialogFunc, dwInitParam);

            IEProcessHelper::TabWindow()->ReleaseModalDialogLockAndParent(
                    hModalDialogLock);
        }
    }
    
    return nRes;
} 

Check out MSDN related docs for running in Protected Mode: https://msdn.microsoft.com/en-us/library/bb250462.aspx

 

 

IEProcessHelper.cpp