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;
}

输出

Windows多线程编程

相关文章: