u 一个进程至多建立拥有1万个不同类型的用户对象(User Object):图符、光标、窗口类、菜单、加速键表等。
u 窗口和挂钩(hook)由线程拥有。
u 线程(进程)结束,用户对象被OS删除。
u 建立窗口的线程必需为这个窗口处理所有的消息。主消息循环使用GetMessage 或PeekMessage取出一个消息,检查hwnd是否为NULL,并检查MSG来执行特殊处理。如果该消息不被指派给该窗口,则不调用DispatchMessage,并继续取下一个消息。
一、线程的消息队列:
THREADINFO结构指定:
登记(投递)消息队列(Posted-message queue)
发送消息队列(Send-message queue)
应答消息队列(reply-message queue)
虚拟输入队列(virtualized-input queue)
唤醒标志(wake flag)
线程局部输入状态的变量
二、将消息发送到线程的消息队列中(登记消息队列,登记后立即返回):
BOOL PostMessage(HWND hwnd, //
UINT uMsg,
WPARAM wParam,
LPARAM lParam);
BOOL PostThreadMessage(DWORD
UINT uMsg,
WPARAM wParam,
LPARAM lParam);
VOID PostQuitMessage(int nExitCode);// 终止线程的消息循环。但他并不实际登记一个消息,只设定QS_QUIT唤醒标志和nExitCode值!
注:确定哪个线程建立了一个窗口的函数(返回线程ID):
DWORD GetWindowThreadProcessId(hwnd, pProcId);// pProcId是进程,可以是NULL
三、向窗口发送消息(直接发送到窗口过程,消息被处理后才返回!):
LRESULT SendMessage(HWND hwnd, UINT uMsg, wParam, lParam);
工作原理:
1. 如果SendMessage向线程自己的窗口发送消息,很简单:调用窗口过程。
2. 如果向其他线程(进程)的窗口发送消息,较复杂:调用SendMessage的线程挂起(hung,idle,仅执行一个任务:如果另外的线程向该线程的窗口发送消息,则系统立即处理发送的消息,而不必等待GetMessage等被调用);系统把消息追加到接收线程的发送消息队列(Send-message queue),并设定QS_SENDMESSAGE标志;在接收线程等待消息(调用GetMessage、PeekMessage、WaitMessage)时,系统从消息队列中取出第一个消息并调用窗口过程来处理;如果消息队列空,则QS_SENDMESSAGE标志被关闭;其消息处理后,窗口过程返回值被登记到发送线程(调用SendMessage的线程)的应答消息队列(reply-message queue),发送线程被唤醒,取出应答消息队列的返回值(就是调用SendMessage的返回值)
如果处理消息的线程含有错误进入死循环,会导致发送消息的线程被挂起。可以使用下面的函数以免被挂起:
n LRESULT SendMessageTimeout( // 规定消息返回时间,执行成功返回TRUE
HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT fuFlags, UINT uTimeout/*等待的毫秒数*/, PDWORD_PTR pdwResult);
u fuFlags参数(可以传递下列标志的组合):
SMTO_ABORTIFHUNG:查看接收消息的线程是否处于挂起状态。是,立即返回;
SMTO_NOTIMEOUTIFNOTHUNG:如接受线程没有挂起,不考虑时间限制;
SMTO_BLOCK:使调用线程在函数未返回前,不再处理任何其他发送来的消息;
SMTO_NORMAL:不指定任何其他标志。
u 如果调用SendMessageTimeout向线程自己的窗口发送消息,调用函数后的代码要等消息被处理完后才开始执行!
n BOOL SendMessageCallback(
HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
SENDASYNCPROC pfnResultCallBack, ULONG_PTR dwData);
u 发送消息后,立即返回。
u 可用于向系统中所有窗口广播消息,并检查每一个返回结果(使用SendMessage(HWND_BROADCAST,…)也可以广播消息,但只有一个LRESULT返回值)。
u 接收线程完成对消息的处理后,系统调用应答函数(pfnResultCallBack 为这个函数的地址),其原型:
VOID CALLBACK ResultCallBack(
HWND hwnd, // 处理消息的窗口句柄
UINT uMsg, // 消息值
ULONG_PTR dwData, // 等于SendMessageCallback的dwData
LRESULT lResult); // 处理消息的窗口过程返回的结果
u 如果调用SendMessageCallback向线程自己的窗口发送消息,系统立即调用窗口过程,处理完后,调用ResultCallBack函数,返回后,系统从调用SendMessageCallback函数后的代码开始执行!
n BOOL SendNotifyMessage(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam);
u 将消息置于接收线程的发送消息队列中,并立即返回调用线程(似PostMessage),但有区别:
u SendNotifyMessage向窗口发送消息,比在线程消息队列存放登记的消息有优先权!
u 如果向线程自己的窗口发送消息,同SendMessage完全一样!
u 发送给窗口的大多数消息用于通知(WM_ACTIVATE、WM_DESTROY、WM_ENABLE、WM_SIZE、WM_SETFOCUS、WM_MOVE),但WM_CREATE消息,则在窗口处理完这个消息之前,系统必须等待,如果返回-1,则不建立窗口。
n BOOL ReplyMessage(LRESULT lResult);// lResult指出消息处理的结果。
u 为了接收窗口消息,必须在接收消息的窗口过程中调用。
u 编写保护性代码,不建议用上述三个Send*函数代替SendMessage,而是依靠窗口过程的实现线程调用ReplyMessage
u 如果在处理同一个线程发来的消息时调用ReplyMessage,则ReplyMessage什么也不做。
u 查询是处理线程间或线程内的消息,可以调用:
BOOL InSendMessage();// 处理线程间发送的消息,返回TRUE
DWORD InSendMessageEx(PVOID pvReserved);// 线程内消息,返回ISMEX_NOSEND
四、唤醒一个线程(线程调用GetMessage等,如果没有消息,则被挂起):
1. 队列状态标志:
查询函数:DWORD GetQueueStatus(UINT fuFlags);// 传入检查的消息类型。
返回值:高字(两字节)为当前消息的类型;低字为已经添加到队列尚未处理的消息类型。
QS_MOUSEMOVE,QS_KEY,QS_MOUSEBUTTON,QS_HOTKEY:只要队列中有就被设定;
QS_PAINT:线程建立的窗口有无效区域;
QS_POSTMESSAGE:登记消息队列中有消息;
QS_TIMER:定时器;
QS_SENDMESSAGE:发送消息队列中有消息;
QS_QUIT:线程调用PostQuitMessage(只设置标志,队列中无WM_QUIT消息)。
2. 从线程的队列中提取消息的算法:
查询顺序:QS_ SENDMESSAGE à QS_POSTMESSAGE à QS_QUIT à QS_INPUT à QS_PAINT à QS_TIMER
3. 利用内核对象或队列状态标志唤醒线程:
DWORD MsgWaitForMultipleObjects(
DWORD nCount, // < MAXIMUM_WAIT_OBJECT(64)
PHANDLE phObjects,
BOOL fWaitAll, // FALSE:其一内核对象有信号或当指定的消息类型出现时,函数返回;TRUE:所有内核对象有信号并且指定的消息类型出现才返回。
DWORD dwMilliseconds,
DWORD dwWakeMask);// 告诉系统何时让事件成为有信号状态,取值同GetQueueStatus。
DWORD MsgWaitForMultipleObjectsEx(
DWORD nCount,
PHANDLE phObjects,
DWORD dwMilliseconds,
DWORD dwWakeMask
DWORD dwFlags);
类似于WaitForMultipleObjects函数。
不同点:当一个内核对象变成有信号状态(signaled)或当一个窗口消息需要派送到调用线程建立的窗口时,用于线程调度。
五、通过消息发送数据:
lParam参数指出内存块的地址。
u 如果是WM_SETTEXT或WM_GETTEXT消息:则把lParam指定的内存空间中以0结尾的字符串放入一个内存映射文件中(可跨进程共享)。
u 进程间建立用户自定义信息(WM_USER+x):可以使用特殊的窗口消息:WM_COPYDATA:(需先初始化COPYDATASTRUCT结构),系统建立内存映射文件,并复制数据,接收的窗口过程的lParam参数指向这个结构。
typedef struct tagCOPYDATASTRUCT {
ULONG_PTR dwData; // 备用,可存放任何值
DWORD cbData; // 发送的字节数
PVOID lpData; // 指向要发送的第一个字节
}COPYDATASTRUCT;
注:只能发送这个消息,不能登记这个消息!在SendMessage未返回前,发送线程不应该修改COPYDATASTRUCT的内容!可以实现16位和32位程序之间的通信(也可实现32位与64位之间的通信)。
六、Windows如何处理ANSI/Unicode字符和字符串:
取决于注册窗口类时所使用的函数RegisterClassA、RegisterClassW(RegisterClass要看是否定义了UNICODE)。
可以查看:BOOL IsWindowUnicode(HWND hwnd);// 如果要求Unicode,返回TRUE
但实际上,如果向要求Unicode的窗口发送ANSI字符串,系统会自动转换。