Click here to Skip to main content
15,884,176 members
Articles / Desktop Programming / MFC
Article

Thunking MouseProc in IE add-in

Rate me:
Please Sign up or sign in to vote.
4.38/5 (7 votes)
3 Nov 20044 min read 70.4K   1K   41   9
An article on thunking MouseProc in IE add-in using BHO.

Introduction

Many IE add-ins are using Windows Hook mechanism to monitor message traffic for mouse messages and to perform their intended task related with mouse activities. Since the global hook slows down the system performance drastically, the thread-specific hook will be used in most cases.

IE, on the other hand, is a multi-threaded SDI application. That is, each IE instance will be running in its own thread under the same process address space as long as the new instance of IE is created from the same process address space. (You can still create a new IE in new process address space by double-clicking the IE icon in Desktop again, or run command prompt and type "IExplore.exe").

Now, take a look at MSDN to check the hook API function's prototype:

HHOOK SetWindowsHookEx(
  int idHook,        // hook type
  HOOKPROC lpfn,     // hook procedure
  HINSTANCE hMod,    // handle to application instance
  DWORD dwThreadId   // thread identifier
);

You can find that the third parameter of the function is thread identifier and it must be provided, otherwise it will monitor all existing threads running in the same desktop as the calling thread (and this is called as "global Windows hook", right?).

In order to install the mouse hook, we call ::SetWindowHookEx() API function and provide WH_MOUSE as hook type, and also provide the address of the global mouse hook procedure which is an application-defined or library-defined callback function, as shown below:

LRESULT CALLBACK MouseProc(
  int nCode,        // hook code
  WPARAM wParam,    // message identifier
  LPARAM lParam)    // mouse coordinates
{
    if (nCode == HC_ACTION)
    {
        if (lParam)
        {
            MOUSEHOOKSTRUCT *pMH = reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam);

            switch (wParam)
            {
            case WM_LBUTTONDOWN:
            case WM_MBUTTONDOWN:
            case WM_RBUTTONDOWN:
            case WM_LBUTTONDBLCLK:
            case WM_MBUTTONDBLCLK:
            case WM_RBUTTONDBLCLK:
            case WM_MOUSEWHEEL:
                // do something
                break;

            default:
                break;
            }
        }
    }

    return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

To make a window hook chain mechanism work correctly, you should call ::CallNextHookEx() API in MouseProc callback function that you provided, before or after processing your own task. Here, you can see that the first parameter of ::CallNextHookEx() API function is the handle to the current hook, and this is exactly the return value of ::SetWindowsHookEx() API.

LRESULT CallNextHookEx(
  HHOOK hhk,      // handle to current hook
  int nCode,      // hook code passed to hook procedure
  WPARAM wParam,  // value passed to hook procedure
  LPARAM lParam   // value passed to hook procedure
);

At this point, you will be able to see the necessity of the global map structure to find out the appropriate thread-specific HHOOK handle in global MouseProc callback function. Map is a good solution, and a hash map is even better since its look up cost is known to be O(1) at best. Therefore, I believe the most of IE add-ins will use map structure in this context.

But, isn't this very similar to WndProc and MFC's global HWND map structure situation? Maybe I can use ATL assembly thunking technique to improve performance as did ATL over MFC. Since WM_MOUSEMOVE message is one of the most frequent window message, even the minimum look up cost isn't that cheap to waste. To infinity and beyond :P

Implementation Note

I think you might be already tired with my bad English, so I will give you references here to help you understand of what the assembly thunk is and how it does its magic.

The core of my thunking implementation is shown below:

ASM
mov    eax, dword ptr [esp+0Ch]            // 8B 44 24 0C
mov    [pThis->lParam], eax                // A3 [DWORD pThis->lParam]
mov    dword ptr [esp+0Ch], [pThis]        // C7 44 24 0C [DWORD pThis]
jmp    [MouseProc addr]                    // E9 [DWORD MouseProc addr]

