[SOLVED] Win API question: AttachThreadInput prevents menu activation ...

Hi, programmers.
I would need your help/experienced advice with using the AttachThreadInput() API function.

We are writing a callback function for a low level keyboard hook to monitor the user's keyboard activity (as a part of a complex surveillance/parental kit - currently being developed) and we include this code snippet (simplified):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LRESULT CALLBACK LowLevelKeyboardProc (int nCode, WPARAM wParam, LPARAM lParam)
{
    HWND hwndActiveWin = GetForegroundWindow();
    int  idActive      = GetWindowThreadProcessId(hwndActiveWin, NULL);
    if ( AttachThreadInput(GetCurrentThreadId(), idActive, TRUE) )
    {
        GetKeyboardState(keyboardState);   // keyboard state of the window with keyboard input
        HWND hwndFocused = GetFocus ();    // focused control within the active window, i.e. focus throughout the screen
        AttachThreadInput(GetCurrentThreadId(), idActive, FALSE);
    }

     ...  // data processing (including getting the keyboard layout of hwndFocused)

     return CallNextHookEx(KBHook, nCode, wParam, lParam);  // KBHook is the value returned by SetWindowsHookEx()
}


The problem: Everything works fine, BUT: when the above code is running, pressing the [Alt] key doesn't appear to activate the menu in the active application, say Notepad (with underlining the menu shortcuts); however, pressing the right combination (like Alt+F for File menu) still opens the respective menu (but the user must know the shortcuts:-( as there's no underlining); the story is the same for the F10 key which also fails to visually activate the menu, but again, it does (you must press it again to deactivate the menu although you don't see this happening). Alt+Tab works fine.
We are completely sure that leaving AttachThreadInput() out "fixes" the problem (but you probably see we need this function call to detect the active control with its keyboard layout and the actual state of Shift, Ctrl etc.).

So I would be extremely thankful if you could answer any of these questions:

1. How is the menu activation different from processing other keyboard messages? (Because all other keys work well, so I am actually asking - why does this happen?)
2. How to fix the problem?
3. If it is not possible to fix the usage of AttachThreadInput(), then without AttachThreadInput():
3a. How can you find the active control (with user input) of the whole screen? (Active window is not sufficient, as in some programs, various edits of the active window can have different keyboard layouts - like English in the address bar of IE7 and German within the displayed page)
3b. How to find the actual keyboard state? (OK, we can "remember" that Shift was pressed until it's released again, but maybe you know a better solution).

Tested on XP SP2 and Vista SP1 with the same results/problem.

Thank you in advance for any idea or solution.
Last edited on
i'll have to ponder this a little more, however, i believe that some of the accelerators are system-wide, and may actually generate interrupts, not keyboard messages (not positive on this though).

as i know very little about your project, i'm fairly confused as to why you are not utilizing 'wParam' which lets you know the message itself, and 'lParam' which is a structure which may contain the information you need, the structure is KBDLLHOOKSTRUCT structure, and if you haven't already i suggest reading the MSDN page on it to see if it may contain the information you require <http://msdn.microsoft.com/en-us/library/ms644967(VS.85).aspx>

if you can use the information in the structure to process the information you require, just cast the lParam to that structure, and this will avoid having to use AttachThreadInput() and processing every single key. again, you may have already looked at this avenue and have a reason for not using it, so i apologize if i'm being redundant.

i'll ponder this a bit more, but i have a calc II test this week... if there's any other info about the project you think might be helpful too... sorry i couldn't help more initially
Last edited on
Hi, Mal,
thanks for your reply.
You are right: Although I didn't include this part in the code sample, naturally, we cannot avoid using lParam and wParam to find out the virtual code (plus scan code) of the key along with its state (WM_KEYUP and the like) - inspecting the contents of the KBDLLHOOKSTRUCT structure is actually the gist of our work in this section.
Now, having the virtual key code, one must employ
MapVirtualKeyEx()
or
ToUnicodeEx()
to translate it into a character - and this translation requires:
- the keyboard layout of the active window/component (English, Russian ... keyboard): see my original question 3a
- and the keyboard state (Shift pressed, CapsLock on, ...) for the active thread, as input states are specific to individual threads (as stated in Remarks in <http://msdn.microsoft.com/en-us/library/ms681956(VS.85).aspx>): that's why my question 3b and line 7 in the attached code.

So I am sorry for not mentioning that we already use the callback parameters and I appreciate that you responded anyway,

Please, if you (or anybody else) can think of some more information, I would be very thankful.

PS: To Mal: please, could you elaborate a little bit on your first sentence? Or maybe send a link to a relevant article? Thanks a lot.
edit: just a couple ideas - are you sure that there are no other applications that could have installed a keyboard monitoring hook (innocent or malware alike), and i am not entirely sure if this would make a difference anyway, since [alt] is being pressed, but it may be worth a try to right click on desktop and open properties, then go to the 'appearance' tab and click 'effects...'. under there, there should be a checkbox that says "hide underlined letters for keyboard navigation until i press alt", according to a couple of articles this is checked by default, and may somehow be affecting your program if it is.

ok, so i haven't had time to think more yet, but

1) on closer inspection it looks like it may only be ctrl+alt+del that is implemented as an interrupt, and not other key combos.
<http://www.codeguru.com/vb/gen/vb_system/keyboard/article.php/c4829>
the code there is VB, but uses the windows api, so the functions are the same,
look under the heading 'IsHooked: Checking for Blocked Key Combinations', this may be useful info.

2) this may just be a typo, but i noticed that in your code snippet you have
HWND hwndFocused:= GetFocus ();
, and i was assuming that this was c/c++, but i didn't think that ':=' was a valid operator!? i tried compiling a normally working project with a ':=' in it using a c++ compiler (gcc) and if failed to compile.

3) i started a dummy application to try and recreate your problem, don't know how long though, i have two tests this week, parents visiting this weekend, concert friday.

