-1

I have set a WH_KEYBOARD_LL hook to monitor keystrokes the code works fine for a while but suddenly stops tough the application does not give any single error.

I started debugging it and the behaivour appeared sooner while having a breakpoint in the function. It looks like the function is called aproximatelly 6 times until it stops receiving keyboard events.

function hookproc(code: Integer; wparam: WPARAM;lparam: LPARAM): LRESULT; stdcall;
begin
  result := CallNextHookEx(hook, code, wParam, lParam);      // I have put breakpoint here
end;

procedure Start();
begin
       hook := SetWindowsHookEx(WH_KEYBOARD_LL,@hookproc,GetModuleHandle(nil),0); 
end;

    procedure TMyApplication.DoRun;
var
  msg : tagMSG;
begin
  Start();
  ZeroMemory(@msg,sizeof(msg));
  while GetMessage(Msg, 0, 0, 0) do
  begin
  TranslateMessage(Msg);
  DispatchMessage(Msg);
  end;
end.

Basically I reduced the code to this, and the behaivour still exists. Any idea what is wrong with the code?

opc0de
  • 11,557
  • 14
  • 94
  • 187
  • According to http://msdn.microsoft.com/en-us/library/windows/desktop/ms644936(v=vs.85).aspx you are not supposed to pass in `nil` as the first argument for `GetMessage`. You should also check the return value of `GetMessage`. And why do you use the ANSI version of `GetMessage`? – jpfollenius Nov 11 '13 at 14:55
  • @jpfollenius If hWnd is NULL, GetMessage retrieves messages for any window that belongs to the current thread, and any messages on the current thread's message queue whose hwnd value is NULL (see the MSG structure). Therefore if hWnd is NULL, both window messages and thread messages are processed. I don't have a window anyway... – opc0de Nov 11 '13 at 14:57
  • Yes, but the first argument is not HWND but a pointer to a MSG structure to be filled. – jpfollenius Nov 11 '13 at 14:59
  • Try with `OutputDebugString` instead of a breakpoint. It works fine. Seems to be an issue with the debugger. – jpfollenius Nov 11 '13 at 15:01
  • @jpfollenius It has nothing to do with the debugger, I tested it with free pascal too, same behaivour. – opc0de Nov 11 '13 at 15:04
  • Works for me with `OutputDebugString` on XE2, Windows 7 32bit. – jpfollenius Nov 11 '13 at 15:05
  • Yeah, I know ith works but the problem is the problem ocurred in my application after several days of running. Don't know what triggers this behaivour but I think that happend with my application and I can't just simply find a quick solution to a problem that exists. – opc0de Nov 11 '13 at 15:08

1 Answers1

2

Update: The first part of this answer referred to code that has subsequently been removed from the question.

Your call to GetMessage is incorrect and fails. You have to pass a pointer to a MSG struct. If you don't messages will not be pulled from the queue.

I'm not sure where you are getting GetMessageA from. The one declared in Windows.pas accepts a var parameter for the MSG parameter.

You probably ought to dispatch the messages too. In fact, I don't understand why you dont use the standard message pump:

while GetMessage(Msg, 0, 0, 0) do
begin
  TranslateMessage(Msg);
  DispatchMessage(Msg);
end;

In the question you say that you are setting a breakpoint in your hook function. The documentation has this to say:

The hook procedure should process a message in less time than the data entry specified in the LowLevelHooksTimeout value in the following registry key:

 HKEY_CURRENT_USER\Control Panel\Desktop 

The value is in milliseconds. If the hook procedure times out, the system passes the message to the next hook. However, on Windows 7 and later, the hook is silently removed without being called. There is no way for the application to know whether the hook is removed.

So, you'll need to stop using breakpoints. Instead use a technique like OutputDebugString.


Even with these changes, the hook is never called when I run the program. Probably the reason why your hook is not being called is that your console application's main thread message queue is not active. The window that hosts the console is in a different process, conhost.exe. Your program is a console application that creates no windows, and so it won't have a message queue. When I run your program, the call to GetMessage never returns, entirely as expected.

You can see that the hooking code works if you switch to a GUI subsystem application. For instance:

program LowLevelKeyboardHook;

uses
  SysUtils, Windows, Forms;

var
  hook : HHook;

function hookproc(code: Integer; wparam: WPARAM; lparam: LPARAM): LRESULT; stdcall;
begin
  OutputDebugString('hookproc called');
  result := CallNextHookEx(hook, code, wParam, lParam);
