1、消息类型
消息结构
typedef struct tagMSG {
HWND hwnd; //窗口句柄(窗口句柄起到标识对象的作用)
UINT message; //消息标识符,确定具体是哪个消息
WPARAM wParam; //消息的附加信息,含义依赖于具体的消息。
LPARAM lParam; //消息的附加信息,含义依赖于具体的消息。
DWORD time; //消息被放入消息队列的时间
POINT pt; //鼠标位置
} MSG;
注:
- wParam:低字节位表示ID号,高字节位表示控件通知,比如用户单击了按钮,通知码为BN_CLICKED,这样我们就可以了解到用户具体对按钮干了什么。
- lParam:中保存了控件的句柄。
(1)系统消息前缀
|
WM_ |
Window Message |
窗口消息,一般用在SendMessage,PostMessage这样的消息函数中 |
|
SM_ |
Static Message |
静态标签消息 |
|
SS_ |
Static Style |
静态标签风格 |
|
BM_ |
Button Message |
按钮消息 |
|
BS_ |
Button Style |
按钮风格 |
|
BN_ |
Button Notify |
按钮通知 |
|
EM_ |
Edit Message |
编辑框消息 |
|
ES_ |
Edit Style |
编辑框风格 |
|
CB_ |
ComboBox |
组合框消息 |
|
CBN_ |
ComboBox Notify |
组合框通知 |
|
LBM_ |
ListBox Message |
列表框消息 |
|
LBS_ |
ListBox Style |
列表框风格 |
|
LBN_ |
ListBox Notify |
列表框通知 |
|
LVM_ |
ListView Message |
超级列表框(列表视图)消息 |
|
LVS_ |
ListView Style |
超级列表框(列表视图)风格 |
|
LVN_ |
ListView Notify |
超级列表框(列表视图)通知 |
|
TVM_ |
TreeView Message |
树型框(树型视图)消息 |
|
TVS_ |
TreeView Style |
树型框(树型视图)风格 |
|
TVN_ |
TreeView Notify |
树型框(树型视图)通知 |
|
VK_ |
Virtual Keyboard |
虚拟键、键代码 |
注:系统保留了0x0000-0x03FF的消息值,应用程序不能使用
(2)自定义消息
用户可以自定义0x0400~0x7FFF(WM_USER)的消息值。调用RegisterWindowMessage函数返回一个系统范围内唯一的消息标识符。
2、SendMessage 和 PostMessage 的区别
(1)首先是返回值意义的区别,我们先看一下 MSDN 里的声明:
LRESULT SendMessage(
HWND hWnd, //窗口句柄
UINT Msg, //用于区别其他消息的常量值
WPARAM wParam, //通常是一个与消息有关的常量值,也可能是窗口或控件的句柄
LPARAM lParam //通常是一个指向内存中数据的指针
);
BOOL PostMessage(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);
注:
hWnd:
- 指定接收消息的窗口过程的窗口句柄
- 如果该参数的值是 HWND_BROADCAST((HWND)0xffff),那么消息将发送给所有的顶层窗口(包括无效或不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口,但消息就是不发送给子窗口)
- 如果该参数的值是 NULL,函数的行为类似调用 PostThreadMessage 函数,并将 dwThreadId 参数设置为当前线程的标识符
- 自 Windows Vista 系统开始,消息发送需服从 UIPI(User Interface Privilege Isolation,用户界面特权隔离)技术,消息只能发送到较小或同等级别的进程中的线程的消息队列
返回值:
其中 4 个参数的意义是一样的,返回值类型不同(其实从数据上看他们一样是一个 32 位的数,只是意义不一样),LRESULT 表示的是消息被处理后的返回值,BOOL 表示的是消息是不是 Post 成功。
(2)PostMessage 是异步的,SendMessage 是同步的。
PostMessage 只把消息放入队列,不管消息是否被处理就返回,消息可能不被处理;而 SendMessage 等待消息被处理完了之后才返回,如果消息不被处理,发送消息的线程将一直被阻塞。
(3)如果在同一个线程内,SendMessage 发送消息时,由 USER32.DLL 模块调用目标窗口的消息处理程序,并将结果返回。SendMessage 在同一线程中发送消息并不入线程消息队列。PostMessage 发送消息时,消息要先放入线程的消息队列,然后通过消息循环分派到目标窗口(DispatchMessage)。
如果在不同线程内,SendMessage 发送消息到目标窗口所属线程的消息队列,然后发送消息的线程在 USER32.DLL 模块内监视和等待消息处理,直到目标窗口处理完返回。SendMessage 在返回前还做了很多工作,比如,响应别的线程向它 SendMessage。Post 到别的线程时,最好用 PostThreadMessage 代替 PostMessage,PostMessage 的 hWnd 参数可以是 NULL,等效于 PostThreadMessage + GetCurrentThreadId。Post WM_QUIT 时,应使用 PostQuitMessage 代替。
(4)系统只整编(marshal)系统消息(0 到 WM_USER 之间的消息),发送用户消息(WM_USER 以上)到别的进程时,需要自己做整编。
用 PostMessage、SendNotifyMessage、SendMessageCallback 等异步函数发送系统消息时,参数里不可以使用指针,因为发送者并不等待消息的处理就返回,接受者还没处理指针就已经被释放了。
(5)在 Windows 2000/XP 里,每个消息队列最多只能存放 10,000 个 Post 的消息,超过的还没被处理的将不会被处理,直接丢掉。这个值可以改得更大:[HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Windows] USERPostMessageLimit,最小可以是 4000.
3、GetMessage()和PeekMessage()区别
(1)GetMessage()
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax,
)
a、功能:
从调用线程的消息队列里取得一个消息并将其放于指定的结构。
b、返回值:
除了接收到WM_QUIT外的消息均返回非零值。如果函数取得WM_QUIT消息,返回值是零。如果出现了错误,返回值是-1。
c、参数:
- lpMsg:指向一个消息结构体,GerMessage从线程的消息队列中取出消息信息将保存在该结构体对象中。
- hWnd:指向接收属于哪个窗口的消息。一般为NULL,用于接收属于调用线程的所有窗口的窗口消息。
- wMsgFilterMin:获取的消息的最小值,一般为0
- wMsgFilterMax; 获取的消息的最大值。如果wMsgFilterMin和wMsgFilterMax都为0,则接收所有消息
d、备注:
有在消息对列中有消息时返回,队列中无消息就会一直阻塞等待,直至下一个消息出现时才返回。在等的这段时间,应用程序不能执行任何指令。如果发现是WM_QUIT消息,消息循环结束,否则继续下一步。
(2)PeekMessage()
BOOL PeekMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMSGfilterMin
UINT wMSGfilterMax,
UINT wRemoveMsg
)
b、功能:
一个消息检查线程消息队列,并将该消息(如果存在)放于指定的结构。
c、返回值:
如果消息可得到,返回非零值;如果没有消息可得到,返回值是零。
d、参数:
- lpMsg:街火速消息信息的MSG结构指针
- hWnd:其消息被检查的窗口句柄
- wMSGfilterMin:指定被检查的消息范围里的第一个消息
- wMSGfilterMax:指定被检查的消息范围里的最后一个消息
- wRemoveMsg:确定消息如何被处理,可去下值之一:
|
PM_NOREMOVE |
PeekMessage处理后,消息不从队列里除掉。 |
|
PM_REMOVE |
PeekMessage处理后,消息从队列里除掉。 |
|
PM_NOYIELD |
此标志使系统不释放等待调用程序空闲的线程。可将PM_NOYIELD随意组合到PM_NOREMOVE或PM_REMOVE。 |
缺省地,处理所有类型的消息。若只处理某些消息,指定一个或多个下列值:
|
值 |
意义 |
|
PM_QS_INPUT |
Windows NT 5.0和Windows 98:处理鼠标和键盘消息。 |
|
PM_QS_PAINT |
Windows NT 5.0和Windows 98:处理画图消息。 |
|
PM_QS_POSTMESSAGE |
Windows NT 5.0和Windows 98:处理所有被寄送的消息,包括计时器和热键。 |
|
PM_QS_SENDMESSAGE |
Windows NT 5.0和Windows 98:处理所有发送消息。 |
e、备注
无论应用程序消息队列是否有消息,PeekMessage函数都立即返回,成功返回true,否则返回false,程序得以继续执行后面的语句。它的一个参数(UINT wRemoveMsg),如果设置为PM_REMOVE,消息则被取出并从消息队列中删除;如果设置为PM_NOREMOVE,消息就不会从消息队列中删除。
4、PostThreadMessage()
#include<windows.h>
BOOL PostThreadMessage(
DWORD idMessage,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);
(1)功能:
将一个队列消息放入(寄送)到指定线程的消息队列里,不等待线程处理消息就返回。
(2)参数:
idMessage:其消息被寄送的线程的线程标识符。如果线程没有对列,此函数将失败。当线程第一次调用一个win32 VSER或GDI函数时,系统创建线程的消息队列。
Msg:指定被寄送的消息类型
wParam:指定附加的消息特定信息
lParam:指定附加的消息特定信息
(3)返回值:
如果函数调用成功,返回非零值。如果函数调用失败,返回值是零。若想获得更多的错误信息,请调用GetLastError函数。如果idThread不是一个有效的线程标识符或由idThread确定的线程没有消息队列,GetLastError返回ERROR_INVALID_THREAD_ID。
(4)备注
消息将寄送到的线程必须创建消息队列,否则调用PostThreadMessage会失败。用下列方法之一来处理这种情况:
a、调用PostThreadMessage。如果失败,调用Sleep,再调用PostThreadMessage,反复执行,直到PostThreadMessage成功。
b、创建一个事件对象,再创建线程。在调用PostThreadMessage之前,用函数WaitForSingleObject来等待事件被设置为被告知状态。消息将寄送到的线程调用PeekMessage(&msg,NULL,WM_USER,WM_USER,PM_NOREMOVE)来强制系统创建消息队列。设置事件,表示线程已准备好接收寄送的消息。
消息将寄送到的线程通过调用GetMessage或PeekMessage来取得消息。返回的MSG结构中的hwnd成员为NULL。
5、线程同步
(1)CreateEvent()
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性
BOOL bManualReset, // 复位方式
BOOL bInitialState, // 初始状态
LPCTSTR lpName // 对象名称
);
1)功能:
它用来创建或打开一个命名的或无名的事件对象
2)返回值:
如果函数调用成功,函数返回事件对象的句柄。如果函数失败,函数返回值为NULL,如果需要获得详细的错误信息,需要调用GetLastError。
3)参数:
lpEventAttributes:一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承。
bManualReset:指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当一个等待线程被释放以后,系统将会自动将事件状态复原为无信号状态。
bInitialState:指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态
lpName:指定事件的对象的名称,是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的。
(2)SetEvent()
CEvent::SetEvent
BOOL SetEvent(HANDLE hEvent);
功能:设置事件的状态为有标记,释放任意等待线程。如果事件是手工的,此事件将保持有标记直到调用ResetEvent,这种情况下将释放多个线程;如果事件是自动的,此事件将保持有标记,直到一个线程被释放,系统将设置事件的状态为无标记;如果没有线程在等待,则此事件将保持有标记,直到一个线程被释放。
返回值:如果操作成功,则返回非零值,否则为0。
(3)OpenEvent()
函数功能:根据名称获得一个事件句柄。
函数原型:
HANDLEOpenEvent(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName //名称
);
函数说明:
第一个参数:表示访问权限,对事件一般传入EVENT_ALL_ACCESS。详细解释可以查看MSDN文档。
第二个参数:表示事件句柄继承性,一般传入TRUE即可。
第三个参数:表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。
返回值:返回事件的句柄
(4)ResetEvent
函数功能:将事件设为末触发
函数原型:
BOOL ResetEvent(HANDLE hEvent);
6、WaitForSingleObject()
DWORD WaitForSingleObject(
HANDLE hHandle, //对象句柄
DWORD dwMilliseconds //定时时间间隔
);
(1)功能:
等待某一线程完成了再继续做其他事情,有点类似阻塞,用于同步。当等待仍在挂起状态时,句柄被关闭,那么函数行为是未定义的。该句柄必须具有 SYNCHRONIZE 访问权限。
用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。
(2)返回值:
WAIT_OBJECT_0, 表示等待的对象有信号(对线程来说,表示执行结束);
WAIT_TIMEOUT, 表示等待指定时间内,对象一直没有信号(线程没执行完);
WAIT_ABANDONED 表示对象有信号,但还是不能执行 一般是因为未获取到锁或其他原因
(3)参数
hHandle:对象句柄。可以指定一系列的对象,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等。
dwMilliseconds:定时时间间隔,单位为milliseconds(毫秒).如果指定一个非零值,函数处于等待状态直到hHandle标记的对象被触发,或者时间到了。如果dwMilliseconds为0,对象没有被触发信号,函数不会进入一个等待状态,它总是立即返回。如果dwMilliseconds为INFINITE,对象被触发信号后,函数才会返回。
(4)等待对象
它可以等待如下几种类型的对象:
Event,Mutex,Semaphore,Process,Thread
(5)WaitForMultipleObjects
DWORD WaitForMultipleObjects(
DWORD nCount,
const HANDLE* lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds
);
几乎可以等待Windows中的所有的内核对象
7、Windows多线程编程基本顺序
(1)创建事件对象
CreateEvent(0, FALSE, FALSE, 0); //用于创建消息队列
(2)创建线程
hThread = (HANDLE)_beginthreadex(NULL, 0, &fun, NULL, 0, &nThradID);
(3)等待事件被触发
::WaitForSingleObject(hStartEvent, INFINITE);
(4)消息发送
PostThreadMessage(nThradID, MY_MSG, (WPARAM)pInfo, 0)
(5)关闭事件对象、关闭线程
CloseHandle(hStartEvent);
CloseHandle(hThread);
(6)线程回调函数
a、触发事件
SetEvent(hStartEvent)
b、接收消息
GetMessage(&msg, NULL,0,0);
8、实例
#include <windows.h>
#include <cstdio>
#include <process.h>
#include <iostream>
#define MY_MSG WM_USER + 100
using namespace std;
const int MAX_INFO_SIZE = 20; // 定义消息的最大值
HANDLE hStartEvent; //设置事件句柄
// 线程绑定的函数返回值和参数是确定的,而且一定要__stdcall
unsigned __stdcall fun(void* param)
{
cout << "thread fun start" << endl;
MSG msg;
// 检查消息队列,并将消息队列存在于指定结构(可选)
//PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
/***1:触发事件***/
// 释放等待事件、线程。主线程中有WaitForSingleObject等待
if (!SetEvent(hStartEvent))
{
cout << "set event error:" << ::GetLastError();
return 1;
}
/***2:接收消息***/
while (true)
{
if (GetMessage(&msg, NULL,0,0))
{
switch (msg.message)
{
case MY_MSG:
{
char * pInfo = (char *)msg.wParam; //注:switch中的case中有定义则需要用{}括起来
cout << "recv : " << pInfo << endl;
delete[] pInfo;
break;
}
default:
break;
}
}
}
return 0;
}
int main()
{
HANDLE hThread; // 线程句柄
unsigned nThradID; // 线程ID,用来传递消息时使用
cout << "main thread" << endl;
/***1: 创建事件对象***/
// 创建一个未被触发的事件,事件是自动置位
// 消息将寄送到的线程必须创建消息队列。创建事件对象再创建线程,再用函数WaitForSingleObject
// 来等待事件被设置为被告知状,则会强制系统创建消息队列。
hStartEvent = ::CreateEvent(0, FALSE, FALSE, 0);
if (hStartEvent == 0)
{
cout << "createEvent error;" << GetLastError();
return 1;
}
/*** 2:创建线程***/
hThread = (HANDLE)_beginthreadex(NULL, 0, &fun, NULL, 0, &nThradID);
if (hThread == 0)
{
cout << "createThread error;" << GetLastError();
// 如果线程创建失败则事件就没有意义
CloseHandle(hStartEvent);
return 1;
}
/***3:等待事件被触发***/
// 等待事件被设置为被告知状态,类似于阻塞。
// 在消息没有发出时,线程一直处于阻塞状态
// 如果没有则PostThreadMessage可能失败
::WaitForSingleObject(hStartEvent, INFINITE);
int cout = 0;
/***4:消息发送***/
while (true)
{
char * pInfo = new char[MAX_INFO_SIZE]; //要发送的消息内容
sprintf(pInfo, "msg_%d", ++cout);
if (!PostThreadMessage(nThradID, MY_MSG, (WPARAM)pInfo, 0))
{
printf("post message error:%d\n", ::GetLastError());
delete[] pInfo;
}
::Sleep(1000);// sleep后继续发送消息,直到PostThreadMessage成功
}
/***5:关闭事件对象***/
CloseHandle(hStartEvent);
/***5:关闭线程***/
CloseHandle(hThread);
return 0;
}
输出