ps since you seem to be a knowledgeable programmer, i posted a question recently that you may have an idea, if you happen to have time <http://www.cplusplus.com/forum/windows/5199/> don't worry if you don't, or if you don't have ideas if you do.
Last edited on
question for you:
1) it is my understanding that for a hook procedure to be global, it must be stored in a DLL. at first i thought that yours was global, however, i see that you are passing a handle of the current hook to CallNextHookEx(), implying that the hook callback is in the ".exe" image of your original process. So my question... how exactly do you have this implemented? are you keeping it in the resident ".exe" and attaching it as a thread-specific hook, only to threads that you wish to monitor, thus no need to put it in a DLL? sorry for asking so many questions of you, but i'm kind of intrigued by this problem and would like to help.

ps, again i assume typos:
GetCurrentThreadID() -----> GetCurrentThreadId()
GetWindowThreadProcessId(hwndActiveWin) ----> takes 2 parameters,
i would think any compiler would catch these anyway.
Last edited on
Hi,
I feel terribly sorry for posting the messed up code - I cleaned it up a little (we often analyse problematic chunks of code in isolation, sometimes under various IDEs and when really helpless, we try different languages to rule out any compiler issues and internal dependencies - so we had a small Delphi file containing nothing but the problem-causing-code and I assumed it would be easier to "translate" it back to C than to reduce rather complex C++ classes of the original code - but several details passed unnoticed, for which I must apologise once again).

To quickly reproduce the erroneous behaviour, create a project with a form, put a label onto it, paste the code below in the form's unit (it compiles well, I checked it this time:) ) and assign TForm1::FormCreate and TForm1::FormClose as the respective event handlers. Now open any app like Notepad or Explorer and press Alt and nothing happens... Hold Alt and press F and the menu opens...

Low-level hook procedure (very simple, no error-checking, dead key processing, Unicode support etc.); tested with C++ Builder 6:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
LRESULT __stdcall LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
HHOOK KBHook;

void __fastcall TForm1::FormCreate(TObject *Sender)
{
    KBHook = SetWindowsHookExA(WH_KEYBOARD_LL, (int(__stdcall*)())(LowLevelKeyboardProc), HInstance, 0);
}
//---------------------------------------------------------------------------

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
    UnhookWindowsHookEx(KBHook);
}
//---------------------------------------------------------------------------


LRESULT __stdcall LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    KBDLLHOOKSTRUCT *KeyInfo = (KBDLLHOOKSTRUCT*)lParam;
    HWND hwndActiveWin     = GetForegroundWindow();
    unsigned long idActive = GetWindowThreadProcessId(hwndActiveWin, NULL);
    HWND hwndFocused       = 0;
    char lpKBState[256];
    if ( AttachThreadInput(GetCurrentThreadId(), idActive, TRUE) )
    {
        GetKeyboardState(lpKBState);   // keyboard state of the window with keyboard input
        hwndFocused = GetFocus ();     // focused control within the active window, i.e. focus throughout the screen
        AttachThreadInput(GetCurrentThreadId(), idActive, FALSE);
    }
    else
    {
        GetKeyboardState(lpKBState);
        hwndFocused = hwndActiveWin;
    }
    unsigned long idControlActive = GetWindowThreadProcessId(hwndFocused, NULL);
    unsigned short textWritten[10];
    HKL dwhkl        = GetKeyboardLayout(idControlActive);
    int charsWritten = ToAsciiEx(KeyInfo->vkCode, KeyInfo->scanCode, lpKBState, textWritten, 0, dwhkl);
    Form1->Label1->Caption = AnsiString ((char*)textWritten, charsWritten);
    return CallNextHookEx(KBHook, nCode, wParam, lParam);
}