end;

var
  Form: TForm;

begin
  hook := SetWindowsHookEx(WH_KEYBOARD_LL, @hookproc, GetModuleHandle(0), 0);
  Application.CreateForm(TForm, Form);
  Application.Run;
end.

And take a read of Sertac's informative comments. His tests back up the hypothesis that your problem is down to the lack of a message queue. He throws in a call to PeekMessage to create a queue, and that changes behaviour.


All this said, I suspect that your real application's problem is different. I suspect that the problem there is that you are passing nil to the first parameter of GetMessage. And eventually your message queue fills up.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Try it now, still the same behaivour. Complete cut and paste code http://pastebin.com/VsrsM3yj – opc0de Nov 11 '13 at 15:20
  • 1
    The code is fine now. Use OutputDebugString rather than a debugger. – David Heffernan Nov 11 '13 at 15:24
  • That would be the correct answer I believe. The OS kicks out unresponsive hook procedures from the hook chain. Don't *break* in one. – Sertac Akyuz Nov 11 '13 at 16:15
  • @SertacAkyuz I think it's more than that actually. I don't think the message queue ever gets any messages, so `GetMessage` never returns. – David Heffernan Nov 11 '13 at 16:19
  • @opc0de Why did you change the question so that the first part of my answer looks stupid? – David Heffernan Nov 11 '13 at 16:22
  • @DavidHeffernan because it is stupid that doesn't solve the problem the behaivour still exists. And telling me to use outputdebugstring that doesn't solve my problem I told you I have the same problem in a big application but this behaivour ocurrs when I am doing work in other threads.The code worked the same with your modifications or not. – opc0de Nov 11 '13 at 16:43
  • Thanks for telling my that my answer is stupid. How does your real app call GetMessage? Do you see my code that shows the difference between running in a console app and a GUI app? `GetMessage` doesn't return in a console app. And why would it? There are no windows so who is going to send messages to that thread's queue? – David Heffernan Nov 11 '13 at 16:45
  • @DavidHeffernan my app doesn't have any windows so getmessage has the same effect either calling it your way or my way.Test the code in an actual envoirment and then tell me I am wrong, I changed the code because that you do every time criticize the code from the wrong point of view. What has to do with a api call with the actual problem ? – opc0de Nov 11 '13 at 16:48
  • You should have left the original code there, and added an edit that extended the question. Again, do you see the program in my answer? – David Heffernan Nov 11 '13 at 16:51
  • @DavidHeffernan yeah I see I will test and come back with an answer.You are answering my questions for over 3 years now, I never seem to understand your answering style either you have a problem or I do. – opc0de Nov 11 '13 at 16:55
  • Probably it's both of us!! I just think that if someone has an answer covering one aspect of the question, then you should not remove that aspect completely. By all means say, "EDIT: well I tried this, but problem persists." And for sure passing `nil` to `GetMessage` is a problem. You must not do that. – David Heffernan Nov 11 '13 at 16:58
  • +1 as in its current state the thread will not receive any messages, and for all the other good information. But the thread can get a message queue created by calling `PeekMessage(Msg,0,WM_USER,WM_USER,PM_NOREMOVE)` as per the documentation (PeekMessage), without creating a window. – Sertac Akyuz Nov 11 '13 at 19:39
  • I wonder how does the application receives 6 or so messages (as stated in the question).. – Sertac Akyuz Nov 11 '13 at 19:41
  • @Sertac hook proc fires 0 times when I run program – David Heffernan Nov 11 '13 at 22:45
  • @David - Even if you include the PeekMessage call in my comment before the GetMessage call? – Sertac Akyuz Nov 11 '13 at 22:49
  • @Sertac Of course. Create a message queue with PeekMessage. But the code in Q and in pastebin does not do that. – David Heffernan Nov 11 '13 at 23:07
  • @David - Tried it now, just inserted the PeekMessage just before the 'while' and it works. The pastebin code from the first comment to this answer. D2007 and XE2 (32bit), W7x64. I find test cases quite unreliable these days.. – Sertac Akyuz Nov 11 '13 at 23:20
  • Just for information in case OP would doubt the same symptoms, I pressed space about 50 times while dexplore was active and watched '32' in the event viewer of the IDE (`OutputDebugString(PChar(Format('%d',[PKBDLLHOOKSTRUCT(lparam).vkCode])));`) – Sertac Akyuz Nov 11 '13 at 23:31