www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - WinAPI LowLevel Keyboard Hooks

reply "DLimited" <tanojoshu googlemail.com> writes:
Hello everyone,

I had this great idea of writing a Program that intercepts all
keyboard presses and modifies them in certain cases.
I want to use it as some kind of global makro program to run in
the background and for example allow me to easily post unicode
smileys.

This is where the probelms begin.
If I understood the WinAPI doc correctly, I need to install a
LowLevel Keyboard Hook using SetWindowsHookEx().

Unfortunately there are two versions of this function,
SetWindowsHookExW and SetWindowsHookExA. What's the difference?

The function to get called is passed as a parameter, but
apparently it needs to be located in a .dll if using global hooks.


So I need to first load my .dll file using LoadLibraryA( ) or
LoadLibraryW( ), locate my function using GetProcAddress( ) and
then set the hook using SetWindowsHookExW (or *-A)).


Unfortunately for me, even the LoadLibrary function fails,
returning "Module could not be found". Now I'm 99% sure my .dll
is crap because this is the first time I ever wrote one, but it
DOES have a DllMain(), initializes the D Runtime and also has the
other function I want to install the hook for.

I'm hoping some bright minds here could help me out because
google didn't, and I'm out of my depth.
If you want me to, I'll post the source code, but I didn't want
this post to get too big, which still did, but anyway.
Jul 19 2012
next sibling parent reply David <d dav1d.de> writes:
 Unfortunately there are two versions of this function,
 SetWindowsHookExW and SetWindowsHookExA. What's the difference?

The W-Function is the Unicode version and the A is the ANSI version. Showing the code of your DLL might help.
Jul 19 2012
next sibling parent Mike Parker <aldacron gmail.com> writes:
On 7/20/2012 5:17 AM, DLimited wrote:
 On Thursday, 19 July 2012 at 20:06:55 UTC, dnewbie wrote:
 On Thursday, 19 July 2012 at 19:51:31 UTC, DLimited wrote:
 Yes, I did. Are the newlines important?

 And you really get a MessageBox per keystroke? I start as admin,
 disabled my AV but still, no success.

Yes, I get 2 "WHOA" messages. One from the WM-KEYDOWN and the other from WM-KEYUP. Sorry I don't know what is wrong.

THANK YOU for your help! It works now! I didn't call Runtime.initialize; in my exported function because I thought the runtime would already be initialized in the DllMain - seems like that is not the case. Wouldn't have thought of it if I hadn't had your sample code, though! Thanks a ton!

The runtime is initialzed automatically in an executable because it has its own C 'main' entry point. That gets called first, the runtime does all its initialization, then it calls your D main function. In a DLL, the entry point is DllMain, not 'main'. So the runtime is not initialized at startup and you have to do it manually. The same holds true if you create an application with a WinMain entry point instead of main.
Jul 19 2012
prev sibling parent Mike Parker <aldacron gmail.com> writes:
On 7/20/2012 12:49 AM, DLimited wrote:
 But what are the differences of loading the Unicode version vs. the ANSI
 version?

You should always be using the Unicode version of Win32 functions in new applications. AFAIK, the ANSI versions call the Unicode versions internally, but do a conversion on the string args before doing so.
Jul 19 2012
prev sibling next sibling parent "DLimited" <tanojoshu googlemail.com> writes:
But what are the differences of loading the Unicode version vs. 
the ANSI version? I called the Unicode one because I figured that 
would be the sensible choice, since Unicode is the default for D 
(if I remember correctly). I have no clue what the actual effects 
of calling the wrong version would be.

Anyway, here's the of my .dll:

< ------ Code begin ------ >

import std.c.windows.windows;
import core.sys.windows.dll;
import core.runtime;


extern (C) void gc_init();
extern (C) void gc_term();
extern (C) void _minit();
extern (C) void _moduleCtor();
extern (C) void _moduleDtor();

