We help IT Professionals succeed at work.

How to Detect WM_KEYUP and WM_KEYDOWN in a KeyboardHook procedure using WPARAM and LPARAM

ramavorray
ramavorray asked
on
9,123 Views
Last Modified: 2013-12-03
I have a KeyboardHook (as a DLL) which senses all keyboard activity and it is of type WH_KEYBOARD

The keyboard hook is working well and the keyboard callback procedure is called with parameters WPARAM and LPARAM

I am able to sense the key pressed (like VK_A, VK_DOWN) but I am not able to sense KEYUP/KEYDOWN

I tried many alternatives including code found online (even on EE) but it doesn't work.

When a key is hit my Keyboard Callback Procedure is being called multiple times instead of only one time (that is, when key is pressed)

Code snippets are attached.

I am using Visual Studio 2005 and running on Windows XP Professional 32bit system
//Keyboard Hook Procedure inside in the DLL which posts a message to my application
KEYDLL3_API LRESULT CALLBACK hookproc(int ncode,WPARAM wparam,LPARAM lparam)
{
	if(ncode>=0)
	{
		if((lparam & 0x80000000) == 0x00000000)//Check whether key was pressed(not released).
		{
			hwnd = FindWindow("#32770","Keylogger Exe");//Find application window handle
			PostMessage(hwnd,WM_USER+755,wparam,lparam);//Send info to app Window.
		}
	}
	return ( CallNextHookEx(hook,ncode,wparam,lparam) );//pass control to next hook in the hook chain.
}
 
 
 
