1. 程式人生 > >使用SetWindowsHookEx(WH_KEYBOARD,...)製作全域性鍵盤鉤子

使用SetWindowsHookEx(WH_KEYBOARD,...)製作全域性鍵盤鉤子

學習《Windows程式設計》時,照著例子作了一個全域性的鍵盤鉤子,可以截獲到使用者的按鍵。

先是動態庫部分:

///////////////////////////////////////////
// KeyHookLib.h檔案

// 定義函式修飾巨集,方便引用本DLL工程的匯出函式
#ifdef KEYHOOKLIB_EXPORTS
#define KEYHOOKLIB_API __declspec(dllexport)
#else
#define KEYHOOKLIB_API __declspec(dllimport)
#endif

// 自定義與主程式通訊的訊息
#define HM_KEY WM_USER + 101

// 宣告要匯出的函式
BOOL KEYHOOKLIB_API WINAPI SetKeyHook(BOOL bInstall, 
				      DWORD dwThreadId = 0, HWND hWndCaller = NULL);


////////////////////////////////////////////////
// KeyHookLib.cpp檔案

#include <windows.h>

#define KEYHOOKLIB_EXPORTS
#include "KeyHookLib.h"


// 共享資料段
#pragma data_seg("YCIShared")
HWND g_hWndCaller = NULL;	// 儲存主視窗控制代碼
HHOOK g_hHook = NULL;		// 儲存鉤子控制代碼
#pragma data_seg()

// 一個通過記憶體地址取得模組控制代碼的幫助函式
HMODULE WINAPI ModuleFromAddress(PVOID pv) 
{
	MEMORY_BASIC_INFORMATION mbi;
	if(::VirtualQuery(pv, &mbi, sizeof(mbi)) != 0)
	{
		return (HMODULE)mbi.AllocationBase;
	}
	else
	{
		return NULL;
	}
}

// 鍵盤鉤子函式
LRESULT CALLBACK KeyHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
        if(nCode < 0 || nCode == HC_NOREMOVE)
		return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
	
        if(lParam & 0x40000000)	// 訊息重複就交給下一個hook鏈
	{
		return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
	}
	
	// 通知主視窗。wParam引數為虛擬鍵碼, lParam引數包含了此鍵的資訊
        ::PostMessage(g_hWndCaller, HM_KEY, wParam, lParam);
	
        return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

// 安裝、解除安裝鉤子的函式
BOOL WINAPI SetKeyHook(BOOL bInstall, DWORD dwThreadId, HWND hWndCaller)
{
	BOOL bOk;
	g_hWndCaller = hWndCaller;
	
	if(bInstall)
	{
		g_hHook = ::SetWindowsHookEx(WH_KEYBOARD, KeyHookProc, 
					ModuleFromAddress(KeyHookProc), dwThreadId);
		bOk = (g_hHook != NULL);
	} 
	else 
	{
		bOk = ::UnhookWindowsHookEx(g_hHook);
		g_hHook = NULL;
	}
	
	return bOk;
}

然後是應用程式部分:
// win32_DllApp.h

#include <afxwin.h>	


class CMyApp : public CWinApp
{
public:
	BOOL InitInstance();
};

class CMainDialog : public CDialog
{
public:
	CMainDialog(CWnd* pParentWnd = NULL);

protected:
	virtual BOOL OnInitDialog();
	virtual void OnCancel();

	afx_msg long OnHookKey(WPARAM wParam, LPARAM lParam);

	DECLARE_MESSAGE_MAP()
};


// win32_DllApp.cpp
#include "win32_DllApp.h"
#include "resource.h"

#include "KeyHookLib.h"

#pragma comment(lib, "Win32DllLib.lib")


CMyApp theApp;


BOOL CMyApp::InitInstance()
{
	CMainDialog dlg;
	m_pMainWnd = &dlg;

	dlg.DoModal();

	return FALSE;
}

CMainDialog::CMainDialog(CWnd* pParentWnd) : CDialog(IDD_DIALOG1, pParentWnd)
{

}

BOOL CMainDialog::OnInitDialog()
{
	CDialog::OnInitDialog();
	//SetIcon(theApp.LoadIcon(IDI_MAIN), FALSE);
	::SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, 
		0, 0, SWP_NOSIZE|SWP_NOREDRAW|SWP_NOMOVE);

	// 安裝鉤子
	if(!SetKeyHook(TRUE, 0, m_hWnd))
		MessageBox("安裝鉤子失敗!");

	return TRUE;

}

void CMainDialog::OnCancel()
{
	SetKeyHook(FALSE);
	CDialog::OnCancel();
}

BEGIN_MESSAGE_MAP(CMainDialog, CDialog)
	ON_MESSAGE(HM_KEY, OnHookKey)
END_MESSAGE_MAP()

long CMainDialog::OnHookKey(WPARAM wParam, LPARAM lParam)
{
	// 此時引數wParam為使用者按鍵的虛擬鍵碼,
	// lParam引數包含按鍵的重複次數、掃描碼、前一個按鍵狀態等資訊

	char szKey[80];
	::GetKeyNameText(lParam, szKey, 80);

	CString strItem;
	strItem.Format(" 使用者按鍵:%s \r\n", szKey);
	// 新增到編輯框中
	CString strEdit;
	GetDlgItem(IDC_KEYMSG)->GetWindowText(strEdit);
	GetDlgItem(IDC_KEYMSG)->SetWindowText(strItem + strEdit);

	::MessageBeep(MB_OK);
	return 0;
}

結果:當本視窗為活動視窗時,能收到HM_KEY訊息;如果不是,則不能收到。

在網上查了一下,可能是共享資料段的問題。加入下面最後一行之後,問題解決。

// 共享資料段
#pragma data_seg("YCIShared")
HWND g_hWndCaller = NULL;	// 儲存主視窗控制代碼
HHOOK g_hHook = NULL;		// 儲存鉤子控制代碼
#pragma data_seg()

#pragma comment(linker, "/SECTION:YCIShared,rws")