I changed the MouseProc to accept the pointer to C++ class (CMouseProcHook class) as a parameter instead of LPARAM. When BrowserHelperObject gets connected through the call to IObjectWithSite::SetSite(), the handle to the hook for the thread-specific IE from ::SetWindowsHookEx() API function is stored in the C++ object, and it will cache the this pointer in some well-known place, then install Windows hook with a special 'start-up' mouse hook procedure. In the 'start-up' mouse hook procedure, I crate a sequence of assembly language instructions (the thunk) that replace the LPARAM parameter of the mouse hook procedure with the physical address of the this pointer (retrieved from the well-known place), and then jumps to the 'real' mouse hook procedure with the altered stack. In 'real' mouse hook procedure, we grab the C++ class by simply casting the LPARAM parameter into the CMoudeProcHook, and get the 'real' LPARAM as well as the handle to thread-specific hook. The picture below shows how the thunk alters the call stack and then forwards the call.

Image 1

The LPARAM of the MouseProc was found to located at esp + 0Ch, and I was able to double-check this by setting up the breakpoint in the first line of MouseProc code and enabling Disassembly Debug Windows (ALT+8).

Image 2

Image 3

Now, new MouseProc will be updated as shown below:

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    CMouseProcHook *pThis = reinterpret_cast<CMouseProcHook *>(lParam);
    lParam = pThis->GetLPARAM();

    if (nCode == HC_ACTION)
    {
        if (lParam)
        {
            MOUSEHOOKSTRUCT *pMH = reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam);

            switch (wParam)
            {
            case WM_LBUTTONDOWN:
            case WM_MBUTTONDOWN:
            case WM_RBUTTONDOWN:
            case WM_LBUTTONDBLCLK:
            case WM_MBUTTONDBLCLK:
            case WM_RBUTTONDBLCLK:
            case WM_MOUSEWHEEL:
                // do something
                break;

            default:
                break;
            }
        }
    }

    return ::CallNextHookEx(pThis->GetHHOOK(), nCode, wParam, lParam);
}

The last thing I should mention is a map structure used in my codes. The map is only here to avoid a multiple hook installation and to remove an already installed hook procedure from a hook chain. And the rest of the story goes the same as WndProc thunk case. Refer to source code for details.

Using code

When you compile the source code, it will automatically register the output DLL file in BrowserHelperObject section of Window Registry. And then you can run an IE, and make a click, or double-click any mouse button on the IE client area, which will simply display the pre-defined message in IE's status bar. When you finish the testing, you can merge the included registry file ("DelBHO.reg") to remove and clean up the registered BrowseHelperObject entry from the Windows Registry manually.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Other
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionsomething for 64 bits? Pin
umeca749-Jan-10 20:23
umeca749-Jan-10 20:23 
GeneralExe for Thunking Mouse Click Pin
djshuman31-Jul-06 13:58
djshuman31-Jul-06 13:58 
Generalhi~ Pin
byun doo ri15-Dec-05 19:12
byun doo ri15-Dec-05 19:12 
GeneralOne Event Pin
the niros2-Feb-05 3:43
the niros2-Feb-05 3:43 
GeneralKeyboard thunk Pin
Kalyan Surampalli13-Dec-04 9:27
sussKalyan Surampalli13-Dec-04 9:27 
GeneralRe: Keyboard thunk Pin
JaeWook Choi15-Dec-04 17:02
JaeWook Choi15-Dec-04 17:02 
GeneralRe: Keyboard thunk Pin
Jerry Hahn15-Feb-06 18:16
Jerry Hahn15-Feb-06 18:16 
I agree with Kalyan, excellent article in an area where I have personally experienced problems. Kalyan, I did not know your were branching into such areas these days. Let me know where you are now. Hope you are well.

Jerry Hahn (jhahnemail@yahoo.com)
GeneralTLS Pin
c2j216-Nov-04 0:04
c2j216-Nov-04 0:04 
GeneralRe: TLS Pin
JaeWook Choi16-Nov-04 16:41
JaeWook Choi16-Nov-04 16:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.