Click here to Skip to main content
Click here to Skip to main content

Using Input Hooks in Windows Mobile

, 28 Oct 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
A look at the undocumented message hook functions for Windows Mobile
WM-Input-Hooks/JournalHook1.png WM-Input-Hooks/JournalHook2.png

Contents

Introduction

In this article we will explore the undocumented and unsupported Hook API for Windows Mobile. This API allows our application to receive input messages from the system even if it doesn't have input focus. This feature can be very useful for logging keystrokes, mouse movements, and macro recording. These functions can only be used to receive input messages, they cannot be used to block or alter those messages.

The attached sample application uses WTL and Boost. I find these libraries dramatically improve the quality of my code. If you don't use them (and you really should!) the concepts presented in this article will still apply.

The QA Journal Hook API

The Journal Hook API functions used by this article are defined in pwinuser.h which is supplied as part of platform builder. If you don't have platform builder, you can find their definitions easily enough by some quick Internet searching. Though Microsoft does document message hook functions for Windows, there is no official documentation for this mobile version of the hook API even with platform builder.

  • HOOKPROC - The callback procedure activated by the hook mechanism when a user input event is detected. This procedure is called by GWES.exe, not your application's process. This means we cannot set breakpoints in this function to debug it nor can we use NKDbgPrintfW and see debug information in the output window.
  • CallNextHookEx - Allow the OS to process any other application's hooks. This is called from within HOOKPROC.
  • QASetWindowsJournalHook - Activate the hook mechanism.
  • QAUnhookWindowsJournalHook - Deactivate the hook mechanism. It is very important that this be called when you're done with the hook. If it isn't properly called, you will likely have to restart your Windows Mobile device.

The HOOKPROC callback

We begin with an implementation of the HOOKPROC callback function.

Because the HOOKPROC callback function is called by another process (GWES.exe), we must use an interprocess communications method to get the message data from that procedure back to our process. For this example, we will use the Message Queue. When an EVENTMSG is received by the HOOKPROC, we will open a named queue and write that message to it.

When we're finished with the message, we invoke CallNextHookEx to allow other hooks to process the message. Altering the contents of the message sent to CallNextHookEx will only affect information sent to other hooks. We cannot change or block the input given to the application the user is interacting with.

/// global journal handle.
HHOOK journal_;
 
static LRESULT JournalCallback( int nCode, WPARAM wParam, LPARAM lParam )
{
    if( HC_ACTION == nCode )
    {
        EVENTMSG* msg = reinterpret_cast< PEVENTMSG >( lParam );
        if( msg )
        {
            // We've received a message; put it on the message queue.
            CMessageQueue message_queue( JOURNAL_QUEUE_NAME, 
                                         0, 
                                         sizeof( EVENTMSG ), 
                                         FALSE, 
                                         MSGQUEUE_NOPRECOMMIT | 
                                         MSGQUEUE_ALLOW_BROKEN );
            message_queue.WriteMsgQueue( msg, sizeof( EVENTMSG ), 0, 0 );
        }
    }
    
    // tell the OS to continue processing other hooks.
    return ::CallNextHookEx( journal_, nCode, wParam, lParam );
}

The message processing thread

When invoked, this thread will listen for messages sent to the journal event message queue and activate a callback function to alert the user class that a new event is available for processing. With this method, we take the messages from the hook process and make them safely available for our application.

void JournalHook::JournalThread( OnJournalEvent callback )
{
    // listen on our interprocess message queue for hooked events
    CMessageQueue message_queue( JOURNAL_QUEUE_NAME, 
                                 0, 
                                 sizeof( EVENTMSG ), 
                                 TRUE, 
                                 MSGQUEUE_NOPRECOMMIT | MSGQUEUE_ALLOW_BROKEN );
 
    // initialize the journal hook
    EVENTMSG evt = { 0 };
    journal_ = ::QASetWindowsJournalHook( WH_JOURNALRECORD, 
                                          JournalCallback, 
                                          &evt );
 
    DWORD bytes_read = 0;
    DWORD flags = 0;
    HANDLE handles[] = { message_queue, stop_event_ };
 
    // listen for journal events from JournalCallback. 
    while( ::WaitForMultipleObjects( _countof( handles ), 
                                     handles, 
                                     FALSE, 
                                     INFINITE ) == WAIT_OBJECT_0 )
    {
            EVENTMSG msg = { 0 };
            if( message_queue.ReadMsgQueue( &msg, 
                                            sizeof( EVENTMSG ), 
                                            &bytes_read, 
                                            0, 
                                            &flags ) )
            {
                // We received an event. Package it up and alert the user.
                MSG send = { msg.hwnd,
                             msg.message,
                             msg.paramH,
                             msg.paramL,
                             msg.time,
                             { 0, 0 } };
                callback( send );
            }
            else
            {
                // we failed to read from the message queue. exit.
                break;
            }
    }
 
    // cleanup the journal hook. 
    ::QAUnhookWindowsJournalHook( WH_JOURNALRECORD );
    journal_ = NULL;
}

Pulling it all Together

