1.1 初识Windows消息
大部分 Windows 应用程序都是基于消息机制的(命令行下的程序并不基于消息机制),熟悉 Windows 操作系统的消息机制是掌握 Windows 操作系统下编程的基础。本节将带领读者认识和熟悉Windows的消息机制。
1.1.1 对消息的演示测试
在真正学习和认识消息之前,先来完成一个简单的任务,看看消息能完成什么样的工作。首先写一个简单的程序,通过编写的程序发送消息来关闭记事本的进程、获取窗口的标题和设置窗口的标题。
程序的具体代码如下:
void CMsgTestDlg::OnClose()
{
//TODO:Add your control notification handler code here
HWND hWnd = ::FindWindow("Notepad", NULL);
if ( hWnd == NULL )
{
AfxMessageBox("没有找到记事本");
return ;
}
::SendMessage(hWnd, WM_CLOSE, NULL, NULL);
}
void CMsgTestDlg::OnExec()
{
//TODO:Add your control notification handler code here
WinExec("notepad.exe", SW_SHOW);
}
void CMsgTestDlg::OnEditWnd()
{
//TODO: Add your control notification handler code here
HWND hWnd = ::FindWindow(NULL, "无标题 - 记事本");
if ( hWnd == NULL )
{
AfxMessageBox("没有找到记事本");
return ;
}
char *pCaptionText = "消息测试";
::SendMessage(hWnd, WM_SETTEXT, (WPARAM)0, (LPARAM)pCaptionText);
}
void CMsgTestDlg::OnGetWnd()
{
//TODO: Add your control notification handler code here
HWND hWnd = ::FindWindow("Notepad", NULL);
if ( hWnd == NULL )
{
AfxMessageBox("没有找到记事本");
return ;
}
char pCaptionText[MAXBYTE] = { 0 };
::SendMessage(hWnd, WM_GETTEXT, (WPARAM)MAXBYTE, (LPARAM)pCaptionText);
AfxMessageBox(pCaptionText);
}
编写的代码中有4个函数:第1个函数OnClose()是用来关闭记事本程序的;第2个函数OnExec()是用来打开记事本程序的,主要是测试其他3个函数时可以方便地打开记事本程序;第3个函数OnEditWnd()是用来修改记事本标题的;第4个函数OnGetWnd()是用来获取当前记事本标题的。程序的界面如图1-1所示。
图1-1 消息测试窗口
简单测试一下这个程序。首先单击“打开记事本程序”按钮,出现记事本的窗口(表示记事本程序被打开了);接着单击“修改记事本标题”按钮,可以发现记事本程序的窗口标题改变了;再单击“获取记事本标题”按钮,弹出记事本程序窗口标题的一个对话框;最后单击“关闭记事本程序”按钮,记事本程序被关闭。
1.1.2 对MsgTest代码的解释
上面的代码中要学习的API函数有两个,分别是FindWindow()和SendMessage()。下面看一下它们在MSDN中的定义。
FindWindow()函数的定义如下:
HWND FindWindow(
LPCTSTR lpClassName, // class name
LPCTSTR lpWindowName // window name
);
FindWindow()函数的功能是,通过指定的窗口类名( lpClassName )或窗口标题(lpWindowName)查找匹配的窗口并返回最上层的窗口句柄。简单理解就是,通过指定的窗口名(窗口名相对于窗口类来说要直观些,因此往往使用的是窗口名)返回窗口句柄。FindWindow()函数有2个参数,分别是lpClassName和lpWindowName。通过前面的描述,该函数通常使用的是第2个参数lpWindowName,该参数是指定窗口的名称。在例子代码中,为程序指定的窗口名是“无标题—记事本”。“无标题—记事本”是记事本程序打开后的默认窗口标题,当 FindWindow()找到该窗口时,会返回它的窗口句柄。例子代码中也使用了lpClassName(窗口类名),在窗口的名称会改变的情况下,只能通过窗口类名来获取窗口的句柄了。FindWindow()函数返回的窗口句柄是为了给SendMessage()函数来使用的。
SendMessage()函数的定义如下:
LRESULT SendMessage(
HWND hWnd, // handle to destination window
UINT Msg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
该函数的作用是根据指定窗口句柄将消息发送给指定的窗口。该函数有3个参数,第1个参数hWnd是要接收消息的窗口的窗口句柄,第2个参数Msg是要发送消息的消息类型,第3个参数wParam和第4个参数lParam是消息的两个附加参数。第1个参数hWnd在前面已经介绍过了,该参数通过FindWindow获取。
程序的代码中,SendMessage()函数的第2个参数分别使用的是WM_CLOSE消息、WM_SETTEXT消息和WM_GETTEXT消息。下面来看这3个消息的具体含义。
WM_CLOSE:将WM_CLOSE消息发送后,接收到该消息的窗口或应用程序将要关闭。WM_CLOSE消息没有需要的附加参数,因此wParam和lParam两个参数都为NULL。
WM_SETTEXT:应用程序发送WM_SETTEXT消息对窗口的文本进行设置。该消息需要附加参数,wParam参数未被使用,必须指定为0值,lParam参数是一个指向以NULL为结尾的字符串的指针。
WM_GETTEXT:应用程序发送WM_GETTEXT消息,将对应窗口的文本复制到调用者的缓冲区中。该消息也需要附加参数,wParam 参数指定要复制的字符数数量,lParam 是接收文本的缓冲区。
例子代码在VC6下进行编译连接,生成可执行文件后,可以通过按钮的提示进行测试,以便感性认识消息的作用。
1.1.3 如何获取窗口的类名称
编写程序调用FindWindow()函数的时候,通常会使用其第2个参数,也就是窗口的标题。但是有些软件的窗口标题会根据不同的情况进行改变,那么程序中就不能在FindWindow()函数中直接通过窗口的标题来获得窗口的句柄了。而窗口的类名通常是不会变的,因此编程时可以指定窗口类名来调用FindWindow()函数以便获取窗口句柄。那么,如何能获取到窗口的类名称呢?这就是将要介绍的第1个开发辅助工具——Spy++。
Spy++是微软 Visual Studio 中提供的一个非常实用的小工具,它可以显示系统的进程、窗口等之间的关系,可以提供窗口的各种信息,可以对系统指定的窗口进行消息的监控等。它的功能非常多,这里演示如何用它来获取窗口的类名称。
打开开始菜单,在 Visual Studio 的菜单路径下找到 Spy++,打开 Spy++窗口,如图 1-2所示。
选择工具栏中的“Find Window”按钮,如图1-3所示。
图1-2 Microsoft Spy++窗口
图1-3 Find Window按钮
单击“Find Window”按钮,出现如图1-4 所示的窗口。
图1-4 Find Window窗口
在图1-4 中,用鼠标左键单击“Finder Tool”后面的图标,然后拖曳到指定的窗口上,会显示出“Handle”(窗口句柄)、“Caption”(窗口标题)和“Class”(窗口类名),其中“Class”是编程时要使用的类名称。
“Hide Spy++”是一个比较实用的功能,它用来隐藏Spy++主窗口界面。选中该复选框后,拖曳“Finder Tool”后的图标时,图1-2所示为窗口将被隐藏。这个功能的实用之处在于,有些应用软件有反Spy++的功能,隐藏Spy++主窗口有助于避免被反Spy++的软件检测到。为什么隐藏Spy++的“Find Window”窗口会有反检测的功能,反检测的原理是什么?原理很简单,目标程序也是通过调用FindWindow()函数来查找Spy++窗口的,如果有该窗口,就进行一些相应的处理。
注:通过Spy++找到的窗口句柄是不能在编程中使用的,每次打开窗口时,窗口的句柄都会改变。
将“Finder Tool”后的图标拖曳到记事本的标题处,Spy++的Find Window窗口显示的内容如图1-5所示。
图1-5 获取到信息的Find Window窗口
从图1-5中可以得到记事本程序的标题和类名称。当编写程序调用FindWindow()函数,不能通过程序的标题文本得到窗口的句柄时,可以通过窗口类名称得到窗口的句柄。