The SetWindowsHookEx function accepts a HINSTANCE parameter. The documentation explains that it is a handle to the DLL containing the hook procedure. Why does the window manager need to have this handle?

It needs the handle so it knows which DLL to load into each process when the hook fires. It injects the DLL you pass, then calls the function you pass. Clearly the function needs to reside in the DLL you pass in order for its code to be there when the window manager calls it.

This is also why hook functions must reside in DLLs. You can't load an EXE into another EXE.

The WH_KEYBOARD_LL and WH_MOUSE_LL hooks are exceptions to this rule. These two are non-injecting hooks, as explained in their respective documentation pages. Rather, the hook function is called in its original thread context.

Okay, armed with this information, perhaps you can solve this person's problem with global hooks.