//the keyboard callback function in my application which received 
//WPARAM and LPARAM. During the multiple number of times this function 
//is getting called it is always going to the line if(w == VK_SPACE || w == VK_DOWN). It is not resulting any KEYUP/KEYREPEAT events
LRESULT CKeyexeDlg::processkey(WPARAM w, LPARAM l)//This block processes the keystroke info.
{
	if (l & 0x80000000) // check bit 31 for up/down
	{
		//KEY UP
	}
	else
	{
		if (l & 0x40000000) // check bit 30 for previous up/down
			//KEY REPEAT
 
		else
		{
			if(w == VK_SPACE || w == VK_DOWN)
			{
				//the virtual character
			}
		}
	}
 
//I tried the other alternative. But it doesn't even enter into the switch block.
	LPMSG m=(LPMSG)l;
	switch(m->message)
	{
	case WM_KEYDOWN:
		l=l;
		break;
	case WM_KEYUP:
		break;
	case WM_CHAR:
		break;
	};

Open in new window

Comment
Watch Question

jkr
CERTIFIED EXPERT
Top Expert 2012

Commented:
Try the following:
//Keyboard Hook Procedure inside in the DLL which posts a message to my application
KEYDLL3_API LRESULT CALLBACK hookproc(int ncode,WPARAM wparam,LPARAM lparam)
{
    PMSG    pmsg    =   ( PMSG) lparam;
 
    if  (   0   >   ncode   ||  PM_NOREMOVE ==  wparam) 
        {
            return  (   CallNextHookEx  (   hook,
                                            ncode,
                                            wparam,
                                            lparam
                                        )
                    );
        }
 
    if  (       WM_KEYDOWN  ==  pmsg->message
            ||  WM_KEYUP    ==  pmsg->message   
        )
        {
           // check if you get here
        }
 
    return  (   CallNextHookEx  (   hook,
                                    ncode,
                                    wparam,
                                    lparam
                                )
            );
}

Open in new window

Author

Commented:
No, It is not reaching the point you indicated. I tried two alternatives. Please see the code below.
//Tried this as you indicated. No messages are Posted at all, either for keyup or keydown.
KEYDLL3_API LRESULT CALLBACK hookproc(int ncode,WPARAM wparam,LPARAM lparam)
{
	if(0   >   ncode   ||  PM_NOREMOVE ==  wparam)
		return ( CallNextHookEx(hook,ncode,wparam,lparam) );//pass control to next hook in the hook chain.
 
	PMSG msg = (PMSG) lparam;
	if(msg->message == WM_KEYDOWN || msg->message == WM_KEYUP)
	{
		hwnd = FindWindow("#32770","Keylogger Exe");//Find application window handle
		PostMessage(hwnd,WM_USER+755,wparam,lparam);//Send info to app Window.
	}
 
	return ( CallNextHookEx(hook,ncode,wparam,lparam) );//pass control to next hook in the hook chain.
}
 
//thought of checking where the control is going and tested with. Messages are posted. So, the checks of WM_KEYUP/DOWN are not being sensed!!! and I am not understanding why!
KEYDLL3_API LRESULT CALLBACK hookproc(int ncode,WPARAM wparam,LPARAM lparam)
{
	if(0   >   ncode   ||  PM_NOREMOVE ==  wparam)
		return ( CallNextHookEx(hook,ncode,wparam,lparam) );//pass control to next hook in the hook chain.
 
	PMSG msg = (PMSG) lparam;
	if(msg->message == WM_KEYDOWN || msg->message == WM_KEYUP)
	{
		//hwnd = FindWindow("#32770","Keylogger Exe");//Find application window handle
		//PostMessage(hwnd,WM_USER+755,wparam,lparam);//Send info to app Window.
	}
 
	hwnd = FindWindow("#32770","Keylogger Exe");//Find application window handle
	PostMessage(hwnd,WM_USER+755,wparam,lparam);//Send info to app Window.
 
	return ( CallNextHookEx(hook,ncode,wparam,lparam) );//pass control to next hook in the hook chain.
}
 
//For your reference below is the function which installs hook in DLL
KEYDLL3_API void installhook(HWND h)
{
	hook = NULL;
	hwnd = h;
	hook = SetWindowsHookEx(WH_KEYBOARD,hookproc,hinstance,NULL);
	if(hook==NULL)
		MessageBox(NULL,"Unable to install hook","Error!",MB_OK);
}
 
//in my application which receives event message is mapped as
#define WM_KEYSTROKE (WM_USER + 755)
 
//all are defined well and working well, except the sensing of
//WM_KEYUP / WM_KEYDOWN

Open in new window

jkr
CERTIFIED EXPERT
Top Expert 2012

Commented:
Hmm, stupid question - how did you check that this point is not reached? I took the above from a working project that successfully detects the keypresses (the purpose was to remap some keys, and that works fine)

Author

Commented:
If that point is reached, the procedure in my application which should be called as a result of the posted message should be called.

I can say that this point is not reached as the hook procedure in my application is not called.

It was called in the second situation (where I had put postmessage outside the if...block)

Please get the source code http://www.cs.ucl.ac.uk/staff/R.Vorray/Keyexe.zip A zip file which contains hook DLL project and exe project. please check. LINE NUMBER 241 in KeyExeDlg.cpp in KeyExe project should be reached when everything is working.

Please put the generated Debug DLL in the Debug folder of KeyExe. All include paths are arranged correct.
jkr
CERTIFIED EXPERT
Top Expert 2012

Commented:
Hm, try to just place an

OutputDebugString("Keypress detected\n");

inside the 'if' body and check the output with DebugView (http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx). Chances are that something goes wrong when communicating the event.
Commented:
This one is on us!
(Get your first solution completely free - no credit card required)
UNLOCK SOLUTION

Author

Commented:
But when WH_GETMESSAGE is used wParam cannot be used to get virtual keycodes!

I cannot do checks like, if(wParam == VK_SPACE)

Also I need to use ToUnicodeEx(...) function to translate the wParam value into a unicode character based on current keyboard layout.

This no longer works when WH_GETMESSAGE is used!!!?

Commented:
For the WH_GETMESSAGE hook, the lParam points to a MSG structure.  If you look in that structure for (message == WM_KEYDOWN), then for that message, the wParam of that structure will contain the virtual key code, and the lParam of that structure will contain the key data.

Is there something here that you need that I am missing?

Author

Commented:
when typecasted lparam into PMSG in the hook handler procedure inside my applicatin, it is giving me Access Violation exception!

Please see the code below and please consider me a beginner in this regard.
//This is what I am doing inside my hook handler
LRESULT CAI_TalapatraDlg::ProcessKeyboard(WPARAM w, LPARAM l)
{
  PMSG msg = (PMSG)lparam;
  if(msg->message == WM_KEYDOWN)   //access violation error with message.
  //debugger shows invalid data for msg
  {
        if(msg->wParam == VK_SPACE || msg->wParam == VK_DOWN)
		//do stuff
  }
}
 
 
//here is the message posting function inside the DLL. Here everything works fine and the procedure above inside my application is called only once, but not able to process the virtual key code.
KEYHOOK_API LRESULT CALLBACK Hookproc(int nCode, WPARAM wparam, LPARAM lparam)
{
	if(0   >   nCode   ||  PM_NOREMOVE ==  wparam)
                return ( CallNextHookEx(hook,nCode,wparam,lparam) );//pass control to next hook in the hook chain.
 
    PMSG msg = (PMSG) lparam;
    if(msg->message == WM_KEYDOWN)
    {
		hwnd=FindWindow(NULL, _T("AI Talapatra"));
		if(hwnd!=NULL)
			PostMessage(hwnd, WM_USER+3, wparam, lparam);
	}
 
	return (CallNextHookEx(hook, nCode, wparam, lparam));
}

Open in new window

Commented:
Did you change your call to CreateWindowsHookEx to use WH_GETMESSAGE instead of WH_KEYBOARD?

Author

Commented:
sorry there was a typo in the code in previous comment. Please consider code below for the first half.
//This is what I am doing inside my hook handler
LRESULT CAI_TalapatraDlg::ProcessKeyboard(WPARAM w, LPARAM l)
{
  PMSG msg = (PMSG)l;
  if(msg->message == WM_KEYDOWN)   //access violation error with message.
  //debugger shows invalid data for msg
  {
        if(msg->wParam == VK_SPACE || msg->wParam == VK_DOWN)
                //do stuff
  }
}

Open in new window

Author

Commented:
Yes. now I am using WH_GETMESSAGE
KEYHOOK_API void Installhook(HWND h)
{
	hook = NULL;
	hwnd = h;
	hook = SetWindowsHookEx(WH_GETMESSAGE, Hookproc, hinstance, NULL);
 
	if(hook == NULL)
		MessageBox(NULL, _T("Unable to Install Hook"), _T("Error"), MB_OK);
}

Open in new window

Author

Commented:
This is what debugger is showing inside the function ProcessKeyboard(WPARAM w, LPARAM l) inside my application.

This conversion is working well in the DLL but not inside my application.


msg              0x0023f7e8 {msg=??? wp=??? lp=???}      tagMSG *
hwnd      CXX0030: Error: expression cannot be evaluated      
message      CXX0030: Error: expression cannot be evaluated      
wParam      CXX0030: Error: expression cannot be evaluated      
lParam      CXX0030: Error: expression cannot be evaluated      
time              CXX0030: Error: expression cannot be evaluated      
pt              {x=??? y=???}      tagPOINT

Commented:
You cannot post the pointer to the MSG structure like that.  By the time the posted message reaches your application, the memory to which the pointer is pointing is likely to no longer be valid.  You need to extract the data from the structure and post that to your application.

Author

Commented:
when key hits are made In all other applications except MS-Word my hook handler ProcessKeyboard(...) is being called once, indicating Wm_KEYDOWN; But when I type something in MS-Word ProcessKeyboard is being called four times as it did previously. Why is WM_KEYDOWN not being sensed from within MS-Word?

Now, I am posting msg->wParam and msg->lParam as two parameters inside PostMessage function.

below is the code for HookProc. WM_USER + 3 message is being posted four times from MS_WORD. Any clue on this?
KEYHOOK_API LRESULT CALLBACK Hookproc(int nCode, WPARAM wparam, LPARAM lparam)
{
	if(0   >   nCode   ||  PM_NOREMOVE ==  wparam)
                return ( CallNextHookEx(hook,nCode,wparam,lparam) );//pass control to next hook in the hook chain.
 
    PMSG msg = (PMSG) lparam;
    if(msg->message == WM_KEYDOWN)
    {
		hwnd=FindWindow(NULL, _T("AI Talapatra"));
		if(hwnd!=NULL)
			PostMessage(hwnd, WM_USER+3, msg->wParam, msg->lParam);
	}
 
	return (CallNextHookEx(hook, nCode, wparam, lparam));
}

Open in new window

Commented:
I am not sure I can help you any further.  Oddly enough, a while back I was trying to help another user intercept keys, and he too has some difficulty getting it to work with Word.  As I recall, he was able to get the keyup/down messages, but not the WM_CHAR messages.  It worked with all other apps, but not with Word.  Here is the link to that discussion.

https://www.experts-exchange.com/Programming/Languages/CPP/Q_23471531.html

I am not sure if he ever solved the problem, but perhaps if you posted a comment on that question, he might see it, and answer.
 

Author

Commented:
Hi JohnGaby,

I will deal with the MS-Word thing later on. Now another messy problem I am facing with your approach is that I am not able to process SHIFT + key combinations

the wParam is returning 'a' instead of 'A' when I press SHIFT + 'a'

I think it is calling my hook procedure once for SHIFT key press and once from 'a' key press. How to handle this situation?

Author

Commented:
I am using ToUnicodeEx(...) function to translate the wParam value into Unicode characters, as I deal with non-english languages aswell.

I get keyboard state using GetKeyboardState(..) and pass the resulting array as one of the arguments to ToUnicodeEx(...) but keyboardsate array here is not possessing the keydown bit for SHIFT key as by the time it is called for 'a' in SHIFT + 'a' shift key is down!

Any clue in this regard?

Author

Commented:
sorry in my previous comment please read the last line as "...by the time it is called for 'a' in SHIFT+'a' shift key is UP!

Author

Commented:
even if pressed 'a' or SHIFT + 'a' buf contains only 'a'. How can I get 'A' here out of ToUnicodeEx(...) function?
LRESULT CAI_TalapatraDlg::ProcessKeyboard(WPARAM w, LPARAM l)
{
 BYTE ks[256];
 WCHAR buf[10];
 UINT scan=0;
 
 GetKeyboardState(ks);
 
 if(ToUnicodeEx(w, scan, ks, buf, 1, 1, m_hklval))
{
        ......//even if pressed 'a' or SHIFT + 'a' buf contains only 'a'
}
 
}

Open in new window

Commented:
If you want characters, instead of keys, you should be looking for the WM_CHAR message, rather than the WM_KEYDOWN message.  The WM_KEYDOWN messages returns the raw 'virtual' keys.  The WM_CHAR message is generated when you call the TranslateMessage function in your message loop, and should give you unicode characters if you have compiled using the unicode character set.

Author

Commented:

PMSG msg = (PMSG) lparam;
    if(msg->message == WM_CHAR)
    {
		hwnd=FindWindow(NULL, _T("AI Talapatra"));
		if(hwnd!=NULL)
			PostMessage(hwnd, WM_USER+3, msg->wParam, msg->lParam);
	}
 
//in the above code (when using WM_CHAR) what does wParam and lParam indicate?
 
//How do I specify that my WM_USER+3 message is posted only when Key is down but not otherwise
 
//wParam the unicode character?
 
//then which variable indicates other key hits like VK_SPACE, VK_BACK, etc...

Open in new window

Author

Commented:
This is how my current code looks, which sends key hits to my application called "AI Talapatra". Can I just change WM_KEYDOWN to WM_CHAR and wParam is an Unicode Character? If so, how to sense SPACE, BACKSPACE. etc.
KEYHOOK_API LRESULT CALLBACK Hookproc(int nCode, WPARAM wparam, LPARAM lparam)
{
    if(0   >   nCode   ||  PM_NOREMOVE ==  wparam)
               return ( CallNextHookEx(hook,nCode,wparam,lparam) ); //pass control to next hook in the hook chain.
 
    PMSG msg = (PMSG) lparam;
    if(msg->message == WM_KEYDOWN)
    {
		hwnd=FindWindow(NULL, _T("AI Talapatra"));
		if(hwnd!=NULL)
		PostMessage(hwnd, WM_USER+3, msg->wParam, msg->lParam);
    }
 
return (CallNextHookEx(hook, nCode, wparam, lparam));
}

Open in new window

Commented:
Here is the manual page for the WM_CHAR message:

http://msdn.microsoft.com/en-us/library/ms646276.aspx

The wParam contains the unicode (UTF16) character.  The lParam is not particularly useful.  If you are looking for the space character, compare (wParam == ' ').  For the backspace look for (wParam == '\b').

Note that you WILL have problems with this message when hooking MS Word (and I believe Excel also).

Author

Commented:
When did so I am getting a numerical value in msg->wParam which is posted to my application. (eg., 3108).

How to translate it into an appropriate character?

Commented:
I am not sure what is wrong.  Here is some code that uses the WH_GETMESSAGE hook to hook notepad, and replace the character 'x' with 'y' each time it is typed.  This code works with no problem.  Perhaps you can compare this to what you are doing and figure out what is wrong.

It consists of two pieces.  The first is the code for the dll which sets and process the hook.  The second is for a console app which finds an instance of notepad and calls the dll to set the hook.

// HookDll.cpp
//
// This is the DLL which both sets the hook and
//      subclasses the edit window for the notepad app
//      which is hooked
//
 
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
 
HINSTANCE   hMod;
HHOOK       hHook;
 
BOOL APIENTRY DllMain(  HANDLE hModule, 
                        DWORD  ul_reason_for_call, 
                        LPVOID lpReserved)
{
    hMod	= (HINSTANCE) hModule;
 
    return TRUE;
}
 
#ifdef __cplusplus
extern "C" {
#endif
 
LRESULT CALLBACK HookProc(int code, WPARAM wParam, LPARAM lParam)
{
    LRESULT	ret	= 0;
 
    if (code == HC_ACTION)
    {
        MSG		*	pMsg	= (MSG *) lParam;
 
        if (pMsg->message == WM_CHAR)
        {
            if (pMsg->wParam == 'x')
            {
                pMsg->wParam	= 'y';
            }
        }
    }
 
    return(CallNextHookEx(hHook, code, wParam, lParam));
}
 
__declspec(dllexport) BOOL SetHook(DWORD threadId)
{
    hHook = SetWindowsHookEx(WH_GETMESSAGE, HookProc, hMod, threadId);
 
    return(hHook ? TRUE : FALSE);
}
 
__declspec(dllexport) void ClearHook(void)
{
    if (hHook)
    {
        UnhookWindowsHookEx(hHook);
        hHook	= 0;
    }
}
 
#ifdef __cplusplus
}
#endif
 
// GlobalHook.cpp
//
// This is a console application which find an instance of notepad.exe
//  and sets a hook into it's process
//
 
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <conio.h>
 
#ifdef __cplusplus
extern "C" {
#endif
 
__declspec(dllimport) BOOL SetHook(DWORD threadId);
__declspec(dllimport) void ClearHook(void);
 
#ifdef __cplusplus
}
#endif
 
int _tmain(int argc, _TCHAR* argv[])
{
    HWND	hEdit;
 
    /*
     * Find an instance of notepad to hook
     */
    if (hEdit = FindWindow(_T("Notepad"), 0))
    {
        DWORD	threadId = 0;
        DWORD	processId;
 
        /*
         * Get the thread ID for the window
         */
        if (threadId = GetWindowThreadProcessId(hEdit, &processId))
        {
            printf("Setting hook\n");
 
            /*
             * Set a hook for this thread
             */
            if (SetHook(threadId))
            {
                printf("Hook set, press any key to clear hook\n");
                getch();
                printf("Clearing hook\n");
                ClearHook();
            }
            else
            {
                printf("Cannot set hook\n");
            }
        }
    }
    else
    {
		printf("Cannot find an instance of word\n");
    }
 
    return 0;
}

Open in new window

Author

Commented:
Here you are comparing character with 'x' but similarly can you compare it with '' a Hindi character. Also how can this wParam be converted into a string. ToUnicodeEx(...) function cannot understand the value of wParam.

It is able to understand the value of wParam with WM_KEYDOWN, but with WM_KEYDOWN I am not able to sense SHIFT.

for example, key 'k' gives a character and also SHIFT + K gives another characeter. WM_KEYDOWN is working for sensing single key strokes but not SHIFT + K.

WM_CHAR is giving a numerical value to wParam (because non-english keyboard layout has been loaded) and I am not understanding how to convert it into a string.

Commented:
I have not tried this with unicode characters, but according to the manual page, the wParam is the UTF16 unicode character.  This means that it is already converted to unicode (assuming you have compiled your application using the unicode character set).  To compare it to a Hindi character, you would need to know the UTF16 value for the Hindi character.

I cannot tell you much more.  Handling languages other than English (especially ones which require the UTF16 character set) is not my area of expertise.  Perhaps another expert can help you out there.

Author

Commented:
Complete solution hasn't been found but my original question has been answered.

Gain unlimited access to on-demand training courses with an Experts Exchange subscription.

Get Access
Why Experts Exchange?

Experts Exchange always has the answer, or at the least points me in the correct direction! It is like having another employee that is extremely experienced.

Jim Murphy
Programmer at Smart IT Solutions

When asked, what has been your best career decision?

Deciding to stick with EE.

Mohamed Asif
Technical Department Head

Being involved with EE helped me to grow personally and professionally.

Carl Webster
CTP, Sr Infrastructure Consultant
Empower Your Career
Did You Know?

We've partnered with two important charities to provide clean water and computer science education to those who need it most. READ MORE

Ask ANY Question

Connect with Certified Experts to gain insight and support on specific technology challenges including:

  • Troubleshooting
  • Research
  • Professional Opinions
Unlock the solution to this question.
Join our community and discover your potential

Experts Exchange is the only place where you can interact directly with leading experts in the technology field. Become a member today and access the collective knowledge of thousands of technology experts.

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

OR

Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.