extern (Windows) struct KBDLLHOOKSTRUCT {
   DWORD     vkCode;
   DWORD     scanCode;
   DWORD     flags;
   DWORD     time;
   ULONG_PTR dwExtraInfo;
};
extern (Windows) LRESULT CallNextHookEx(
     int function() hhk,
       int nCode,
        WPARAM wParam,
         LPARAM lParam
);


__gshared HINSTANCE g_hInst;


  extern (Windows) BOOL DllMain(HINSTANCE hInstance, ULONG 
ulReason, LPVOID pvReserved) {
	return true;
	switch (ulReason) {
		case DLL_PROCESS_ATTACH:
		g_hInst = hInstance;
		Runtime.initialize;
		//dll_process_attach( hInstance, true );
		break;
		
		case DLL_PROCESS_DETACH:
		dll_process_detach( hInstance, true );
		break;

		case DLL_THREAD_ATTACH:
		dll_thread_attach( true, true );
		break;
		case DLL_THREAD_DETACH:
		dll_thread_detach( true, true );
		break;
		
		default:
		return true;
	}
	return true;
}

extern (Windows) LRESULT LowLevelKeyboardProc(int code, WPARAM 
wParam, LPARAM lParam)
{
     KBDLLHOOKSTRUCT* details = cast(KBDLLHOOKSTRUCT*) lParam;
	MessageBoxA(null, cast(char *)"WHOA", "Error",
		    MB_OK | MB_ICONEXCLAMATION);
     if(code == 0 && wParam == WM_KEYDOWN)
     {
         if(details.vkCode == 0x41)
         {
			
             return 1;
         }
     }

     return CallNextHookEx(null, code, wParam, lParam);
}

< ------ Code End ------ >

Lots of copy&paste was used. I injected some senseless code to 
try and check if a specific function ever gets called, though I 
now realise the DllLoad itself is what fails. Haven't cleaned it 
back up yet, though.

The .def file contains the following: (including newlines)

< ------ .DEF BEGIN ------ >
LIBRARY "keydll.dll"
EXETYPE NT
SUBSYSTEM WINDOWS
CODE PRELOAD
DATA PRELOAD
< ------ .DEF END   ------ >


I compiled the dll using:
dmd -ofkeydll.dll -L/IMPLIB keydll.d keydll.def

No linker/compiler errors.
Jul 19 2012
prev sibling next sibling parent "dnewbie" <run3 myopera.com> writes:
On Thursday, 19 July 2012 at 15:49:48 UTC, DLimited wrote:
 But what are the differences of loading the Unicode version vs. 
 the ANSI version? I called the Unicode one because I figured 
 that would be the sensible choice, since Unicode is the default 
 for D (if I remember correctly). I have no clue what the actual 
 effects of calling the wrong version would be.

 Anyway, here's the of my .dll:

 < ------ Code begin ------ >

 import std.c.windows.windows;
 import core.sys.windows.dll;
 import core.runtime;


 extern (C) void gc_init();
 extern (C) void gc_term();
 extern (C) void _minit();
 extern (C) void _moduleCtor();
 extern (C) void _moduleDtor();

 extern (Windows) struct KBDLLHOOKSTRUCT {
   DWORD     vkCode;
   DWORD     scanCode;
   DWORD     flags;
   DWORD     time;
   ULONG_PTR dwExtraInfo;
 };
 extern (Windows) LRESULT CallNextHookEx(
     int function() hhk,
       int nCode,
        WPARAM wParam,
         LPARAM lParam
 );


 __gshared HINSTANCE g_hInst;


  extern (Windows) BOOL DllMain(HINSTANCE hInstance, ULONG 
 ulReason, LPVOID pvReserved) {
 	return true;
 	switch (ulReason) {
 		case DLL_PROCESS_ATTACH:
 		g_hInst = hInstance;
 		Runtime.initialize;
 		//dll_process_attach( hInstance, true );
 		break;
 		
 		case DLL_PROCESS_DETACH:
 		dll_process_detach( hInstance, true );
 		break;

 		case DLL_THREAD_ATTACH:
 		dll_thread_attach( true, true );
 		break;
 		case DLL_THREAD_DETACH:
 		dll_thread_detach( true, true );
 		break;
 		
 		default:
 		return true;
 	}
 	return true;
 }

 extern (Windows) LRESULT LowLevelKeyboardProc(int code, WPARAM 
 wParam, LPARAM lParam)
 {
     KBDLLHOOKSTRUCT* details = cast(KBDLLHOOKSTRUCT*) lParam;
 	MessageBoxA(null, cast(char *)"WHOA", "Error",
 		    MB_OK | MB_ICONEXCLAMATION);
     if(code == 0 && wParam == WM_KEYDOWN)
     {
         if(details.vkCode == 0x41)
         {
 			
             return 1;
         }
     }

     return CallNextHookEx(null, code, wParam, lParam);
 }

 < ------ Code End ------ >

 Lots of copy&paste was used. I injected some senseless code to 
 try and check if a specific function ever gets called, though I 
 now realise the DllLoad itself is what fails. Haven't cleaned 
 it back up yet, though.

 The .def file contains the following: (including newlines)

 < ------ .DEF BEGIN ------ >
 LIBRARY "keydll.dll"
 EXETYPE NT
 SUBSYSTEM WINDOWS
 CODE PRELOAD
 DATA PRELOAD
 < ------ .DEF END   ------ >


 I compiled the dll using:
 dmd -ofkeydll.dll -L/IMPLIB keydll.d keydll.def

 No linker/compiler errors.