Now to your questions, Mal:
- other hooks installed - good idea, we had here one (resident AV); I tried to turn it off, but unfortunately, this was not the cause
- "underlined menu letters" option - works, but pressing the Alt key should still highlight (activate apparently to the user) the menu, which doesn't happen with the attached code running
- thousands thanks for the link to the codeguru article - seems helpful, I 'll read it through asap
- global hooks and DLL: after some consultations with senior programmers, we found out that low level hooks should work fine even when the callback is located in the exe's code (check the above code and you'll see for yourself) - someone mentioned this also in the "Community Content" section of <http://msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx>; but it may be possible that leaving the hook procedure's code within the application's source and attaching threads might not work well together - interesting suggestion of yours, thanks, I'll separate the code into a DLL and we'll see (I will post the result here probably on Friday as I'm not at work tomorrow).
Thank you for all the ideas.

Now I will have a look at your question - please excuse me if it is answered (or at least inspected) not sooner than Friday night/weekend, as I do not work tomorrow.

Kindest regards.
Last edited on
ok, thank you for providing the code to test, I didn't use a form (I've never written any .NET yet), so I just put it in a normal win32 application. Not only was I able to reproduce the behavior, but I also discovered a peculiar oddity.
Below is the exact code that I compiled with DevC++ (Mingw) and reproduced the behavior.

The only parts of the code that i had to change was: this line,
Form1->Label1->Caption = AnsiString ((char*)textWritten, charsWritten);, as i wasn't doing a form, and also, the functions GetKeyboardState(), and ToAsciiEx() needed the variable 'lpKBState' passed as a 'BYTE*' not 'char*' even though they're the same, but that may have just been specific to my dev. environ., so i'm not sure. As mentioned earlier, below is the exact code that I used, I think it should compile with almost any C++ compiler, let me know if you have problems:

Note: there are a few things, the first, when the messagebox that is in your hook proc in the code below is not commented out, the alt key sometimes works. i believe that this is just because the hook times out (<http://msdn.microsoft.com/en-us/library/ms644985(VS.85).aspx>, see 'remarks' section). however, it allows the next thing to be called, and ultimately, for the application to get the 'alt' message. also, of interest is this: <http://msdn.microsoft.com/en-us/library/ms681956(VS.85).aspx> again under 'remarks' it states that keyboard state is reset after a call to AttachThreadInput(), maybe this has something to do with it? if keyboard state is reset, and the application gets a message for the key, then looks at keyboard state for the alt, but it has been reset, it wouldn't see the alt, and ignore it? I will also continue to look at alternatives, there may be a way to get around calling attachthreadinput(), but i haven't tested it yet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//-----------------------------------------------------------//
//  This is just a dummy application to test a low-level     //
//  keyboard hook process.  Most of it is just normal        //
//  windows overhead.  It installs the hook procedure, then  //
//  runs the message loop until the window is closed, at     //
//  which point it unhooks the hook, and exits.              //
//-----------------------------------------------------------//

#include <windows.h>
#include <stdio.h>
#include <string.h>

using namespace std;

//window procedure, hook proc proto, and an error function
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
LRESULT __stdcall LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
char* GetLastErrorString(void);

//handle for the hook
HHOOK KBHook = NULL;

//window class name
char szClassName[ ] = "HookWindow";

// process entry
int WINAPI WinMain(HINSTANCE hThisInstance,HINSTANCE hPrevInstance,LPSTR lpszArgument,int nShow)
{
    // normal windows variables
    HWND hwnd;
    MSG messages;
    WNDCLASSEX wincl;

    // normal windows programming overhead
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;
    wincl.style = CS_DBLCLKS;
    wincl.cbSize = sizeof (WNDCLASSEX);
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;
    wincl.cbClsExtra = 0;
    wincl.cbWndExtra = 0;
    wincl.hbrBackground = (HBRUSH)COLOR_BACKGROUND;

    // Register the window class, and if it fails quit the program
    if (!RegisterClassEx(&wincl))
    {
        return 0;
    }

    // create the window
    hwnd = CreateWindowEx (0, szClassName, "Low Level Keyboard Hook", WS_OVERLAPPEDWINDOW,
           CW_USEDEFAULT, CW_USEDEFAULT, 300, 200,
           HWND_DESKTOP, NULL, hThisInstance, NULL);

    // make sure window is visible
    ShowWindow (hwnd, nShow);
    
    // store address of hook proc
    HOOKPROC lpfnHookProc = &LowLevelKeyboardProc;
    // try to set hook
    KBHook = SetWindowsHookEx(WH_KEYBOARD_LL, lpfnHookProc, GetModuleHandle(NULL), 0);
    // if hook was not installed, get the last error code, and string and
    //     show them both in a messagebox()
    if(KBHook == NULL)
    {
        DWORD n;
        char lpBuffer[50];
        char* errorString = GetLastErrorString();
        sprintf(lpBuffer, "keyboard hook not installed\n"
        "error code: %d\n", (int)GetLastError());
        strcat(lpBuffer, errorString);
        MessageBox(HWND_DESKTOP, lpBuffer, "keyboard hook could not be installed!", MB_OK);
    }
    
    // start a basic message loop (only if the hook was properly installed)
    while (GetMessage(&messages, NULL, 0, 0) && (KBHook != NULL))
    {
        TranslateMessage(&messages);
        DispatchMessage(&messages);
    }
    
    // if there is a valid hook procedure, uninstall it
    if(KBHook)
    {
        // if unhookwindowshookex fails, show the last error as a string
        if(!UnhookWindowsHookEx(KBHook))
        {
            char* errorString = GetLastErrorString();
            MessageBox(HWND_DESKTOP, errorString, "Last Error", MB_OK);
        }
    }
    return messages.wParam;
}



char* GetLastErrorString(void)
{
    LPTSTR pszMessage;
    DWORD dwLastError = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dwLastError,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&pszMessage,
        0, NULL );
    return pszMessage;
}

// skeleton window procedure to run app
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_DESTROY:
            PostQuitMessage (0);
            break;
        default:
            return DefWindowProc (hwnd, message, wParam, lParam);
    }

    return 0;
}

// your hook procedure
LRESULT __stdcall LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    //MessageBox(HWND_DESKTOP, "", "", MB_OK);
    KBDLLHOOKSTRUCT *KeyInfo = (KBDLLHOOKSTRUCT*)lParam;
    HWND hwndActiveWin     = GetForegroundWindow();
    unsigned long idActive = GetWindowThreadProcessId(hwndActiveWin, NULL);
    HWND hwndFocused       = 0;
    char lpKBState[256];
    if (AttachThreadInput(GetCurrentThreadId(), idActive, TRUE) )
    {
        GetKeyboardState((BYTE*)lpKBState);   // keyboard state of the window with keyboard input
        hwndFocused = GetFocus ();     // focused control within the active window, i.e. focus throughout the screen
        AttachThreadInput(GetCurrentThreadId(), idActive, FALSE);
    }
    else
    {
        GetKeyboardState((BYTE*)lpKBState);
        hwndFocused = hwndActiveWin;
    }
    unsigned long idControlActive = GetWindowThreadProcessId(hwndFocused, NULL);
    unsigned short textWritten[10];
    HKL dwhkl        = GetKeyboardLayout(idControlActive);
    int charsWritten = ToAsciiEx(KeyInfo->vkCode, KeyInfo->scanCode, (BYTE*)lpKBState, textWritten, 0, dwhkl);
    return CallNextHookEx(KBHook, nCode, wParam, lParam);
}


I am sorry that I have not been able to solve your problem, I will continue to work on it when possible.

ps - I will not be at home this weekend, so it may be Monday before I have a chance to continue work on this, good luck, and take care.
NOTE: I have not had time yet to completely test this possible solution, so I am not positive that it will work. This is just putting logical thoughts together.

ok, working under the assumption that your users' systems are Windows 98/Me or Windows NT 4.0 SP3 and later... I think i may have found a viable solution to your dilemma.

1) to get the window with the focus, I would use the GetGUIThreadInfo() function. here is the MSDN page: < http://msdn.microsoft.com/en-us/library/ms633506(VS.85).aspx > and here is the MSDN page for the structure it fills with the info: < http://msdn.microsoft.com/en-us/library/ms632604(VS.85).aspx > The function takes a thread id and an address of a GUITHREADINFO structure. the thread id you can get from your previous call to GetWindowThreadProcessId(), or, MSDN also says that if the first parameter is NULL, it will default to the foreground thread; i'd just set it to NULL. the 'hwndFocus' member of the structure should contain a handle to the window with focus after your call to GetGUIThreadInfo(). so this should give you the handle you need:
1
2
3
4
5
6
GUITHREADINFO guiInfo;
guiInfo.cbSize = sizeof(GUITHREADINFO);
if(GetGUIThreadInfo(0, &guiInfo))
{
    hwndFocused = guiInfo.hwndFocus;
}


