窗口消息

——Windows核心编程学习手札之二十六

Windows允许一个进程至多建立10000个不同类型的用户对象(user object):图符、光标、窗口类、菜单、加速键表等,当一个线程调用一个函数来建立某个对象时,则该对象就归属这个线程的进程所拥有。这种线程拥有关系的概念对窗口有重要意义:建立窗口的线程必须为窗口处理所有消息。一个线程创建了一个窗口,系统为其分配一个消息队列,用于窗口消息的派送(dispatch),为了使窗口接收这些消息,线程必须有自己的消息循环。一旦线程调用一个与图形界面有关的函数(如检查它的消息队列或建立一个窗口)系统就会为该线程分配一些另外的资源,以便它能够执行与用户界面有关的任务,特别的是,系统分配一个THREADINFO结构,并将这个数据结构和线程联系起来。

THREADINFO结构是一个内部的、未公开的数据结构,用来指定线程的登记消息队列(posted-message queue)、发送消息队列(send-message queue)、应答消息队列(reply-message queue)、虚拟输入队列(virtualized-input queue)、唤醒标志(wake flag)、以及用来描述线程局部输入状态的若干变量。当线程有了与之相联系的THREADINFO结构时,线程就有了自己的消息队列集合。

将消息发送到线程的消息队列中

发送到线程的消息被放置在接收线程的登记消息队列中,发送消息通过函数PostMessage完成:

BOOL PostMessage(HWND hwnd,

UINT uMsg,

WPARAM wParam,

LPARAM lParam);

也可以通过调用PostThreadMessage将消息放置在接收线程的登记消息队列中:

BOOL PostThreadMessage(DWORD dwThreadID,

UINT uMsg,

WPARAM wParam,

LPARAM lParam);

可以通过调用GetWindowsThreadPorcessID来确定是哪个线程建立了一个窗口:

DWORD GetWindowThreadProcessID(HWND hwnd,

PDWORD pdwProcessID);

传递窗口DWORD地址返回拥有该线程的进程ID。

为线程编写主消息循环以便在GetMessage或PeekMessage取出一个消息时,主消息循环代码检查hwnd是否为null,并检查MSG结构的msg成员来执行特殊的处理。如线程确定该消息不被指派给一个窗口,则不调用DispatchMessage,消息循环进行下一个消息。

终止线程的消息循环,可以调用函数PostQuitMessage:

VOID PostQuitMessage(int nExitCode);

类似于调用PostThreadMessage(GetCurrentThreadId(),WM_QUIT,nExitCode,0);

不过,PostQuitMessage并不实际登记一个消息到任何一个THREADINFO结构的队列,只是在内部,PostQuitMessage设定QS_QUIT唤醒标志,并设置THREADINFO结构的nExitCode成员。

向窗口发送消息

使用SendMessage函数可将窗口消息直接发送给一个窗口过程:

LRESULT SendMessage(HWND hwnd,

UINT uMsg,

WPARAM wParam,

LPARAM lParam);

窗口过程将处理这个消息,只有当消息被处理之后,SendMessage才能返回到调用程序。由于具有这个同步特性,比之PostMessage或PostThreadMessage,SendMessage函数用得频繁,调用这个函数的线程在下一行代码执行之前就知道窗口消息已经被完全处理。

如果调用SendMessage的线程向自己创建的窗口发送一个消息:调用指定窗口的窗口过程,将其作为一个子例程,当窗口过程完成对消息的处理,向SendMessage返回一个值,SendMessage再将这个值返回给调用线程。

如果一个线程通过SendMessage向其他线程创建的窗口发送消息:发送线程挂起,由另外线程处理消息并返回。由于可能造成线程挂起(hang):接收线程的bug导致发送线程挂起,可利用4个函数:SendMessageTimeout、SendMessageCallback、SendNotifyMessage、ReplyMessage编写保护性代码防止出现这个情况:

LRESULT SendMessageTimeout(

HWND hwnd,

UINT uMsg,

WPARAM wParam,

LPARAM lParam,

UINT fuFlags,

UINT uTimeout,

PDWORD_PTR pdwResult);

这个函数可以设置等待其他线程返回消息的时间最大值。

线程间发送消息的第二个函数:

BOOL SendMessageCallback(

HWND hwnd,

UINT uMsg,

WPARAM wParam,

LPARAM lParam,

SENDASYNCPROC pfnResultCallBack,

ULONG_PTR dwData);

当发送消息的线程调用该函数时发送消息到接收线程的发送消息队列,并立即返回使发送线程可以继续执行。当接收线程完成对消息的处理时,一个消息被登记到发送线程的应答消息队列中,然后系统通过调用一个函数将这个应答通知给发送线程。

线程发送消息的第三个函数是:

BOOL SendNotifyMessage(

HWND hwnd,

UINT uMsg,

WPARAM wParam,

LPARAM lParam);

该函数将一个消息置于接收线程的发送消息队列中,并立即返回到发送线程,与PostMessage一样,但有两点不同:SendNotifyMessage是向另外的线程建立的窗口发送消息,发送的消息比起接收线程消息队列中存放的登记消息有更高的优先级;当向发送线程自己创建的窗口发送消息时,SendNotifyMessage和SendMessage函数一样,在消息被处理完后才能返回。

用于线程发送消息的第四个函数:

BOOL ReplyMessage(LRESULT lResult);

当发送线程调用ReplyMessage,是告诉系统:消息结果应该包装起来并登记到发送线程的应答消息队列中,唤醒发送线程获得结果并继续执行。

唤醒一个线程

当一个线程调用GetMessage或WaitMessage,但没有对这个线程或这个线程所建立窗口的消息时,系统可以挂起这个线程,这样就不再分配给它CPU时间。当有一个消息登记或发送到这个线程,系统要设置一个唤醒标志,指出现在要给这个线程分配CPU时间,以便处理消息。正常情况下,如果用户不按键或移动鼠标,就没有消息发送给任何窗口。

当一个线程正在运行时,可以通过调用GetQueueStatus函数来查询队列的状态:

DWORD GetQueueStatus(UINT fuFlags);

参数fuFlags是一个标志或一组由OR连接起来的标志,可用来测试特定的唤醒位。

当一个线程调用GetMessage或PeekMessage时,系统必须检查线程的队列状态标志的情况,并确定应该处理哪个消息:

1)如果QS_SENDMESSAGE标志被设置,系统向相应的窗口发送消息;

2)如果消息在线程的登记消息队列中,函数GetMessage或PeekMessage填充传递给它们的MSG结构;

3)如果QS_QUIT标志被设置,GetMessage或PeekMessage返回一个WM_QUIT消息,并复位QS_QUIT标志;

4)如果消息在线程的虚拟输入队列,函数GetMessage或PeekMessage返回硬件输入消息;

5)如果QS_PAINT标志被设置,GetMessage或PeekMessage为相应的窗口返回一个WM_PAINT消息;

6)如果QS_TIMER标志被设置,GetMessage或PeekMessage返回一个WM_TIMER消息;

GetMessage或PeekMessage函数导致一个线程睡眠,也可以利用内核对象或队列状态标志唤醒线程,函数:

DWORD MsgWaitForMultipleObjects(

DWORD nCount,

PHANDLE phObjects,

BOOL fWaitAll,

DWORD dwMilliseconds,

DWORD dwWakeMask);

DWORD MsgWaitForMultipleObjectsEx(

DWORD nCount,

PHANDLE phObjects,

DWORD dwMilliseconds,

DWORD dwWakeMask);

通过消息发送数据

特殊窗口消息WM_COPYDATA可以通过消息在进程间发送和接收数据。

例子:

COPYDATASTRUCT cds;

SendMessage(hwndReceiver,WM_COPYDATA,(WPARAM)hwndSender,(LPARAM)&cds);

其中COPYDATASTRUCT是一个结构,定义在WinUser.h文件中:

typedef struct tagCOPYDATASTRUCT{

ULONG_PTR dwData;

DWORD cbData;

PVOID lpData;

}COPYDATASTRUCT;

当SendMessage看到要发送一个WM_COPYDATA消息时,它建立一个内存映像文件,大小是cbData字节,并从发送进程的地址空间向这个内存映像文件复制数据,然后向目的窗口发送消息。