With the difficult parts out of the way, we can now show a simple active-object implementation that will allow the using class access to user input events even if they're directed at another application.

/// Active object that alerts its user of any user input events regardless of 
/// which process has input focus.
class JournalHook
{
public:
    JournalHook( void ) : stop_event_( NULL, FALSE, FALSE, NULL )
    {
    };
 
    /// prototype for the function called when a journal event occurs
    /// @param MSG - windows user input message received
    typedef boost::function< void( const MSG& msg ) > OnJournalEvent;
 
    /// start listening for journal events
    /// @param OnJournalEvent - activated when an event occurs
    void Start( OnJournalEvent callback )
    {
        stop_event_.ResetEvent();
        journal_thread_.reset( new CThread( 
            boost::bind( &JournalHook::JournalThread, this, callback ) ) );
    };
    
    /// stop listening for journal events
    void Stop()
    {
        stop_event_.SetEvent();
        journal_thread_->Join();
    };
    
private:
 
    /// journal callback function activated by GWES.exe
    static LRESULT JournalCallback( int nCode, WPARAM wParam, LPARAM lParam );
 
    /// thread handle that listens for journal events
    boost::shared_ptr< CThread > journal_thread_;
 
    /// This class' thread function that listens for journal event messages
    /// @param OnJournalEvent - activated by this function when an event occurs.
    void JournalThread( OnJournalEvent callback );
 
    /// event signaled when we should stop listening for events
    CEvent stop_event_;
 
}; // class JournalHook

Using the API in an application

Our journal hook active object takes care of the difficult parts of using the journal hook API. Its usage in an application is very simple:

  1. Start the active object and provide a callback function that will be activated when the journal hook API detects an event.
  2. In the callback function, process the provided MSG just as if it were any other windows message. Because our example includes a user interface, we use the PostMessage API to return to the UI thread before modifying any dialog controls.
  3. Remember to stop the active object when we exit our application. This ensures the hook API is properly cleaned up.

static const UINT UWM_USER_INPUT = ::RegisterWindowMessage( _T( "UWM_USER_INPUT" ) );
 
/// Our journal hook active object
JournalHook hook_;
 
/// WM_INITDIALOG message handler
LRESULT CJournalHookDemoDialog::OnInitDialog( UINT /*uMsg*/, 
                                              WPARAM /*wParam*/, 
                                              LPARAM /*lParam*/, 
                                              BOOL& bHandled )
{
    // perform dialog initialization...
    
    // start listening for events from the journal hook API
    hook_.Start( boost::bind( &CJournalHookDemoDialog::OnHookEvent, this, _1 ) );
    return ( bHandled = FALSE );
}
 
/// JournalHook callback function activated when a user input event occurs.
void CJournalHookDemoDialog::OnHookEvent( const MSG& msg )
{
    // We received a user-input notification from the Journal Hook. Get back to 
    // our UI thread context to update the UI.
    // We use PostMessage() rather than the simpler SendMessage() to avoid a
    // deadlock if the user calls JournalHook::Stop() while we're processing
    // this message.
    PostMessage( UWM_USER_INPUT, ( WPARAM )new MSG( msg ) );
}
 
/// UWM_USER_INPUT message handler
LRESULT CJournalHookDemoDialog::OnUserInput( UINT /*uMsg*/, 
                                             WPARAM wParam, 
                                             LPARAM /*lParam*/, 
                                             BOOL& /*bHandled*/ )
{
    std::auto_ptr< MSG > msg( reinterpret_cast< MSG* >( wParam ) );
    switch( msg->message )
    {
    case WM_SYSKEYDOWN:
        // the user pressed a physical key
        break;
 
    case WM_SYSKEYUP:
        // the user released a physical key
        break;
 
    case WM_MOUSEMOVE:
        // the user moved the stylus
        break;
 
    case WM_LBUTTONDOWN:
        // the user pressed the stylus to the screen
        break;
 
    case WM_LBUTTONUP:
        // the user lifted the stylus off the screen
        break;
    }
 
    // display some information in our UI.
    return 0;
}
 
/// WM_CLOSE message handler
LRESULT CJournalHookDemoDialog::OnClose( UINT /*uMsg*/, 
                                         WPARAM /*wParam*/, 
                                         LPARAM /*lParam*/, 
                                         BOOL& bHandled )
{
    hook_.Stop();
    EndDialog( IDCANCEL );
    return ( bHandled = FALSE );
}

Conclusion

The undocumented nature of the Windows Mobile QA Journal Hook API presents us with several implementation challenges. I hope this article, and its accompanying demonstration code, has demonstrated how to overcome these challenges and made its basic usage clear.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Paul Heil
Software Developer (Senior) An engineering firm in Cedar Rapids, Iowa
United States United States
I'm also on the MSDN forums
http://social.msdn.microsoft.com/profile/paulh79

Comments and Discussions

 
GeneralMy vote of 5 PinmemberMorries27-Aug-13 1:25 
GeneralMy vote of 5 PinmemberMember 78703855-May-11 23:43 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411022.1 | Last Updated 28 Oct 2010
Article Copyright 2010 by Paul Heil
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid