C26、窗口消息

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 PostMessageHWND hwnd, //

UINT uMsg,

WPARAM wParam,

LPARAM lParam);

BOOL PostThreadMessageDWORD

UINT uMsg,

WPARAM wParam,

LPARAM lParam);

VOID PostQuitMessageint nExitCode);// 终止线程的消息循环。但他并不实际登记一个消息,只设定QS_QUIT唤醒标志和nExitCode值!

 

注:确定哪个线程建立了一个窗口的函数(返回线程ID):

DWORD GetWindowThreadProcessIdhwnd, pProcId);// pProcId是进程,可以是NULL

三、向窗口发送消息(直接发送到窗口过程,消息被处理后才返回!):

LRESULT SendMessage(HWND hwnd, UINT uMsg, wParam, lParam);

工作原理:

1.         如果SendMessage向线程自己的窗口发送消息,很简单:调用窗口过程。

2.         如果向其他线程(进程)的窗口发送消息,较复杂:调用SendMessage的线程挂起(hungidle,仅执行一个任务:如果另外的线程向该线程的窗口发送消息,则系统立即处理发送的消息,而不必等待GetMessage等被调用);系统把消息追加到接收线程的发送消息队列(Send-message queue),并设定QS_SENDMESSAGE标志;在接收线程等待消息(调用GetMessagePeekMessageWaitMessage)时,系统从消息队列中取出第一个消息并调用窗口过程来处理;如果消息队列空,则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,        //       等于SendMessageCallbackdwData

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_ACTIVATEWM_DESTROYWM_ENABLEWM_SIZEWM_SETFOCUSWM_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_SETTEXTWM_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如何处理ANSIUnicode字符和字符串:

取决于注册窗口类时所使用的函数RegisterClassARegisterClassWRegisterClass要看是否定义了UNICODE)。

可以查看:BOOL IsWindowUnicode(HWND hwnd)// 如果要求Unicode,返回TRUE

但实际上,如果向要求Unicode的窗口发送ANSI字符串,系统会自动转换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值