aram,LPARAM lParam);
hWnd即为要接受消息的窗口句柄。
Msg为要发送的消息。
wParam和lParam为消息的附加参数。
虽然可以使用PostMessage实现向主程序的窗口发送消息,但是我们如何获得主程序的窗口句柄呢?我们知道钩子函数是在DLL内实现的,而DLL会被加载到各个进程内。在其他进程要想得到主程序的窗口句柄这是一个问题。
在《windows核心编程系列》谈谈内存映射文件中,我们谈到了在可执行文件内使用共享段,可以实现同一个可执行文件的多个实例共享共享段内的数据的目的。那么在DLL使用共享段呢?哈哈,或许你已经猜出来了,由于DLL被映射到了各个进程,将数据放在DLL的共享段,可以实现在各个进程内共享DLL内共享段数据的目的。
我们的解决方法就是:在DLL内建立共享段,将主程序的窗口句柄放在共享段中。在主程序调用安装钩子的函数时可以将共享段内的窗口句柄赋为主程序的窗口句柄。从而达到在各个进程内共享数据的目的。到此,我们又学习一种在进程间共享数据的方法,另一种方法是利用内存映射文件。
建立和设置共享段的代码:可以参考《windows核心编程》谈谈内存映射文件。
#pragma data_seg("shared")
HWND hWnd=NULL;
HHOOK hHook=NULL;
#pragma data_seg()
#pragma comment(linker,"/SECTION:shared,RWS")
怎么多了个hHook,hHook是创建的钩子的句柄。由于在钩子函数中会调用CallNextHookEx将消息传给钩子链的下一结点。二者都是在其他进程调用的,因此我们也必须把钩子的句柄设为共享。
DLL内创建钩子的代码:
KEYHOOKDLL_API bool SetHook(
bool IsInstall,//true表示安装钩子,false表示卸载钩子。
HWND hWnd, //主程序窗口句柄,用于在主程序内传入设置。
int ThreadId)//要安装钩子的线程。
{
::hWnd=hWnd;//将当前窗口句柄赋给DLL共线段内的窗口句柄。
if(IsInstall)
{
hHook=SetWindowsHookEx( WH_KEYBOARD,KeyHookProc,GetModuleHandle
("keyhookdll"),ThreadId);
return true;
}
else
{
UnhookWindowsHookEx(hHook);
return true;
}
}
创建的钩子类型为WH_KEYBOARD,他可以拦截WM_KEYDOWN 和WM_KEYUP 消息。具体请参考MSDN.
创建钩子函数功能很简单,仅仅安装钩子和设置共享段内的数据。Thread为要安装钩子的线程。主程序在调用时传入0,表示为所有线程安装钩子。
再看钩子函数:
LRESULT CALLBACK KeyHookProc(int nCode ,WPARAM wParam,LPARAM lParam)
{
if(nCode<0||nCode==HC_NOREMOVE)
{
return CallNextHookEx(hHook,nCode,wParam,lParam);
}
if(lParam&0x40000000)//只对WM_DOWN进行响应。
{
PostMessage(hWnd,WM_KEYDOWN,wParam,lParam);
}
return CallNextHookEx(hHook,nCode,wParam,lParam);
在钩子函数中首先判断nCode的值,当他小于零时应该直调用CallNextHookEx,除此之外它也可以有以下取值:
ACTION:说明wParam和lParam包含按键消息的信息,可以处理。
HC_NOREMOVE:说明wParam和lParam包含按键消息的信息,但该消息没有被从消息队列中移除。即程序是调用PeekMessage来查询消息队列内的消息的。
( 与GetMessage的区别与联系:他们都从消息队列内查询消息,有消息时将此消息发送出去,GetMessage在消息队列没有消息时会一直等待,直到有消息到达时才返回。而PeekMessage无论消息队列中是否有消息都立即返回。)
因此当检测到nCode小于0或者为WH_NOREMOVE时不能对消息进行处理而要直接调用CallNextHookEx。lParam的第30位为1时说明此时键被按下,为零时说明键被弹起。此处进行了判断,仅在键被按下时向窗口发送消息。防止消息每次击键发送两次消息。
当某消息到达时我们给主程序窗口发送的消息为用户自定义消息:WM_KEY
他被定义为#define WM_KEY WM_USER+1
在主程序内我们必须自己实现相应此消息的消息处理函数。
原型为:
afx_msg LRESULT OnKey(WPARAM wParam,LPARAM lParam);
实现:
char keyname[100];
::GetKeyNameText(lParam,keyname,100);//获得按键的键名。
CString a;
a.Format("用户按键:%s\r\n",keyname);
m_output+=a;
UpdateData(false);
::MessageBeep(MB_OK);
CEdit *edit=(CEdit*)GetDlgItem(IDC_EDIT_OUTPUT);
edit->LineScroll(edit->GetLineCount());
return 0;
到此为止各主要函数都介绍完毕,剩下都是如何创建dll。此处不再介绍。例子程序2011年12月2日下午实现。
总结:以上程序花了近三个小时实现,此程序看似容易但一旦自己动手实现各种问题接踵而至。所以以后要经常动手实现一些看似容易的程序,不要眼高手低。打这些字的时候键盘监控程序仍在工作,显示着我按下的每一个键。有明显的电脑感觉速度比平常慢了不少,看来使用钩子,尤其是系统范围内的钩子会导致很大的overhead。
windows核心编程中谈到注入dll的几种方式。其中介绍了使用