SetWindowsHookEx on Windows Mobile

  • "supported"??
  • An interesting API: GetForegroundKeyboardTarget

Sometimes cases arrive to my desk with issues regarding the usage of SetWindowsHookEx() API on Windows Mobile. As per Kiosk-applications, we should simply reject such requests, because the developer is asking about something "unsupported" -- that API is not documented on the Windows CE or the Windows Mobile SDK Documentation. It's the usual story: "technically achievable" doesn't mean "supported". In this case it's even worse because SetWindowsHookEx is a Private API and it's even not documented in the OEM Adaptation Kit's Documentation (which is different from the doc for ISV Developers coming with the SDKs and it's available to OEMs only, as they need to know internal details about the platform they're adapting for their hardware basically). This means that the Dev Team can change coredll.dll at any point in time by for example no longer exposing that API without breaking any backward-compatibility... I remember a post by Daniel Moth about using undocumented features... yes, here it is: #NETCF_AGL_ (cit.: "The moral of the story is, if you do take advantage of undocumented features, your application may break when you move it to the next version regardless of the compatibility efforts of the product teams at MSFT. ")

So, even if on the web you'll find many ways to hook keyboards or windows-procedure (and even a NETCF wrapper done by Alex Yakhnin), you should use them with care and consider that it may also be that new images of the OS may not support it. If you really want to use (read: "if you can't find alternatives") on Windows Mobile, at least make sure you sign the executable with a certificate registered on the Privileged Certificate Store of the target devices: I found many entries about this on the MSDN Forums and newsgroups.

In a particular case, a developer was having troubles with SetWindowsHookEx on one particular device model only... so the issue was OEM-specific. How can we know how the OEM customized the image (by adding particular applications in ROM, for example) so that precisely the same application doesn't work? We couldn't go on with the support in this case and we had to revert to using "supported" techniques, such as subclassing, which in some cases is still more appropriate than a system-wide hook, especially in low-resources OSs like Windows CE or Mobile.

I've already discussed here about subclassing NETCF Applications, however this time a developer I recently worked with pointed my attention to an API I had forgot (GetForegroundKeyboardTarget) and that in our case it helped us on achieving precisely what we were looking for: intercepting keyboard presses when focus was given to an editbox. Here it is a snippet (note that the technique for subclassing is always the same).

 s_hExit = CreateEvent(NULL, FALSE, FALSE, NULL); 
HWND hWnd = GetForegroundKeyboardTarget();
if (hWnd!=NULL) 
{ 
    s_OldWndProc = (WNDPROC)GetWindowLong(hWnd, GWL_WNDPROC); 
    BOOL bSetNewProc = SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)NewWndProc); 
    WaitForSingleObject(s_hExit, INFINITE); 
    SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)s_OldWndProc); 
} 

 

A nice idea he had was to associate a hardware button to a console application, so that he subclassed only the window of an editbox when this had the focus (and this is where GetForegroundKeyboardTarget comes to help). A possible code sample is:

 static WNDPROC s_OldWndProc = NULL;
static HANDLE s_hExit = NULL;
 
LRESULT CALLBACK NewWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_CHAR:
        {
            TCHAR chCharCode = (TCHAR) wParam; 
            switch (chCharCode)
            {
                case _T('a'):
                //...
                    break;
            }
        }
        break;
 
    case WM_DESTROY:
        {
            if (s_hExit)
            {
                SetEvent(s_hExit);
            }
        }
        break;
 
    if (s_OldWndProc)
    {
        return CallWindowProc(s_OldWndProc, hwnd, uMsg, wParam, lParam);   
    }
    return 0;
}
 
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{
    s_hExit = CreateEvent(NULL, FALSE, FALSE, NULL);
    HWND hWnd = GetForegroundKeyboardTarget(); 
    if (hWnd!=NULL)
    {
        s_OldWndProc = (WNDPROC)GetWindowLong(hWnd, GWL_WNDPROC);
        BOOL bSetNewProc = SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)NewWndProc);
        WaitForSingleObject(s_hExit, INFINITE);
        SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)s_OldWndProc);
    }
    return (0);
}

 

 

Cheers,

~raffaele