玩dota时候,大多的人会使用改建精灵,在网上找了找资料,发现这个其实不难编,于是自己实现了一个。
首先,要了解什么是钩子(HOOK,彼得潘里的虎克船长就是这个)。我们都知道在windows程序是靠消息驱动的,比如说我们在某程序中点击了鼠标,那么,系统会向这个程序发送一个鼠标消息,在通过其绑定的回调函数来处理这个鼠标消息。钩子就是在系统传递消息时把这个消息截获,然后按照我们的意愿对其处理。
在改键中,我们希望截获某个键消息,然后用一个其他的键的消息来替代它,这个过程正好是钩子的能力范围之内。
我们来看看如何使用钩子,这里我要介绍一个钩子函数 “HHOOK SetWindowsHookEx( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId);”
这个在MSDN里可已找到。
我们先看一下他的参数:
idHook这个参数是设置我们要绑定什么样的钩子,windows系统的消息是多种的,那么对于消息操作的钩子也应该是多种的。比如说,鼠标钩子和键盘钩子就是不同类型的。
lpfn这个参数是设定回调函数的,对于截获的消息,我们要如何处理它,就在这个函数里面写相应的代码。(注意,这是一个函数指针也就是函数名)。
hMod这个参数有一点小复杂,因为它与后面的dwThreadId有关,如果这里是NULL那么dwThreadId就是当前调用这个函数的线程id。这个参数的意义在于如果我们是用的是全局的钩子(也就是对所有桌面的上的线程的消息均截获),那么就需要从dll(动态链接库)中加载钩子,而此参数就是指向那个动态链接库句柄。
dwThreadId如果不是全局钩子,那么这个就是调用函数的线程的id,一般使用GetCurrentThreadId()函数。如果是全局钩子,这里置为0。
我们在总结一下后两个参数,如果你的SetWindowsHookEX函数写在了你要监视的线程中,那么就不需要dll,那么hMod置为NULL,dwThreadId设置为当前线程id。如果你要监视其他线程的消息,那么就需要使用到dll,那么就需要指定dll的句柄,dwThreadId置为0。
在改键的程序中,我们使用的钩子类型是WH_KEYBOARD_LL,这是个底层的键盘钩子,他对应的回调函数我们叫LowLevelKeyboardProc(int code ,WPARAM wParam,LPARAM lParam);
我们来解释一个回调函数的参数:
code参数如果是非0值,那么不能在更进一步的处理,而且必须调用CallNextHookEx(HHOOk hMod)函数,并返回此函数的返回值。
wParam参数是指定我们键盘是什么状态,有WM_KEYDOW、WM_KEYUP、WM_SYSKEYDOWN、WM_SYSKEYUP这四种状态。
了Param是一个结构体指针,这里面有我们具体按下了哪个键的键值。一下是具体格式,我们只用第一个vkCode这里存的是键值。
typedef struct { DWORD vkCode; DWORD scanCode; DWORD flags; DWORD time; ULONG_PTR dwExtraInfo; } KBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
以下是代码
1.dll部分的代码
keyHookDLL.h
#ifndef _KEYHOOKDLL_H_
#define _KEYHOOKDLL_H_
#ifdef DLL_EXPORTS
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#endif
extern HMODULE g_hInist;
HHOOK hHook = NULL;
BYTE* changeKeys = NULL;
UINT count_keys = 0;
void DLLAPI setHook(BYTE* data,UINT count);//设置键盘钩子的键值映射
void DLLAPI freeHook();//释放钩子
LRESULT CALLBACK LowLevelKeyboardProc(int code,WPARAM wParam,LPARAM lParam);
#endif
keyHookDLL.cpp
#include "stdafx.h"
#define DLL_EXPORTS
#include "KeyHookDLL.h"
void DLLAPI setHook(BYTE* data,UINT count)
{
hHook = SetWindowsHookEx(WH_KEYBOARD_LL,LowLevelKeyboardProc,g_hInist,0);
changeKeys = new BYTE[count];
for (UINT i = 0;i<count;i++)
{
changeKeys[i] = data[i];
}
count_keys = count;
}
void DLLAPI freeHook()
{
UnhookWindowsHookEx(hHook);
delete[] changeKeys;
}
BOOL iskeydown = FALSE;
LRESULT CALLBACK LowLevelKeyboardProc(int code,WPARAM wParam,LPARAM lParam)
{
KBDLLHOOKSTRUCT* hookInfo = (KBDLLHOOKSTRUCT*)lParam;
if (code == HC_ACTION)
{
if (WM_KEYDOWN == wParam && !iskeydown)
{
if (changeKeys[0] == hookInfo->vkCode)
{
iskeydown = TRUE;
keybd_event(VK_NUMPAD7,0,0,0);
keybd_event(VK_NUMPAD7,0,KEYEVENTF_KEYUP,0);
iskeydown = FALSE;
return 1;
}
if (changeKeys[1] == hookInfo->vkCode)
{
iskeydown = TRUE;
keybd_event(VK_NUMPAD8,0,0,0);
keybd_event(VK_NUMPAD8,0,KEYEVENTF_KEYUP,0);
iskeydown = FALSE;
return 1;
}
if (changeKeys[2] == hookInfo->vkCode)
{
iskeydown = TRUE;
keybd_event(VK_NUMPAD4,0,0,0);
keybd_event(VK_NUMPAD4,0,KEYEVENTF_KEYUP,0);
iskeydown = FALSE;
return 1;
}
if (changeKeys[3] == hookInfo->vkCode)
{
iskeydown = TRUE;
keybd_event(VK_NUMPAD5,0,0,0);
keybd_event(VK_NUMPAD5,0,KEYEVENTF_KEYUP,0);
iskeydown = FALSE;
return 1;
}
if (changeKeys[4] == hookInfo->vkCode)
{
iskeydown = TRUE;
keybd_event(VK_NUMPAD1,0,0,0);
keybd_event(VK_NUMPAD1,0,KEYEVENTF_KEYUP,0);
iskeydown = FALSE;
return 1;
}
if (changeKeys[5] == hookInfo->vkCode)
{
iskeydown = TRUE;
keybd_event(VK_NUMPAD2,0,0,0);
keybd_event(VK_NUMPAD2,0,KEYEVENTF_KEYUP,0);
iskeydown = FALSE;
return 1;
}
}
}
return CallNextHookEx(hHook,code,wParam,lParam);
//return 1;
}
MainDLL.cpp
// HookDLL.cpp : 定义 DLL 应用程序的入口点。
//
#include "stdafx.h"
#ifdef _MANAGED
#pragma managed(push, off)
#endif
HMODULE g_hInist = NULL;// 调用dll的句柄
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_hInist = hModule;
break;
case DLL_THREAD_ATTACH:
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
#ifdef _MANAGED
#pragma managed(pop)
#endif
建立一个机遇对话框的MFC工程。添加如下的对话框资源
keyHookDlg.h
// KeyHookDlg.h : 头文件
//
#pragma once
// CKeyHookDlg 对话框
class CKeyHookDlg : public CDialog
{
// 构造
public:
CKeyHookDlg(CWnd* pParent = NULL); // 标准构造函数
// 对话框数据
enum { IDD = IDD_KEYHOOK_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
BOOL isHooked;
BOOL isUnHooked;
public:
// 小键盘数字键映射到其他键上
CString m_num7;
CString m_num8;
CString m_num4;
CString m_num5;
CString m_num1;
CString m_num2;
BYTE m_changeKey[6];
afx_msg void OnBnClickedBtnHook();
afx_msg void OnBnClickedBtnUnhook();
};
keyHookDlg.cpp
// KeyHookDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "KeyHook.h"
#include "KeyHookDlg.h"
#include "KeyHookDLL.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
#pragma comment(lib,"HookDLL.lib")
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
class CAboutDlg : public CDialog
{
public:
CAboutDlg();
// 对话框数据
enum { IDD = IDD_ABOUTBOX };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()
// CKeyHookDlg 对话框
CKeyHookDlg::CKeyHookDlg(CWnd* pParent /*=NULL*/)
: CDialog(CKeyHookDlg::IDD, pParent)
,isHooked(FALSE)
,isUnHooked(FALSE)
, m_num7("")
, m_num8("")
, m_num4("")
, m_num5("")
, m_num1("")
, m_num2("")
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_changeKey[0] = VK_NUMPAD7;
m_changeKey[1] = VK_NUMPAD8;
m_changeKey[2] = VK_NUMPAD4;
m_changeKey[3] = VK_NUMPAD5;
m_changeKey[4] = VK_NUMPAD1;
m_changeKey[5] = VK_NUMPAD2;
}
void CKeyHookDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_NUM7, m_num7);
DDX_Text(pDX, IDC_EDIT_NUM8, m_num8);
DDX_Text(pDX, IDC_EDIT_NUM4, m_num4);
DDX_Text(pDX, IDC_EDIT_NUM5, m_num5);
DDX_Text(pDX, IDC_EDIT_NUM1, m_num1);
DDX_Text(pDX, IDC_EDIT_NUM2, m_num2);
}
BEGIN_MESSAGE_MAP(CKeyHookDlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
//}}AFX_MSG_MAP
ON_BN_CLICKED(IDC_BTN_HOOK, &CKeyHookDlg::OnBnClickedBtnHook)
ON_BN_CLICKED(IDC_BTN_UNHOOK, &CKeyHookDlg::OnBnClickedBtnUnhook)
END_MESSAGE_MAP()
// CKeyHookDlg 消息处理程序
BOOL CKeyHookDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
void CKeyHookDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CKeyHookDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标显示。
//
HCURSOR CKeyHookDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CKeyHookDlg::OnBnClickedBtnHook()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData();
if (m_num7.GetLength()>1)
{
MessageBox(_T("can't be string !"),_T("ERROR"),IDOK);
return;
}
if (m_num8.GetLength()>1)
{
MessageBox(_T("can't be string !"),_T("ERROR"),IDOK);
return;
}
if (m_num4.GetLength()>1)
{
MessageBox(_T("can't be string !"),_T("ERROR"),IDOK);
return;
}
if (m_num5.GetLength()>1)
{
MessageBox(_T("can't be string !"),_T("ERROR"),IDOK);
return;
}
if (m_num1.GetLength()>1)
{
MessageBox(_T("can't be string !"),_T("ERROR"),IDOK);
return;
}
if (m_num2.GetLength()>1)
{
MessageBox(_T("can't be string !"),_T("ERROR"),IDOK);
return;
}
if (!isHooked && !isUnHooked)
{
BYTE* _keys = new BYTE[6];
_keys[0] = *((BYTE*)(m_num7.GetBuffer()));
_keys[1] = *((BYTE*)(m_num8.GetBuffer()));
_keys[2] = *((BYTE*)(m_num4.GetBuffer()));
_keys[3] = *((BYTE*)(m_num5.GetBuffer()));
_keys[4] = *((BYTE*)(m_num1.GetBuffer()));
_keys[5] = *((BYTE*)(m_num2.GetBuffer()));
setHook(_keys,6);
isHooked = TRUE;
isUnHooked = TRUE;
delete[] _keys;
}
else
{
MessageBox(_T("you have already Hooked"),_T("Notice"),IDOK);
return;
}
}
void CKeyHookDlg::OnBnClickedBtnUnhook()
{
// TODO: 在此添加控件通知处理程序代码
if (isHooked && isUnHooked)
{
freeHook();
isUnHooked = FALSE;
isHooked = FALSE;
}
else
{
MessageBox(_T("you have already Hooked"),_T("Notice"),IDOK);
return;
}
}
实验结果,按下数字键“1”。
这里的MFC工程文件,我知列举了我有代码修改的部分,也就是对话框部分。
由于本人的水平有限,希望大家有更好的方法或者技术一起分享一下。