I guess you have to 'export' the function: extern (Windows) export LRESULT LowLevelKeyboardProc(int code, WPARAM wParam, LPARAM lParam) and include EXPORTS LowLevelKeyboardProc in the .DEF file
Jul 19 2012
prev sibling next sibling parent "DLimited" <tanojoshu googlemail.com> writes:
 I guess you have to 'export' the function:
 extern (Windows) export LRESULT LowLevelKeyboardProc(int code, 
 WPARAM
 wParam, LPARAM lParam)

 and include
 EXPORTS
   LowLevelKeyboardProc

 in the .DEF file

Thanks, I changed that. Also, I changed LoadLibraryW( ) to LoadLibraryA( ) in the main program and now it works (kinda). I feel stupid now, although I still don't get why it wouldn't work with LoadLibraryW.
Jul 19 2012
prev sibling next sibling parent "DLimited" <tanojoshu googlemail.com> writes:
On Thursday, 19 July 2012 at 16:38:19 UTC, DLimited wrote:
 I guess you have to 'export' the function:
 extern (Windows) export LRESULT LowLevelKeyboardProc(int code, 
 WPARAM
 wParam, LPARAM lParam)

 and include
 EXPORTS
  LowLevelKeyboardProc

 in the .DEF file

Thanks, I changed that. Also, I changed LoadLibraryW( ) to LoadLibraryA( ) in the main program and now it works (kinda). I feel stupid now, although I still don't get why it wouldn't work with LoadLibraryW.

