Question

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.)

Was it helpful?

Solution

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.

OTHER TIPS

Try using WinEvents instead of the CBT hook: SetWinEventHook looking for EVENT_OBJECT_FOCUS as both min and max event, with the WINEVENT_OUTOFPROC flag, and 0 for idThread and idProcess. This will give you a hook that can listen to focus events from any process in the same desktop, without requiring a separate DLL, and it will work across both 32-bit and 64-bit applications.

There's a couple of caveats: one is that the events are not instantaneous; there's a slight lag as they are essentially posted to your process (which is how the out-of-proc option that avoids requiring a DLL works), but they may well be fast enough for your use. (And you'd have this same issue if you use PostMessage in your DLL hook anyhow!)

Also, you will get more events than actual HWND focus changes: various controls send these focus change events to signal internal focus change - focus moving between items in a list box, for example. You can filter these out by filtering in the callback for only those with idObject=OBJID_WINDOW and idChild=0.

Alternatively, if you listen for EVENT_SYSTEM_FOREGROUND events instead of EVENT_OBJECT_FOCUS (see MSDN for the full list of events), then it seems you should only get top-level window foreground events, which sounds like what you are actually after here.

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