3

Generally, whenever we want to wrap a Window/Thread in a C++ object, we do so by passing the this pointer via SetWindowLong/GetWindowLong or SetProp/GetProp for a Window, and as lpParameter for CreateThread/etc.

My question is specific to Hooks. What is the elegant approach to pass the 'this' pointer to SetWindowsHookEx's callback procedures, or in other words How to wrap a hook's callback procedure ?

Since SetWindowsHookEx does not accept any UserData argument, I don't see much options apart from using un-encapsulated i.e. global/static/TLS data.

subdeveloper
  • 1,381
  • 8
  • 21
  • 1
    Possible duplicate of [SetWindowsHookEx, KeyboardProc and Non-static members](https://stackoverflow.com/questions/333575/setwindowshookex-keyboardproc-and-non-static-members) – Roger Lipscombe Apr 16 '19 at 16:17
  • @RogerLipscombe - thanks for the link. I had seen that already, it talks about using static, which I already know/have done many times. but never found it satisfying. – subdeveloper Apr 16 '19 at 16:21
  • It's not satisfying, but it's the "standard" way to do it. If you do something else, it's going to be potentially surprising. – Roger Lipscombe Apr 16 '19 at 16:41
  • @RogerLipscombe Are you saying using `Thunks` as mentioned by @ybungalobill is potentially surprising? I want to be open to learn new and better ways of doing some same old stuff. – subdeveloper Apr 16 '19 at 16:45
  • What I'm saying is this: If you're writing a framework (such as ATL), then using a thunk is probably the right answer. If you're writing an app then prefer the simple (but not as "elegant") solution. – Roger Lipscombe Apr 16 '19 at 16:52
  • @RogerLipscombe yes that makes sense from simplicity point of view. Actually problem is if we make it static, then we have to keep a check on instances/or convert the class to singleton, else there will be chaos. That is why I wanted to avoid static/globals. – subdeveloper Apr 16 '19 at 16:57

2 Answers2

2

You are expected to have just one instance of a given hook, so global data is not an issue.

If you are developing a library allowing multiple hook instances that can be dynamically added or removed, do not add multiple hooks at the OS level. Instead, add a library-level hook procedure that walks the list of hook instances. Since you maintain this list, you can track whatever "user data" alongside each entry you want.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Global mapping is something I am avoiding, ultimately its a global variable/outside the encapsulation. – subdeveloper Apr 16 '19 at 16:02
  • @subdeveloper: Yeah sorry I was thinking of subclassing (window message hooking) the first time I read the question. I think I've understand your actual request this time. – Ben Voigt Apr 16 '19 at 16:07
2

The 'most elegant approach' is to use a thunk. It's a small piece of code generated at runtime that holds your this pointer. This is the approach that ATL uses even for regular windows.

See

Yakov Galka
  • 70,775
  • 16
  • 139
  • 220
  • Elegant in theory but in practice this involves lots of undefined behavior, is non-portable, and likely to trigger anti-virus. – Ben Voigt Apr 16 '19 at 16:06
  • 1
    @BenVoigt: this is literally how **all ATL** works. Never trips an antivirus. And it's as portable as your windows app will be. – Yakov Galka Apr 16 '19 at 16:08
  • Thanks for pointing there, I believe I've read about thunks and specially how ATL used to use it. I agree its elegant, but probably you will agree its dirty work, and may be difficult to maintain. Or do you suggest otherwise? – subdeveloper Apr 16 '19 at 16:08
  • @ybungalobill: Win32 source code can be compiled to x86, x64, ARM (as WinCE). So no, the thunk is not as portable as the rest of the app. – Ben Voigt Apr 16 '19 at 16:09
  • @subdeveloper: I would say it's less dirty than the alternatives (having a global map with a global critical section). – Yakov Galka Apr 16 '19 at 16:10
  • Thunks are neither elegant nor portable. C++ lambdas, on the other hand, are at least portable. It requires [a bit of work](https://stackoverflow.com/a/45365798) to allow a capturing lambda to be used in place of a function pointer, but that's to be expected when doing *anything* in C++. – IInspectable Apr 16 '19 at 16:24
  • 1
    @IInspectable: do you have an example of how to pass a stateful lambda to a winapi function? I would like to see it. – Yakov Galka Apr 16 '19 at 16:26
  • I linked to a Q&A that does. What am I missing? – IInspectable Apr 16 '19 at 16:28
  • Apparently, it does. `void foo(void (*fn)()){ fn(); }` looks like a C pointer to me alright. Might be a Microsoft-specific extension to its compiler that allows C++ lambdas to match C-style function pointers (including an appropriate calling convention), but I haven't looked into the language specification to verify. – IInspectable Apr 16 '19 at 16:33
  • 2
    @IInspectable: that the linked code uses a singleton static that can support at most one lambda at a time. This is the same as just having a single global variable holding a pointer to your only instance of your class. – Yakov Galka Apr 16 '19 at 16:33
  • I'm assuming that you are going to delete that comment, too, like the previous one, once you find out that the code shown outlines a concept, and can be adjusted to meet the requirements in this Q&A. – IInspectable Apr 16 '19 at 16:39
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/191929/discussion-between-ybungalobill-and-iinspectable). – Yakov Galka Apr 16 '19 at 16:39