Acutally, that was only the half-truth. The .dll seems to get loaded correctly ( GetLastError returns 0), but my keyboard-presses aren't captured at all. My system seems to freeze up for ~5sec, after which everything resumes. Any keyboard input seems to get buffered and is processed by my terminal after my program closes. Also I'm unsure about types because the often-used HHOOK is not defined with my imports, so I'm left guessing what it is. I used the type int function() instead (my best guess). Here's the code: < ------- CODE BEGIN --------- > import std.c.windows.windows; import std.stdio; import core.thread; extern (C) void gc_init(); extern (C) void gc_term(); extern (C) void _minit(); extern (C) void _moduleCtor(); extern (C) void _moduleDtor(); extern (C) void _moduleUnitTests(); extern (Windows) int function() SetWindowsHookExA( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId ); extern (Windows) struct KBDLLHOOKSTRUCT { DWORD vkCode; DWORD scanCode; DWORD flags; DWORD time; ULONG_PTR dwExtraInfo; }; extern (Windows) LRESULT CallNextHookEx( int function() hhk, int nCode, WPARAM wParam, LPARAM lParam ); extern (Windows) bool UnhookWindowsHookEx( int function() hhk ); extern (Windows) HMODULE LoadLibraryA( LPCTSTR lpFileName ); extern (Windows) int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { int result; gc_init(); // initialize garbage collector _minit(); // initialize module constructor table try { _moduleCtor(); // call module constructors //_moduleUnitTests(); // run unit tests (optional) result = myWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow); _moduleDtor(); // call module destructors } catch (Exception o) // catch any uncaught exceptions { MessageBoxA(null, cast(char *)o.toString(), "Error", MB_OK | MB_ICONEXCLAMATION); result = 0; // failed } gc_term(); // run finalizers; terminate garbage collector return result; } int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HOOKPROC hkprcSysMsg; HINSTANCE hinstDLL; extern (Windows) int function() hhookSysMsg; hinstDLL = LoadLibraryA(cast(LPCTSTR)"correct_absolute_path\\keydll.dll"); writeln(GetLastError()); //returns 0 hkprcSysMsg = cast(HOOKPROC)GetProcAddress(hinstDLL, "LowLevelKeyboardProc"); writeln(GetLastError()); //return 0 hhookSysMsg = SetWindowsHookExA( 13, hkprcSysMsg, hinstDLL, 0); writeln(GetLastError()); // returns 0 aswell Thread.sleep( dur!("seconds")(10) ); UnhookWindowsHookEx( hhookSysMsg ); return 0; }
Jul 19 2012
prev sibling next sibling parent "dnewbie" <run3 myopera.com> writes:
You don't see the "WHOA" message?
Try this
alias HANDLE HHOOK;
Jul 19 2012
prev sibling next sibling parent "DLimited" <tanojoshu googlemail.com> writes:
On Thursday, 19 July 2012 at 17:35:29 UTC, dnewbie wrote:
 You don't see the "WHOA" message?
 Try this
 alias HANDLE HHOOK;

No, I don't get any message after key-presses. I changed int function() to HANDLE, sadly it still doesn't work.
Jul 19 2012
prev sibling next sibling parent "dnewbie" <run3 myopera.com> writes:
On Thursday, 19 July 2012 at 17:48:06 UTC, DLimited wrote:
 On Thursday, 19 July 2012 at 17:35:29 UTC, dnewbie wrote:
 You don't see the "WHOA" message?
 Try this
 alias HANDLE HHOOK;

No, I don't get any message after key-presses. I changed int function() to HANDLE, sadly it still doesn't work.

For some reason, it doesn't work with 'Thread.sleep' This works: http://dpaste.dzfl.pl/1e6e5960
Jul 19 2012
prev sibling next sibling parent "DLimited" <tanojoshu googlemail.com> writes:
On Thursday, 19 July 2012 at 18:40:15 UTC, dnewbie wrote:
 On Thursday, 19 July 2012 at 17:48:06 UTC, DLimited wrote:
 On Thursday, 19 July 2012 at 17:35:29 UTC, dnewbie wrote:
 You don't see the "WHOA" message?
 Try this
 alias HANDLE HHOOK;

No, I don't get any message after key-presses. I changed int function() to HANDLE, sadly it still doesn't work.

For some reason, it doesn't work with 'Thread.sleep' This works: http://dpaste.dzfl.pl/1e6e5960

It doesn't work for me. I can 1 Message Box from the Code in MyWinMain, but none for the Keystrokes. I registered a hook for Keyboard input, and that code is supposed to produce a message box aswell. The function for that is called LowLevelKeyboardProc and located in above-mentioned .dll file. The registering of the hook seems to pass, but the function never actually gets called.
Jul 19 2012
prev sibling next sibling parent "dnewbie" <run3 myopera.com> writes:
On Thursday, 19 July 2012 at 18:56:15 UTC, DLimited wrote:
 On Thursday, 19 July 2012 at 18:40:15 UTC, dnewbie wrote:
 On Thursday, 19 July 2012 at 17:48:06 UTC, DLimited wrote:
 On Thursday, 19 July 2012 at 17:35:29 UTC, dnewbie wrote:
 You don't see the "WHOA" message?
 Try this
 alias HANDLE HHOOK;

No, I don't get any message after key-presses. I changed int function() to HANDLE, sadly it still doesn't work.

For some reason, it doesn't work with 'Thread.sleep' This works: http://dpaste.dzfl.pl/1e6e5960

It doesn't work for me. I can 1 Message Box from the Code in MyWinMain, but none for the Keystrokes. I registered a hook for Keyboard input, and that code is supposed to produce a message box aswell. The function for that is called LowLevelKeyboardProc and located in above-mentioned .dll file. The registering of the hook seems to pass, but the function never actually gets called.

Did you add EXPORTS LowLevelKeyboardProc to the .DEF file? It works here.
Jul 19 2012
prev sibling next sibling parent "DLimited" <tanojoshu googlemail.com> writes:
On Thursday, 19 July 2012 at 19:43:45 UTC, dnewbie wrote:
 On Thursday, 19 July 2012 at 18:56:15 UTC, DLimited wrote:
 On Thursday, 19 July 2012 at 18:40:15 UTC, dnewbie wrote:
 On Thursday, 19 July 2012 at 17:48:06 UTC, DLimited wrote:
 On Thursday, 19 July 2012 at 17:35:29 UTC, dnewbie wrote:
 You don't see the "WHOA" message?
 Try this
 alias HANDLE HHOOK;

No, I don't get any message after key-presses. I changed int function() to HANDLE, sadly it still doesn't work.

For some reason, it doesn't work with 'Thread.sleep' This works: http://dpaste.dzfl.pl/1e6e5960

It doesn't work for me. I can 1 Message Box from the Code in MyWinMain, but none for the Keystrokes. I registered a hook for Keyboard input, and that code is supposed to produce a message box aswell. The function for that is called LowLevelKeyboardProc and located in above-mentioned .dll file. The registering of the hook seems to pass, but the function never actually gets called.

Did you add EXPORTS LowLevelKeyboardProc to the .DEF file? It works here.

Yes, I did. Are the newlines important? And you really get a MessageBox per keystroke? I start as admin, disabled my AV but still, no success.
Jul 19 2012
prev sibling next sibling parent "dnewbie" <run3 myopera.com> writes:
On Thursday, 19 July 2012 at 19:51:31 UTC, DLimited wrote:
 Yes, I did. Are the newlines important?

 And you really get a MessageBox per keystroke? I start as 
 admin, disabled my AV but still, no success.

Yes, I get 2 "WHOA" messages. One from the WM-KEYDOWN and the other from WM-KEYUP. Sorry I don't know what is wrong.
Jul 19 2012
prev sibling next sibling parent "DLimited" <tanojoshu googlemail.com> writes:
On Thursday, 19 July 2012 at 20:06:55 UTC, dnewbie wrote:
 On Thursday, 19 July 2012 at 19:51:31 UTC, DLimited wrote:
 Yes, I did. Are the newlines important?

 And you really get a MessageBox per keystroke? I start as 
 admin, disabled my AV but still, no success.

Yes, I get 2 "WHOA" messages. One from the WM-KEYDOWN and the other from WM-KEYUP. Sorry I don't know what is wrong.

THANK YOU for your help! It works now! I didn't call Runtime.initialize; in my exported function because I thought the runtime would already be initialized in the DllMain - seems like that is not the case. Wouldn't have thought of it if I hadn't had your sample code, though! Thanks a ton!
Jul 19 2012
prev sibling parent torhu <no spam.invalid> writes:
On 19.07.2012 13:45, DLimited wrote:
 Hello everyone,

 I had this great idea of writing a Program that intercepts all
 keyboard presses and modifies them in certain cases.
 I want to use it as some kind of global makro program to run in
 the background and for example allow me to easily post unicode
 smileys.

If you don't want make your own program, you can go here instead: http://www.autohotkey.com But you probably knew about it.
Jul 21 2012