2) now, how i would approach this problem is to declare the 256 char array outside of the scope of the hook callback, but in the same file. then, right before i registered the hook, i'd fill the array, using GetAsyncKeyState(), (GetKeyState() for keys that are toggled). since this is a low-level hook, it intercepts all keyboard messages with the exception of ctrl-alt-delete. so, then i would just process each message, changing each of the individual char in the array as necessary based on the messages recieved, and everything else is the same, ToAsciiEx() will still take the same things, since holding the shift down will produce the keydown message, then any other key pressed will be with the shift down since the keyup message for the shift hasn't been processed yet! no having to worry about getting the entire keyboard state each time, and no messing with AttachThreadInput()!

This is how I would implement this, i hope that you find some of this helpful. take care
Last edited on
Hi,
I must say you did some really good work, thank you a lot!
For a couple of days, I have been "on a break" from this problem, though, because of having a deadline for tests of another module in the project... so sorry for being silent.
- At first, I cannot express my gratitude for digging out this GetGUIThreadInfo() thing! That looks really promising.
- I'll have to analyse your ideas more in depth, especially I have to reconsider whether we understood the remark on time limits for the hook procedure right, as you pointed out;
- but you are surely completely right with the keyboard state workaround that you suggested (we were slightly afraid this approach might be necessary, as I implied in my original question 3b; now I am almost sure it is the only reasonable way to go).
It'll be necessary to outline the implementation of the changes... I hope I can make some preliminary tests during the weekend (the programmers' way:( of spending the weekend) and let you know how your suggestions work for me. For now, I am really, really thankful for your helpful effort and please, in case the solutions suggested by you (esp. the guithreadinfo) will work and be accepted for the project, send me a PM to authorise us to credit you as an external developer consultant.
Kindest regards.
OK, my last one under this topic:

I tested some ideas of yours, Mal, and I 've got to express my gratitude once again.

So far, I've discovered this:

- your code worked fine for me (btw., my compiler - C++ Builder - demanded one additional typecast, line 63, but as you said, that's just a devel. envir. idiosyncrasy); when MessageBox (line 136 in your code) is not commented out, the Alt key works perfectly - you are right, this is because of the "timeout": the same "solution" is accomplished by using Sleep() instead of MessageBox(), which is more quiet, but you have to call this function (or anything else to detain the KeyboardProc) before attaching the thread input (otherwise the [Alt] key is lost anyway)

- of course, no Sleep() function (or even MessageBox) is allowed, so with my colleague, we deprecated our former AttachThreadInput() approach, as no other (better) workaround then letting the procedure sleep for a while was found (but no user would appreciate if we reduced the typing rate to about two strokes per second maximum)

- I still cannot say why this Alt trouble happens: you suggested that possibly the AttachThreadInput() resets the keyboard state (thus disposing of Alt) - might be; however, some other factors must be present too, as other keys are delivered to the target applications smoothly

- final solution: both your suggestions ACCEPTED:
1. we use GetGUIThreadInfo to find the focused child window and
2. we remember the keyboard state between the HookProc calls by our own means (without depending on GetKeyboardState() )
- noteworthy detail: unlike AttachThreadInput(), GetGUIThreadInfo doesn't prevent Alt from doing its job, so the problem is completely solved!!

Thank you once again.

With best regards.
Last edited on
edit: you posted while i was writing mine! :)

Hello,
I understand, you have an actual job, and things to do, I figured you had just been busy, so no problem.
-I'm just glad that i could actually provide some helpful information! I love doing stuff like this, and trying to figure out solutions. It does get a little frustrating sometimes with the API and digging deep into windows because of the lack of documentation or specific information on lesser known aspects, but still fun.
-I find it interesting how windows has a built in method of setting a low-level hook to the keyboard... yet it is frustratingly difficult to actually retrieve keyboard information. Also, since keyboard information appears to be thread-specific, not process specific, the CreateRemoteThread() trick can't be used. I am still searching, wondering if there's just something simple that I am missing...
-I'm flattered, but I really haven't done much, and as I'm not finished with school, I don't have any real job experience! I guess I'm just not sure how these sort of things work yet :) I was just trying to help find a solution to an intriguing problem.

Good luck with the testing, and I'll try to test another idea that I had this weekend. Take care.
Last edited on
Topic archived. No new replies allowed.