1

subjective...HA

ok so i've been looking around the internetz for a reasonable solution to trapping multiple keystrokes and came accross a few solutions that use the same thing (keyboard hook). One soltuion used a native call to get the IntPtr of a process by name, and the other used LoadLibrary("User32.dll")

so i figured I would be "smart" and did this (with success)

IntPtr hInstance = Process.GetCurrentProcess().MainModule.BaseAddress;
callbackDelegate = new HOOKPROC(HookCallback);
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, callbackDelegate, hInstance, 0);

as apposed to using this

IntPtr hInstance = LoadLibrary("User32.dll");
callbackDelegate = new HOOKPROC(HookCallback);
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, callbackDelegate, hInstance, 0);

is one safer than the other? am I making a fatal error that isn't showing it's head?

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Robert Snyder
  • 2,399
  • 4
  • 33
  • 65
  • Tagged as C# but looks like C++ - which one is it? – sinelaw Jan 10 '13 at 14:58
  • 1
    They're both wrong, but since you are using a low-level hook, it doesn't really matter. – Raymond Chen Jan 10 '13 at 15:03
  • i just realized it does look like C++.. it's not it's C# – Robert Snyder Jan 10 '13 at 15:04
  • Where are the docs for `HOOKPROC` (uppercase)? – sinelaw Jan 10 '13 at 15:06
  • 1
    @RaymondChen Could you please elaborate how they are BOTH wrong? it works...so how is it wrong? – Robert Snyder Jan 10 '13 at 15:06
  • @sinelaw because i didn't like the name the other person chose keyboardHookProc – Robert Snyder Jan 10 '13 at 15:13
  • 1
    @RobertSnyder, `HOOKPROC` is your name then? It's a bit confusing because it's also the name of a C++ Win32 typedef, which is why I thought this is C++, although the `new` didn't make sense then (also, `HOOKPROC` doesn't follow C# naming conventions..) – sinelaw Jan 10 '13 at 15:18
  • @RobertSnyder: The fact that something works doesn't mean that it's right, but I too am interested in an answer explaining the _correct_ way to do it. Try rephrasing your question from an "Either-Or" format to a "What's the best way?" format. – Greg D Jan 11 '13 at 13:57

2 Answers2

3

SetWindowsHookEx() requires a valid module handle. It uses it to figure out what DLL needs to be injected into other processes to make the hook work.

But that's only a requirement for global hooks. The two low-level hooks (WH_MOUSE_LL and WM_KEYBOARD_LL) are special, they don't require DLL injection. Windows calls the hook callback in your own process only. The sole requirement is that your thread pumps a message loop so that Windows can make the callback. Application.Run() is required.

Also the reason that you can make low-level hooks work in C#, the DLL used by global hooks cannot be written in a managed language because the injected process will not have the CLR loaded.

The quirk is that SetWindowsHookEx() checks if you passed a valid module handle but then doesn't actually use it for the low-level hooks. So any valid handle you pass will work. This quirk was fixed in Windows 7 SP1 btw, it no longer performs that check.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • It's probably because of this that IntPtr.Zero works right now is what i'm assuming. Thank you for clearing this up for me. – Robert Snyder Jan 10 '13 at 16:56
  • Right. Don't pass IntPtr.Zero as suggested in the answer you selected. It won't work on older Windows versions. – Hans Passant Jan 10 '13 at 16:59
  • I will keep that in mind, for the present this will work because it only has to work on 3 computers that i'm programming for that all have win 7 pro sp1.. But my big plan is to have it work on XP, XP Embedded, 7, and 7 embedded. all have the latest updates. – Robert Snyder Jan 10 '13 at 17:52
0

EDIT: My original answer left the IntPtr.Zero option open, and gave the neccesary information to help you decide. I'm adding another quote from the docs, which discusses when not to use null:

An error may occur if the hMod parameter is NULL and the dwThreadId parameter is zero or specifies the identifier of a thread created by another process.

Since you're using 0 as the thread id (which means "all existing threads running in the same desktop as the calling thread", as per the docs), you should NOT be using null but Marshal.GetHINSTANCE instead.


I think you should be passing either IntPtr.Zero or Marshal.GetHINSTANCE(your current module)

According to these docs, the third argument (hMod) is -

A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process.

Also as mentioned in this article ("Windows Hooks in the .NET Framework"),

The third argument should be the HINSTANCE handle of the DLL that contains the code for the filter function. Typically, this value is set to NULL for local hooks. Do not use the .NET null object, though; use the IntPtr.Zero expression, which is the correct counterpart for Win32 null handles. The HINSTANCE argument cannot be null for systemwide hooks, but must relate to the module that contains the hook code—typically an assembly. The Marshal.GetHINSTANCE static method will return the HINSTANCE for the specified .NET module.

Also, see this question.

Community
  • 1
  • 1
sinelaw
  • 16,205
  • 3
  • 49
  • 80
  • I used IntPtr.Zero and it still appears to be working. Thank you very much for your information. – Robert Snyder Jan 10 '13 at 16:56
  • @Robert I don't know why you accepted this answer. Hans explains why `IntPtr.Zero` doesn't work on certain Windows versions. – David Heffernan Jan 10 '13 at 18:02
  • @DavidHeffernan if you look at the code that I presented sinelaw answered that I had it wrong and should change my code. He suggested teh use of IntPtr.Zero and Hans expanded on that answer. Since sinelaw pointed me at documentation for exactly what I was working on I chose his answer. This doesn't make Hans' answer any less important though. His answer is VERY useful and will actually cause me to modify my code even more. – Robert Snyder Jan 10 '13 at 18:48
  • There, I've added a clarification. – sinelaw Jan 10 '13 at 20:40