SetWindowsHookEx creates a local hook. How to make it global?

Marek Jedliński picture Marek Jedliński · Mar 3, 2012 · Viewed 8k times · Source

In a Delphi XE application I am trying to set up a global hook to monitor focus changes. The hook is created in a dll:

focusHook := SetWindowsHookEx( WH_CBT, @FocusHookProc, HInstance, 0 );
// dwThreadId (the last argument) set to 0 should create a global hook

In the same dll I have the hook procedure that posts a message to the host app window:

function FocusHookProc( code : integer; wParam: WPARAM; lParam: LPARAM ) : LResult; stdcall;
begin
  if ( code < 0 ) then
  begin
    result := CallNextHookEx( focusHook, code, wParam, lParam );
    exit;
  end;

  result := 0;

  if ( code = HCBT_SETFOCUS ) then
  begin
    if ( hostHWND <> INVALID_HANDLE_VALUE ) then
      PostMessage( hostHWND, cFOCUSMSGID, wParam, lParam );
  end;
end;

This works, but the host only receives notifications on focus changes within the application itself. There is a memo and a few TButtons on the main form, and switching focus between them produces the expected message. However, any focus changes outside the application itself are never reported.

I suppose it has something to do with multiple instances of the DLL getting injected into other processes. There is a similar question with an accepted reply here, but it is for C, and I can't quite see how I can do the same in a Delphi dll (e.g. the pragma statements to set up shared memory).

(This is mostly a proof of concept, but I'd still like to get it to work. I need to know what window was active just before my app got activated by way of clicking, alt+tab, activation hotkey etc. The problem is that if the mouse or alt+tab is used, GetForegroundWindow always returns my own app's window handle, no matter how early I put it, such as by hooking the application's main message queue. So the hook seems like the only viable solution, though I don't really like the idea.)

Answer

Allen Bauer picture Allen Bauer · Mar 3, 2012

Since the DLL is injected into another process, you're not going to get any breakpoints hit for anything other than the process you're debugging. Also, each instance of the DLL in the other process also get their own global/static data. If hostHWND is a global, it won't be the same value in the other process as it is in this one. In fact it won't even get initialized. You need to use a shared memory block to share values among the processes. Shared mutexes and other synchronization objects may need to be used to ensure any shared memory writes are protected. Finally, if you're using Windows Vista+, only processes with the same access level and below will get the DLL injected. IOW, if you're running the process as the logged-in user, only processed running as the logged-in user will get that DLL injected.