这次需要实现的任务是:抓取键盘中每个键的VKcode和Scancode
有两种实现方式,重写PreTeanslateMessage函数和SetWindowsHook来调用hook的回调函数。
这两个的实现原理比较类似,都是在事件触发后,在传递到消息映射函数之前拦截消息信息,对消息信息进行处理
PreTranslateMessage函数是在传递到消息映射函数前进行处理,这个函数的返回值会传递到TranslateMessage函数中,但是采用SendMessage()或其他类似的方式向窗口直接发送的而不经过消息队列的消息根本不会理睬PreTranslateMessage()的存在,这个函数根据返回值来判断是不是需要将信息传递到消息映射函数中。
PreTranslateMessage函数的参数为MSG结构体的指针,
MSG结构具有如下形式:
typedef struct tagMSG
{ // msg
HWND hwnd;//当前消息发送的窗口句柄
UINT message;//当前消息的宏
WPARAM wParam;//wParam的低16位是键盘的VK_Code
LPARAM lParam;//lParam的高16位是scancode
DWORD time;//time是当前消息发送的时间戳
POINT pt;//发布消息时的光标位置(以屏幕坐标为相对位置)
} MSG;
MSG结构中包含了线程的消息队列中的消息信息。
关于wParam和lParam的解释可以看这个链接
wParam&lParam
typedef struct tagKBDLLHOOKSTRUCT {
DWORD vkCode;
DWORD scanCode;
DWORD flags;//0表示按下,128表示抬起
DWORD time;
ULONG_PTR dwExtraInfo;
} KBDLLHOOKSTRUCT, *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
关于VkCode和ScanCode,MSDN中有写道:
分配给键盘上每个键的唯一值称为扫描代码(ScanCode),ScanCode可能会因为厂商的不同而不一样,是键盘上键的设备相关标识符。 当用户键入某个键时,键盘会生成两个扫描代码,一个在用户按下该键时生成,另一个在用户松开该键时生成,BreakCode。
键盘设备驱动程序解释扫描代码并将其转换(映射)为虚拟键代码(Vk_Code),这是一个由系统定义的独立于设备的值,用于标识键的用途,这个是window系统定义的是唯一的。 转换扫描代码后,键盘布局会创建一条消息,其中包含扫描代码、虚拟键代码和有关击键的其他信息,然后将该消息置于系统消息队列中。 系统将该消息从系统消息队列中删除,并将其发布到相应线程的消息队列。
使用 SetWindowsHookExA SetWindowsHookExW函数来把钩子和钩子对应的回调函数链接起来,
SetWindowsHookExA 中的参数;
第一个是确定捕哪个设备,和键盘相关的事:WH_KEYBOARD和WH_KEYBOARD_LL函数
第二个参数是,捕捉到键盘信息后处理键盘信息的回调函数指针:HOOKPROC类型,需要注意的是HOOK和HOOKPROC函数需要设置为全局函数。
第三个参数需要设置当前实例/DLL的句柄
第四个函数是要监控的线程id,如果是想监视所有线程的id,设置第四个参数为0,如果不是,可以设置为监听当前的线程id
关于使用hook方法
使用的全局、静态变量有:
LRESULT CALLBACK HookProcKB(int code, WPARAM wParam, LPARAM lParam);
bool installHook();
static HHOOK hook;
static HWND hwnd;
void uninstallhook();
static DWORD scancode;//传递到界面中
static DWORD vkcode;//vkcode传递到界面中
static CString Keyname;//keyname传递到界面中
这里需要注意的是因为我是在dlg类中写的,所以这些变量函数都需要写在dlg类外,当然更美观的方法是写成xxhook.cpp , xxhook.h文件,还有一种方法是把钩子封装成dll库,这样对我这样的需求太麻烦了,而且当时用了dll库但是一直初始化的hook为null。另外使用钩子的时候,一定一定一定要记得释放钩子函数,我是在initdlg的时候install重写ondestroy的时候uninstall函数
我写的主要的函数是installhook,uninstallhook,KBHooProc函数:
- installhook函数
bool installHook()
{
//hook = SetWindowsHookEx(WH_KEYBOARD_LL,HookProcKB, AfxGetInstanceHandle(),0);//全局钩子
//hook = SetWindowsHookEx(WH_KEYBOARD, HookProcKB, NULL, GetCurrentThreadId());//当前线程钩子
hook = SetWindowsHookEx(WH_KEYBOARD_LL, HookProcKB, GetModuleHandle(NULL), NULL);//当前线程钩子
if (hook != NULL)return true;
return false;
}
- uninstallhook
void uninstallhook()
{
UnhookWindowsHookEx(hook);
if (hook != NULL)
{
hook = NULL;
}
}
- HookProcKB
LRESULT CALLBACK HookProcKB(int code, WPARAM wParam, LPARAM lParam)
{
CString str;
if (code == HC_ACTION)
{
KBDLLHOOKSTRUCT* pkb = (KBDLLHOOKSTRUCT*)lParam;
MSG* msg = (MSG*)lParam;
//WPARAM Param = msg->wParam;
if(/*pkb->flags==128||*/pkb->flags==0)
{
vkcode = pkb->vkCode;
//vkcode = msg->message;
scancode = pkb->scanCode;
char tp[1024] = { 0 };
GetKeyNameTextA(HIWORD(lParam), tp, 1024);
CString t(tp);
Keyname = t;
Keyname = "pq";
if (vkcode == VK_F1 &&
(CWnd::FromHandle(msg->hwnd))->IsKindOf(RUNTIME_CLASS(CKeyBoardCodeDlg)))
::SendMessage(hwnd, WM_Message, wParam, lParam);
switch (vkcode)
{
case VK_F1:
case VK_F2:
case VK_F3:
case VK_F4:
case VK_F5:
case VK_F6:
case VK_F7:
case VK_F8:
case VK_F9:
case VK_F10:
case VK_F11:
case VK_F12:
case VK_ESCAPE:
case VK_SNAPSHOT:
case VK_INSERT:
case VK_DELETE:
case VK_RETURN:
case VK_CAPITAL:
case VK_LWIN:
case VK_RWIN:
::SendMessage(hwnd, WM_Message, wParam, lParam);
return 1;
break;
default:
::SendMessage(hwnd,WM_KEYDOWN, wParam, lParam);
}
}
//}
//if (wParam == VK_LEFT)
//{
// CString tt;
// //tt.Format("%d", pkb->scanCode);
// //scancode = tt;
// //tt.Format("0x%x", pkb->vkCode);
// //vkcode = tt;
// vkcode = pkb->vkCode;
// scancode = pkb->scanCode;
// //PostMessage(WM_KEYDOWN, pkb->vkCode, pkb->scanCode);
// //PostMessage()
//
//}
/*vkcode = pkb->vkCode;
scancode = pkb->scanCode;*/
}
else
{
return CallNextHookEx(hook, code, wParam, lParam);
}
return CallNextHookEx(hook, code, wParam, lParam);
}
接下来是另一种方法,也是我个人比较倾向的方法,因为简单hhhhh
重写PreTransLateMessage函数,这个函数原本是虚函数,重写他:
- PreTranslateMessage
BOOL CKeyBoardCodeDlg::PreTranslateMessage(MSG* msg)
{
//edit1.SetReadOnly(true);
edit1.SetFocus();
///*if (msg->message == WM_CHAR)
//{
// char ch = (char)msg->wParam;
// CString strShortCut = (CString)ch;
// SetDlgItemText(EDIT1, "");
//
//}*/
//CString str;
//if (msg->message == WM_KEYDOWN)
//{
// if (msg->wParam == VK_ESCAPE || msg->wParam == VK_RETURN)return true;
// if (msg->wParam >= 0x01 && msg->wParam <= 0xFE)//VKCODE虚拟键
// {
//
// str.Format("0x%x", msg->wParam);
// SetDlgItemText(EDIT1, str);
// vkcode = LOWORD(msg->wParam);
// scancode = HIWORD(msg->lParam);
// CString tt;
// tt.Format("0x%x", vkcode);
// edit1.SetWindowTextA(tt);
// tt.Format("0x%x", scancode);
// edit3.SetWindowTextA(tt);
// char tp[1024];
// GetKeyNameTextA(msg->lParam, tp, 1024);
// CString t(tp);
// Keyname = t;
// Kname.SetWindowTextA(Keyname);
// //CString tt;
// /*tt.Format("%d", scancode);
// tt.Format(" 0x%x", vkcode);*/
//
// //SetDlgItemText(EDIT3,tt);
// //tt.Format("%d", changed);
// //SetDlgItemText(EDIT3, tt);
// if (hook == NULL)
// {
// SetDlgItemText(EDIT3, "hook is null");
// }
// else
// {
// //SetDlgItemText(EDIT3, "hook is not null");
// }
// return true;
// }
//}
//if (msg->message == WM_SYSKEYDOWN)return true;//禁用系统功能
//
if (msg->message == WM_SYSCOMMAND)return true;
if (msg->message == WM_KEYDOWN||msg->message==WM_SYSKEYDOWN)
{
//if (msg->wParam == VK_ESCAPE || msg->wParam == VK_RETURN)return true;
Keyname = "45";
Kname.SetWindowTextA("in Pre");
info.SetWindowTextA("tp");
vkcode = LOWORD(msg->wParam);
//vkcode = msg->message;
scancode = HIWORD(msg->lParam);
char tp[1024] = { 0 };
GetKeyNameTextA(msg->lParam, tp, 1024);
CString t(tp);
Keyname = t;
if (msg->wParam == VK_F1 &&
(CWnd::FromHandle(msg->hwnd))->IsKindOf(RUNTIME_CLASS(CKeyBoardCodeDlg)))
return true;//禁用F1 HELP键
switch (vkcode)
{
case VK_F1:
case VK_F2:
case VK_F3:
case VK_F4:
case VK_F5:
case VK_F6:
case VK_F7:
case VK_F8:
case VK_F9:
case VK_F10:
case VK_F11:
case VK_F12:
case VK_ESCAPE:
case VK_SNAPSHOT:
case VK_DELETE:
case VK_INSERT:
case VK_RETURN:
case VK_CAPITAL:
case VK_LWIN:
case VK_RWIN:
::SendMessage(hwnd, WM_Message, msg->wParam, msg->lParam);
return true;
default:
::SendMessage(hwnd, WM_KEYDOWN, msg->wParam, msg->lParam);
}
}
return CDialogEx::PreTranslateMessage(msg);
}
另外还有就是一些禁用不了的键的问题
比如F1键在函数中处理拦截之后,点击f1还是会报错,它显示是寻求帮助的界面,去重写界面的onHelp消息函数:
void CKeyBoardCodeDlg::WinHelp(DWORD dwData, UINT nCmd)
{
// TODO: 在此添加专用代码和/或调用基类
//把下面这段函数注释掉,help命令就不会传递到底层
//CDialogEx::WinHelp(dwData, nCmd);
}
禁用不了键的一些原因:
因为有些键是系统功能,点击之后直接传递给硬件来控制硬件,而不是传递到消息队列中
另外还有一些就是有些没有重写的消息函数已经提前处理过这个信号了,传递到Pre或者Hook的时候已经是处理好的信息了
目前想到的原因就是这几个
还有一下禁用不了的键和问题:
Win,弹出菜单界面
F12,计算器的功能
PtrSc:截屏
Caps:大写
Fn:直接就是找不到这个键的code
如果设置FnLock的话,所有的键就会被检测到且目标键会被拦截
如果不设置Fnlock,系统键就会被触发
Ps:什么时候我可以再用QT做业务而不是用MFC啊