Nerdworks logo "The nerd shall inherit the earth."

Nerdworks Blogorama

Nerdspeak

Windows hooks & call sequence
Technobabble
7/6/2006 9:19:58 PM

I ran into an interesting little issue at work the other day. There is this program that I am working on which happens to embed Office documents into a browser control hosted in a CHtmlView derived view window (that's right, we use MFC). The requirement was to popup a little toolbar with a save button whenever the user did some in-place editing on whatever's displayed in the browser control. We already knew how to detect whether at a given point in time the document currently displayed in the browser has been edited. This was done in this manner (error checks omitted for brevity):

bool IsDirty()
{
    //
    // get hold of the html view object somehow
    //
    CHtmlView *pView = GetViewSomehow();

    //
    // get a pointer to the HTML document object
    //
    CComPtr<IDispatch> spDocument( pView->GetHtmlDocument() );

    //
    // turns out that you can QI on the doc object to
    // get a "IPersistStorage" object which is directly linked
    // to whatever happens to be embedded in the browser
    //
    CComPtr<IPersistStorage> spStorage;
    HRESULT hr = spDocument->QueryInterface( IID_IPersistStorage, (void **)&spStorage );

    //
    // now we just call "IPersistStorage::IsDirty" to figure
    // out whether the doc's been edited
    //
    hr = spStorage->IsDirty();
    return ( hr == S_OK );
}

The question of course was to figure out when this routine will be invoked so we can display the toolbar. We first came up with an approach that involved continuously polling for changes by making some creative use of CWinApp::OnIdle. But that resulted in short bursts of CPU usage spikes and while the solution worked, it somehow didn't feel right!

The next thing we tried was to see if Windows hooks can be put to some use here. We quickly set up a system-wide keyboard and mouse hook which "phoned home" so to speak whenever an event occurred by posting a custom message to a window. Whenever this message is received by the window it would call our clever little IsDirty routine to check if the document has been modified and get the toolbar displayed if need be. I let out a satisfied little burp at this point and compiled, linked and hit Ctrl+F5.

I loaded up a PowerPoint file into the browser control and pressed a few keys and whopeeee(!) the toolbar appeared straightaway. On doing some additional testing however I discovered that the damn thing showed up only with the second key press and not immediately after the first one! As it turns out, Windows delivers messages to hooks before they are delivered to the target application. So my hook was getting the keyboard event before PowerPoint was getting it and the call to IsDirty was consequently returning false as PowerPoint hadn't had a chance to mark the file as having been modified yet.

This thing drove us a little nuts until of course we figured a way out. I even made a newsgroup post on this issue (with no response by the way). The solution in the end turned out to be quite simple.

Well-behaving hooks are required to call the CallNextHookEx function before returning from the hook routine. This is to let other hooks that are installed on the system have a go at the message. I had done a PostMessage to the application window before calling CallNextHookEx like so:


PostMessage( hwndNotify, WM_HOOK_NOTIFY_MOUSE, 0, 0 );
return CallNextHookEx( NULL, nCode, wParam, lParam );

I made a small change to the order of invocation in this manner:


LRESULT lResult = CallNextHookEx( NULL, nCode, wParam, lParam );
PostMessage( hwndNotify, WM_HOOK_NOTIFY_MOUSE, 0, 0 );
return lResult;

And voila! it started working! It must be pretty evident what the issue was by just looking at the change that was done. Looks like CallNextHookEx, apart from calling other hooks that may have been installed also actually delivers the message to the target application before returning. In this case, this was just what the doctor ordered :)!

Cool eh?!

 

Please fill this form and click on the "Submit" button to post a comment. All fields except the comment box are optional. You don't have to give me your name and email, but if you do then that might allow me to follow up with you on your comment. Also, I won't publish your email address here or anywhere else.

 
Your Name :
Your Email :
Your Comment :
   

What in your opinion do you get when you multiply the number 5 by the number 2?

Your answer will help me figure out whether you are human or a spam bot. If you're a spam bot I hope your kernel core dumps and your CPU bursts into flames.

   

Please click here to go back to the blog.

blogorama home
about this blog
email the author
where on earth am i?
subscribe to mailing list
feeds Use these links for feed syndication
rss  |  atom
by category
technobabble (54)
philosophical crud (3)
irrelevant stuff (7)
archive
september, 2011 (7)
july, 2011 (3)
june, 2011 (2)
may, 2011 (3)
april, 2011 (1)
march, 2011 (1)
february, 2011 (1)
february, 2010 (1)
october, 2009 (1)
september, 2009 (1)
july, 2009 (5)
march, 2009 (2)
august, 2008 (2)
march, 2008 (1)
january, 2008 (1)
september, 2007 (2)
april, 2007 (1)
february, 2007 (2)
december, 2006 (1)
october, 2006 (1)
september, 2006 (4)
august, 2006 (3)
july, 2006 (4)
june, 2006 (3)
may, 2006 (6)
april, 2006 (2)
recent entries
IE9 web cast / Chen...
Partial function ap...
Web Camps, Virtual...
176873 hits