MFC自带的键盘监听功能只有焦点在MFC程序界面时才能监听键盘消息,要想在MFC程序界面外监听键盘消息,可以通过DLL注入使用全局钩子来监听。首先,通过Visual Studio生成全局钩子的动态库,再在MFC程序中调用动态库。
一、生成dll
1.Visual Studio新建空项目(为了方便调试,我将dll项目保存在调用项目里新建的Hook文件夹中)
2.项目属性将配置类型设为动态库,字符集设为Unicod字符集(如果设为多字节字符集,部分代码需作修改)
3.创建Keyboard.h和Keyboard.cpp这两个文件
(1)Keyboard.h声明组合键类和键位值
#pragma once
#ifdef MATHLIBRARY_EXPORTS
#define MATHLIBRARY_API __declspec(dllexport)
#else
#define MATHLIBRARY_API __declspec(dllimport)
#endif
#include <iostream>
#include "windows.h"
//#include <conio.h>
using namespace std;
#pragma warning(disable:4996)
//extern "C" MATHLIBRARY_API 从cpp中将变量、类和函数导入到h中,这样调用dll的程序才能使用这些变量、类和函数
//extern "C":外部变量声明,MATHLIBRARY_API指本dll,只有声明外部变量、外部类和外部函数,调用dll的程序才能使用这些变量、类和函数
//外部变量的声明以及定义必需写在cpp文件上,再extern "C" MATHLIBRARY_API,这样dll和调用dll的程序才能使用这些变量、类和函数
extern "C" MATHLIBRARY_API BOOL InstallHook(HWND callframe);
extern "C" MATHLIBRARY_API BOOL UninstallHook();
WH_KEYBOARD时的键值wParam
//#define F1 173
//#define F2 174
//#define F3 175
//#define F4 115
//#define F5 116
//#define F6 54
//#define F7 118
//#define F8 119
//#define F9 120
//#define F10 121
//#define F11 122
//#define F12 123
//
//#define Letter_A 65
//#define Letter_B 66
//#define Letter_C 67
//#define Letter_D 68
//#define Letter_E 69
//#define Letter_F 70
//#define Letter_G 71
//#define Letter_H 72
//#define Letter_I 73
//#define Letter_J 74
//#define Letter_K 75
//#define Letter_L 76
//#define Letter_M 77
//#define Letter_N 78
//#define Letter_O 79
//#define Letter_P 80
//#define Letter_Q 81
//#define Letter_R 82
//#define Letter_S 83
//#define Letter_T 84
//#define Letter_U 85
//#define Letter_V 86
//#define Letter_W 87
//#define Letter_X 88
//#define Letter_Y 89
//#define Letter_Z 90
//
//#define Tab 9
//#define CapsLk 20
//#define Wave 192//·~
//#define Ctrl 17
BYTE Fn = FALSE; //Fn无法监听,按Fn+F1-F12切换到F1-F12,否则键位功能为红色字体上方字体代表的功能
//#define Win 91
//#define Alt 18
//#define Enter 13
//#define Shift 16
//#define Space 32
//#define Esc 27
//#define Insert 45
//#define NumLock 144
//#define PrtSc 44
//#define Delete 46
//#define Home 36
//#define End 35
//#define PgUp 33
//#define PgDn 34
//#define BackSpace 8
//#define Mouse_RButton 93 //空格键右边第二个键位,有鼠标右键的作用
//
//#define Operator_divide 111// / == ?/
//#define Operator_plus 107// +
//#define Operator_multiply 106// *
//#define Operator_subtract 109// -
//#define Decimal_point 110
//#define Number_0 96
//#define Number_1 97
//#define Number_2 98
//#define Number_3 99
//#define Number_4 100
//#define Number_5 101
//#define Number_6 102
//#define Number_7 103
//#define Number_8 104
//#define Number_9 105
//
//#define Left 37
//#define Right 39
//#define Up 38
//#define Down 40
Letter area
//#define LetterArea_number_0 48// )
//#define LetterArea_number_1 49// !
//#define LetterArea_number_2 50// @
//#define LetterArea_number_3 51// #
//#define LetterArea_number_4 52// $
//#define LetterArea_number_5 53// %
//#define LetterArea_number_6 54// ^
//#define LetterArea_number_7 55// &
//#define LetterArea_number_8 56// *
//#define LetterArea_number_9 57// (
//#define subtract 189// -_
//#define plus_equal 187// + =
//#define Left_brackets 219// [{
//#define Right_brackets 221// ]}
//#define Stop_sign 220// \|
//#define Semicolon 186// ;:
//#define Quotation_marks 222// '"
//#define Comma 188// ,<
//#define Full_stop 190// .>
//WH_KEYBOARD_LL时的键值vkCode
//BYTE Fn = FALSE; //Fn无法监听,按Fn+F1-F12切换到F1-F12,否则键位功能为红色字体上方字体代表的功能
#define F1 112
#define F2 113
#define F3 114
#define F4 115
#define F5 116
#define F6 117
#define F7 118
#define F8 119
#define F9 120
#define F10 121
#define F11 122
#define F12 123
#define Letter_A 65
#define Letter_B 66
#define Letter_C 67
#define Letter_D 68
#define Letter_E 69
#define Letter_F 70
#define Letter_G 71
#define Letter_H 72
#define Letter_I 73
#define Letter_J 74
#define Letter_K 75
#define Letter_L 76
#define Letter_M 77
#define Letter_N 78
#define Letter_O 79
#define Letter_P 80
#define Letter_Q 81
#define Letter_R 82
#define Letter_S 83
#define Letter_T 84
#define Letter_U 85
#define Letter_V 86
#define Letter_W 87
#define Letter_X 88
#define Letter_Y 89
#define Letter_Z 90
#define Tab 9
#define CapsLk 20
#define Wave 192//·~
#define Ctrl 162
#define Ctrl_R 163
#define Alt 164
#define Alt_R 165
#define Shift 160
#define Shift_R 161
#define Space 32
#define Win 91
#define Enter 13
#define Esc 27
#define Insert 45
#define NumLock 144
#define PrtSc 44
#define Delete 46
#define Home 36
#define End 35
#define PgUp 33
#define PgDn 34
#define BackSpace 8
#define Mouse_RButton 93 //空格键右边第二个键位,有鼠标右键的作用
#define Operator_divide 111// /
#define Operator_plus 107// +
#define Operator_multiply 106// *
#define Operator_subtract 109// -
#define Decimal_point 110
#define Number_0 96
#define Number_1 97
#define Number_2 98
#define Number_3 99
#define Number_4 100
#define Number_5 101
#define Number_6 102
#define Number_7 103
#define Number_8 104
#define Number_9 105
#define Left 37
#define Right 39
#define Up 38
#define Down 40
//Letter area
#define LetterArea_number_0 48// )
#define LetterArea_number_1 49// !
#define LetterArea_number_2 50// @
#define LetterArea_number_3 51// #
#define LetterArea_number_4 52// $
#define LetterArea_number_5 53// %
#define LetterArea_number_6 54// ^
#define LetterArea_number_7 55// &
#define LetterArea_number_8 56// *
#define LetterArea_number_9 57// (
#define subtract 189// -_
#define plus_equal 187// + =
#define Left_brackets 219// [{
#define Right_brackets 221// ]}
#define Stop_sign 220// \|
#define Semicolon 186// ;:
#define Quotation_marks 222// '"
#define Comma 188// ,<
#define Full_stop 190// .>
#define Question_mark 191// ?/
//加MATHLIBRARY_API后表示外部类
class ShortcutKey
{
public:
BYTE key[3];//组合键
int index = 0;//组合键键位数量
ShortcutKey(BYTE key_1 = 0, BYTE key_2 = 0, BYTE key_3 = 0)
{
this->key[0] = key_1;
this->key[1] = key_2;
this->key[2] = key_3;
}
//在组合键中添加一个键位
void add(BYTE key_code)
{
if (index < 3)
{
key[index] = key_code;
index++;
}
}
//获取组合键中最后一个键位
BYTE getkey()
{
if (index != 0)
return key[index - 1];
else
return 0;
}
//删除组合键中所有键位
void deleteAllkey()
{
for (int i = 0; i < 3; i++)
key[i] = 0;
index = 0;
}
//删除组合键中最后一个键位
void deletekey()
{
if (index>0)
{
key[index - 1] = 0;
index--;
}
}
};
键位值是我电脑的键位在全局钩子中对应的数值,其它电脑可能不一样,如果不对自己调一下。
(2)Keyboard.cpp定义
#include "Keyboard.h"
//#include <conio.h>
#include <windows.h>
#include <stdio.h>
// 项目的名称,注意这个项目的名称要和最后导出的DLL的文件名一致
#define PROJECT_NAME L"Hook"
HHOOK g_hHook = NULL;// 全局钩子
HWND callframe = NULL;//发送窗口
PKBDLLHOOKSTRUCT pKeyboardHookStruct = NULL;
int keycode = 0;
boolean keydown = false;//按键标识
int lastkeycode = 0;//上次按键键值
COPYDATASTRUCT cpd;//发送数据结构
ShortcutKey Listenkey;//当前监听键
// 键盘回调
LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code < 0 || code == HC_NOREMOVE)
{
// 如果代码小于零,则挂钩过程必须将消息传递给CallNextHookEx函数,而无需进一步处理,并且应返回CallNextHookEx返回的值。此参数可以是下列值之一。(来自官网手册)
return CallNextHookEx(g_hHook, code, wParam, lParam);
}
//获取键盘值
pKeyboardHookStruct = (PKBDLLHOOKSTRUCT)lParam;
keycode = pKeyboardHookStruct->vkCode;
//键值纠正
switch (keycode)
{
case Ctrl_R:
keycode = Ctrl;
break;
case Shift_R:
keycode = Shift;
break;
case Alt_R:
keycode = Alt;
break;
default:
break;
}
//如果按键,判断键值是否是指定键
if (pKeyboardHookStruct->flags<128)
{
//如果按下键不是上次的键,组合成组合键,发送当前组合键信息到指定窗口
if (lastkeycode != keycode)
{
Listenkey.add(keycode);
lastkeycode = keycode;
cpd.dwData = 0;//dwData可以是任意值,
cpd.cbData = sizeof(ShortcutKey);//指定lpData内存区域的字节数
cpd.lpData = &Listenkey;//发送给目录窗口所在进程的数据
SendMessage(callframe, WM_COPYDATA, NULL, (LPARAM)&cpd);//发送组合键数据到调用窗口中
}
}
//如果松开键,删除组合键键位
if (pKeyboardHookStruct->flags >= 128)
{
//如果松开的是组合键最后的键位,删除最后的键位,否则删除整个组合键
if (keycode == lastkeycode)
{
Listenkey.deletekey();
lastkeycode = Listenkey.getkey();
}
else
{
Listenkey.deleteAllkey();
lastkeycode = 0;
}
}
// 将钩子往下传
return CallNextHookEx(g_hHook, code, wParam, lParam);
}
// 安装钩子
BOOL InstallHook(HWND frame)
{
// 【参数1】钩子的类型,这里代表键盘钩子
// 【参数2】钩子处理的函数
// 【参数3】获取模块,PROJECT_NAME为DLL的项目名称
// 【参数4】线程的ID,如果是全局钩子的话,这里要填0,如果是某个线程的钩子,那就需要写线程的ID
callframe = frame;//获取调用窗口HWND
g_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, GetModuleHandle(PROJECT_NAME), 0);
//WH_KEYBOARD wParam为各键代表值,WH_KEYBOARD_LL不是,而是键的按下和抬起状态,但WH_KEYBOARD时ctrl+space被搜狗输入法拦截,而WH_KEYBOARD_LL不会。
if (g_hHook == NULL)
{
// 钩子安装失败
MessageBox(NULL, L"全局钩子1注册失败", L"信息", MB_OK);
return FALSE;
}
return TRUE;
}
// 卸载钩子,返回是否卸载成功
BOOL UninstallHook()
{
return UnhookWindowsHookEx(g_hHook);
}
在回调函数KeyboardProc中将按键组合成组合键,同时通过SendMessage将组合键数据发送给调用窗口。
4.生成Hook.dll
二、MFC调用
1.dll文件导入MFC项目
(1)将Hook.dll文件放在MFC项目的Debug文件夹内(即exe文件所在文件夹)
(2)导入Hook.lib文件和Hook.h文件
注:如果dll代码修改了,需重新生成dll,并将新生成的dll替换Debug文件夹内旧文件。
2.调用dll,通过OnCopyData接收按键信息,并作指定组合键匹配
#include "Hook/Hook/Keyboard.h"
ShortcutKey* currentkey;//当前按键
ShortcutKey* key1 = new ShortcutKey(Ctrl, Space);//快捷键
BOOL CScreenDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
//安装钩子
//AllocConsole();//打开控制台
InstallHook(this->m_hWnd);//安装钩子
return TRUE;
}
void CScreenDlg::OnDestroy()
{
CDialogEx::OnDestroy();
// TODO: 在此处添加消息处理程序代码
UninstallHook();//卸载钩子
//FreeConsole();//关闭控制台
}
//接收键盘监听dll发送的键盘监听信息
BOOL CScreenDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//获取键盘监听dll发送的键盘监听信息
currentkey = (ShortcutKey*)(pCopyDataStruct->lpData);
//判断键位,如果键位和指定组合键匹配,执行指定代码
if (currentkey->key[0] == key1->key[0] && currentkey->key[1] == key1->key[1] && currentkey->key[2] == key1->key[2])
{
// 在此添加键盘消息处理程序代码
//this->OnBnClickedButton1();
}
return CDialogEx::OnCopyData(pWnd, pCopyDataStruct);
}
注:
1.安装HOOK钩子,如果调试不需要调试与捕获键盘相关功能时,可注释掉安装钩子代码和OnDestroy中卸载钩子代码。因为安装钩子后,断点调试时,按键盘后断点会卡住整个桌面,要等一段时间才能正常,严重时整个屏幕一直卡着,只能重启电脑。
2.问题在于dll中的回调函数KeyboardProc,只要在KeyboardProc内发送消息或者调用KeyboardProc内使用的变量都会卡,原因不明,网上也没搜到相关解决方案。但这对功能没有影响,就是会影响调试。所以项目调试与钩子无关的功能时,可以注释掉钩子的安装和卸载代码再作调试。