什么是HOOK呢?其实很简单,HOOK就是对Windows消息进行拦截检查处理的一个函数。在Windows的消息机制中,当用户产生消息时,应用程序通过调用GetMessage函数取出消息,然后把消息放入到消息队列,再使用消息调度函数DispatchMessage函数讲消息调度给系统,Windows系统会调用创建窗口类时制定的窗口过程中进行次消息的处理。而HOOK函数的话,就可以对此消息进行拦截。经过此HOOK函数的处理后,再决定是屏蔽掉此消息,还是继续往下传递。至于为什么叫“钩子”,可能就是因为它可以像钩子一样把消息给钩住。话说四川话里面的“钩子”却是屁股的意思...哈哈,四川的同学读钩子肯定多了几份趣味
钩子函数的实现分为以下步骤:
1 安装钩子函数
安装钩子函数是通过函数SetWindowHookEx来实现的:
HHOOK WINAPI SetWindowsHookExW(__in int idHook, __in HOOKPROC lpfn,__in_opt HINSTANCE hmod, __in DWORD dwThreadId);
此函数的第一个参数idHook指定将要安装的钩子过程的类型,如果我们安装键盘钩子,则设为WH_KEYBOARD,鼠标钩子则设为WH_MOUSE。 lpfn指向了相应的钩子函数,如果dwThreadId为0,或者指定了一个其他进程创建的线程之标识符,则lpfn必须指向一个位于某动态链接库中的钩子函数。如果为进程外钩子,hMod指向的是钩子函数所在的DLL的句柄,如果为进程内钩子,则设为NULL。dwThreadId指定与钩子过程相关的线程表示,为0表示与所有线程相关。函数成功返回所安装的钩子函数的句柄,否则返回NULL。PS:最后安装的钩子函数总是排在钩子链的最前面。
2 添加全局变量
HHOOK g_hMouse = NULL; HHOOK g_hKeyboard = NULL; HWND g_hWnd = NULL;
全局句柄g_hMouse、g_hKeyboard分别代表我们所要安装的鼠标钩子和键盘钩子过程的句柄,g_hWnd用来保存当前的窗口句柄。
3 声明钩子函数
如果想要监视鼠标消息,需要定义相应的鼠标钩子过程。同样,要监视键盘消息,也需要定义相应的键盘钩子过程。该钩子函数的形式必须如下:
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam);
在当前钩子函数中处理完信息后,如果想把信息继续传递给下一个钩子函数,可以调用CallNextHookEx函数来实现:
LRESULT WINAPI CallNextHookEx( __in_opt HHOOK hhk, __in int nCode, __in WPARAM wParam, __in LPARAM lParam
CallNextHookEx函数的第一个参数是钩子的句柄,就是我们调用SetWindowHookEx函数返回的句柄,其他几个参数和HookProc中是一样的。
4 卸载钩子函数
当我们不需要再使用钩子函数的时候,可以将其卸载,调用函数:
BOOL WINAPI UnhookWindowsHookEx( __in HHOOK hhk);
函数的唯一参数便为钩子函数的句柄。
说一千道一万,不如一个程序来的实在。下面便是一个实现进程内钩子函数的MFC例子。步骤如下:
1 新建一个名字CInnerHookDlg的MFC工程。
2 在CInnerHookDlgDlg.cpp中添加钩子的全局变量和当前句柄
HHOOK g_hMouse = NULL; HHOOK g_hKeyboard = NULL; HWND g_hWnd = NULL;
3 然后再分别实现键盘和鼠标钩子函数,我们在键盘钩子函数中显示出当前按下去的键盘。而在鼠标钩子函数中,当我们双击右键时候,会显示出对话框。
//鼠标钩子函数 LRESULT CALLBACK MouseProc( int nCode, WPARAM wParam, LPARAM lParam ) { ... } //键盘钩子函数 LRESULT CALLBACK KeyBoardProc( int nCode, WPARAM wParam, LPARAM lParam ) { ... }
4 在CCInnerHookDlgDlg的OnInitDialog()中设置钩子,添加如下代码
//设置鼠标钩子 g_hMouse = SetWindowsHookEx(WH_MOUSE, MouseProc, NULL,GetCurrentThreadId()); //设置键盘钩子 g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD, KeyBoardProc, NULL, GetCurrentThreadId()); //获取当前句柄 g_hWnd = m_hWnd;
5 最后在CCInnerHookDlgDlg的OnSysCommand()中卸载钩子,添加如下代码:
UnhookWindowsHookEx(g_hMouse);
UnhookWindowsHookEx(g_hKeyboard);
下面是所有的代码啦!
// CInnerHookDlgDlg.cpp : 实现文件 // #include "stdafx.h" #include "CInnerHookDlg.h" #include "CInnerHookDlgDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #endif HHOOK g_hMouse = NULL; HHOOK g_hKeyboard = NULL; HWND g_hWnd = NULL; // 用于应用程序“关于”菜单项的 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() // CCInnerHookDlgDlg 对话框 CCInnerHookDlgDlg::CCInnerHookDlgDlg(CWnd* pParent /*=NULL*/) : CDialog(CCInnerHookDlgDlg::IDD, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CCInnerHookDlgDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CCInnerHookDlgDlg, CDialog) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP END_MESSAGE_MAP() //鼠标钩子函数 LRESULT CALLBACK MouseProc( int nCode, WPARAM wParam, LPARAM lParam ) { LPMOUSEHOOKSTRUCT pMouseHook = (MOUSEHOOKSTRUCT*)(lParam); if (nCode >= 0) { //双击右键弹出当前窗口的窗口名 if (wParam == WM_RBUTTONDBLCLK) { HWND hWnd = pMouseHook->hwnd; //鼠标所在的窗口 HWND hParent; //获取顶层窗口 while(hWnd != NULL) { hParent = GetParent(hWnd); if (hParent == NULL) break; hWnd = hParent; } if (hWnd != NULL) { //得到窗口名 TCHAR strDlgName[MAX_PATH] = {0}; GetWindowText(hWnd,strDlgName, MAX_PATH); AfxMessageBox( strDlgName ); } } } return CallNextHookEx(g_hMouse,nCode, wParam, lParam); } //键盘钩子函数 LRESULT CALLBACK KeyBoardProc( int nCode, WPARAM wParam, LPARAM lParam ) { LRESULT lReturnValue; lReturnValue = CallNextHookEx(g_hKeyboard, nCode, wParam, lParam); //键盘按键是否已经松动 if ( (lParam & 0x80000000) && (HC_ACTION == nCode)) { char c = (char) lParam; CString strText(_T("按下了键")); strText += c; AfxMessageBox(strText); //显示当前的按键 } //如果按下F4,则退出程序 if (wParam == VK_F4) { SendMessage(g_hWnd, WM_CLOSE,0,0); } return lReturnValue; } // CCInnerHookDlgDlg 消息处理程序 BOOL CCInnerHookDlgDlg::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: 在此添加额外的初始化代码 //设置鼠标钩子 g_hMouse = SetWindowsHookEx(WH_MOUSE, MouseProc, NULL,GetCurrentThreadId()); //设置键盘钩子 g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD, KeyBoardProc, NULL, GetCurrentThreadId()); //获取当前句柄 g_hWnd = m_hWnd; return TRUE; // 除非将焦点设置到控件,否则返回 TRUE } void CCInnerHookDlgDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDialog::OnSysCommand(nID, lParam); } UnhookWindowsHookEx(g_hMouse); UnhookWindowsHookEx(g_hKeyboard); } // 如果向对话框添加最小化按钮,则需要下面的代码 // 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序, // 这将由框架自动完成。 void CCInnerHookDlgDlg::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 CCInnerHookDlgDlg::OnQueryDragIcon() { return static_cast<HCURSOR>(m_hIcon); }