Question

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?

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top