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.
HHOOK journal_;
static LRESULT JournalCallback( int nCode, WPARAM wParam, LPARAM lParam )
{
if( HC_ACTION == nCode )
{
EVENTMSG* msg = reinterpret_cast< PEVENTMSG >( lParam );
if( msg )
{
CMessageQueue message_queue( JOURNAL_QUEUE_NAME,
0,
sizeof( EVENTMSG ),
FALSE,
MSGQUEUE_NOPRECOMMIT |
MSGQUEUE_ALLOW_BROKEN );
message_queue.WriteMsgQueue( msg, sizeof( EVENTMSG ), 0, 0 );
}
}
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 )
{
CMessageQueue message_queue( JOURNAL_QUEUE_NAME,
0,
sizeof( EVENTMSG ),
TRUE,
MSGQUEUE_NOPRECOMMIT | MSGQUEUE_ALLOW_BROKEN );
EVENTMSG evt = { 0 };
journal_ = ::QASetWindowsJournalHook( WH_JOURNALRECORD,
JournalCallback,
&evt );
DWORD bytes_read = 0;
DWORD flags = 0;
HANDLE handles[] = { message_queue, stop_event_ };
while( ::WaitForMultipleObjects( _countof( handles ),
handles,
FALSE,
INFINITE ) == WAIT_OBJECT_0 )
{
EVENTMSG msg = { 0 };
if( message_queue.ReadMsgQueue( &msg,
sizeof( EVENTMSG ),
&bytes_read,
0,
&flags ) )
{
MSG send = { msg.hwnd,
msg.message,
msg.paramH,
msg.paramL,
msg.time,
{ 0, 0 } };
callback( send );
}
else
{
break;
}
}
::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.
class JournalHook
{
public:
JournalHook( void ) : stop_event_( NULL, FALSE, FALSE, NULL )
{
};
typedef boost::function< void( const MSG& msg ) > OnJournalEvent;
void Start( OnJournalEvent callback )
{
stop_event_.ResetEvent();
journal_thread_.reset( new CThread(
boost::bind( &JournalHook::JournalThread, this, callback ) ) );
};
void Stop()
{
stop_event_.SetEvent();
journal_thread_->Join();
};
private:
static LRESULT JournalCallback( int nCode, WPARAM wParam, LPARAM lParam );
boost::shared_ptr< CThread > journal_thread_;
void JournalThread( OnJournalEvent callback );
CEvent stop_event_;
};
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:
- Start the active object and provide a callback function that will be activated when the journal hook API detects an event.
- 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. - 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" ) );
JournalHook hook_;
LRESULT CJournalHookDemoDialog::OnInitDialog( UINT ,
WPARAM ,
LPARAM ,
BOOL& bHandled )
{
hook_.Start( boost::bind( &CJournalHookDemoDialog::OnHookEvent, this, _1 ) );
return ( bHandled = FALSE );
}
void CJournalHookDemoDialog::OnHookEvent( const MSG& msg )
{
PostMessage( UWM_USER_INPUT, ( WPARAM )new MSG( msg ) );
}
LRESULT CJournalHookDemoDialog::OnUserInput( UINT ,
WPARAM wParam,
LPARAM ,
BOOL& )
{
std::auto_ptr< MSG > msg( reinterpret_cast< MSG* >( wParam ) );
switch( msg->message )
{
case WM_SYSKEYDOWN:
break;
case WM_SYSKEYUP:
break;
case WM_MOUSEMOVE:
break;
case WM_LBUTTONDOWN:
break;
case WM_LBUTTONUP:
break;
}
return 0;
}
LRESULT CJournalHookDemoDialog::OnClose( UINT ,
WPARAM ,
LPARAM ,
BOOL& bHandled )
{
hook_.Stop();
EndDialog( IDCANCEL );
return ( bHandled = FALSE );
}
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, have demonstrated how to overcome these challenges and made its basic usage clear.