窗口消息——Windows核心编程学习手札之二十六相关推荐

  1. 插入DLL和挂接API——Windows核心编程学习手札之二十二

    插入DLL和挂接API --Windows核心编程学习手札之二十二 如下情况,可能要打破进程的界限,访问另一个进程的地址空间: 1)为另一个进程创建的窗口建立子类时: 2)需要调试帮助时,如需要确定另 ...

  2. 未处理异常和C++异常——Windows核心编程学习手札之二十五

    未处理异常和C++异常 --Windows核心编程学习手札之二十五 当一个异常过滤器返回EXCEPTION_CONTINUE_SEARCH标识符时是告诉系统继续上溯调用树,寻找另外的异常过滤器,但当每 ...

  3. 异常处理程序和软件异常——Windows核心编程学习手札之二十四

    异常处理程序和软件异常 --Windows核心编程学习手札之二十四 CPU负责捕捉无效内存访问和用0除一个数值这种错误,并相应引发一个异常作为对错误的反应,CPU引发的异常称为硬件异常(hardwar ...

  4. DLL的高级操作技术——Windows核心编程学习手札之二十

    DLL的高级操作技术 --Windows核心编程学习手札之二十 显示加载DLL模块: HINSTANCE LoadLibrary(PCTSTR pszDLLPathName); HINSTANCE L ...

  5. Unicode——Windows核心编程学习手札之二

    Unicode --Windows核心编程学习手札之二 处理软件本地化的核心在于处理不同的字符集.文本串一直作为一系列单字节字符进行编码,并在结尾处放上一个零,当调用strlen函数时,获取以/0结尾 ...

  6. 结束处理程序——Windows核心编程学习手札之二十三

    结束处理程序 --Windows核心编程学习手札之二十三 使用SEH可以只关注程序要完成任务,而运行中发生的错误,系统将会发现并通知.Windows引入SHE是为了便于操作系统的开发,使用SHE所造成 ...

  7. 线程本地存储器——Windows核心编程学习手札之二十一

    线程本地存储器 --Windows核心编程学习手札之二十一 C/C++运行期库使用线程本地存储器,运行期库是在多线程应用程序出现前设计的,因此运行期库里的大多数函数是用于单线程应用程序的.函数strt ...

  8. DLL基础——Windows核心编程学习手札之十九

    DLL基础 --Windows核心编程学习手札之十九 Windows API中的所有函数都包含在DLL中,3个最重要的DLL是Kernel32.dll,它包含用于管理内存.进程和线程的各个函数:Use ...

  9. 内存映射文件——Windows核心编程学习手札之十七

    内存映射文件 --Windows核心编程学习手札之十七 与虚拟内存一样,内存映射文件保留地址空间,并将物理存储器提交给该区域,差别在于所提交的物理存储器是磁盘上有文件存在的空间,而非系统的页文件,一旦 ...

最新文章

  1. celery源码分析:multi命令分析
  2. 复习计算机网络基础 day3---什么是计算机网络:
  3. 使用Trello看板管理项目
  4. (转载)Android手机开发总结
  5. 启明云端方案分享| ESP32-C3智能网关应用解决方案
  6. Django内置的用户认证
  7. linux7 配置mysql5.7字符集编码
  8. (计算机组成原理)第三章存储系统-第二节:基本的半导体原件和存储器芯片的原理
  9. NLP 《seq2seq》
  10. 由H3C高层变动对厂商认证的思考
  11. jasperreport linux 中文不能显示的解决方法
  12. 中广核继续领航光伏风向标 630后首个重大招标项目
  13. 对皮尔逊相关系数进行假设检验
  14. portknocking(端口试探)简介
  15. CST微波工作室 简单微带天线复现(内附模型)
  16. python编程怎么画三角形的外接圆_python画出三角形外接圆和内切圆的方法
  17. Android通过浏览器打开App并传递参数
  18. 链路聚合负载分担方式
  19. US-016超声波测距模块
  20. 【10. 信号量和管程】

热门文章

  1. android 如何打开s3db,[转载]xe5 android 手机上使用sqlite
  2. 洛谷P1816 忠诚 题解
  3. Docker 运行的 应用程序无法连接Oracle数据库的解决办法
  4. [CF960F]Pathwalks
  5. python 大数据学习 遇到的问题,及解决方法。
  6. itertools库
  7. qrcode——js生成二维码
  8. 数据库监视器(SQL Server Profilter)
  9. php关闭warning
  10. oracle9i在window server 2003 sp2 企业版突破1.7G内存限制