Windows消息机制学习笔记(二)—— 窗口与线程
要点回顾
一个GUI线程对应一个消息队列
本篇要解决的问题:
- 消息从哪里来?
- 消息到哪里去?
- 谁来做这些事情?
消息从哪里来?
1)Spy++ 捕捉消息:鼠标消息、键盘消息
2)程序发送消息
实验一:Spy++捕获消息
1)编译并运行以下代码
#include <windows.h>
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
){
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd
){
//窗口的类名
TCHAR className[] = "My First Window";
//创建一个自己的窗口
WNDCLASS wndclass = {0};
wndclass.hbrBackground = (HBRUSH)COLOR_MENU;
wndclass.lpfnWndProc = WindowProc;
wndclass.lpszClassName = className;
wndclass.hInstance = hInstance;
//注册
RegisterClass(&wndclass);
//创建窗口
HWND hwnd = CreateWindow(
className,
TEXT("我的第一个窗口"),
WS_OVERLAPPEDWINDOW,
10,
10,
600,
300,
NULL,
NULL,
hInstance,
NULL);
if(hwnd == NULL)
return 0;
//显示窗口
ShowWindow(hwnd, SW_SHOW);
//消息循环
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
运行结果
2)使用spy++定位窗口
3)在窗口中进行任意操作,例如鼠标移动,鼠标点击,键盘敲击等,观察消息列表
实验二:消息捕获
1)进程A运行以下代码
#include <windows.h>
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
){
switch(uMsg)
{
case 0x401:
MessageBoxA(NULL, "接收到消息", "新消息", MB_OK);
return false;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd
){
//窗口的类名
TCHAR className[] = "My First Window";
//创建一个自己的窗口
WNDCLASS wndclass = {0};
wndclass.hbrBackground = (HBRUSH)COLOR_MENU;
wndclass.lpfnWndProc = WindowProc;
wndclass.lpszClassName = className;
wndclass.hInstance = hInstance;
//注册
RegisterClass(&wndclass);
//创建窗口
HWND hwnd = CreateWindow(
className,
TEXT("我的第一个窗口"),
WS_OVERLAPPEDWINDOW,
10,
10,
600,
300,
NULL,
NULL,
hInstance,
NULL);
if(hwnd == NULL)
return 0;
//显示窗口
ShowWindow(hwnd, SW_SHOW);
//消息循环
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
2)进程B运行以下代码
#include <stdio.h>
#include <windows.h>
int main()
{
HWND hwnd = FindWindow("My First Window", "我的第一个窗口");
SendMessage(hwnd, 0x401, 0, 0);
return 0;
}
执行结果
消息到哪里去?
描述:
- 当我们使用鼠标某个窗口进行点击与滑动时,都会产生一个消息,消息会进入当前窗口对应线程的消息队列中
- 当我们编写程序时,并不会去特地启动两个线程去监控鼠标和键盘,w32k.sys负责了这个事情
当初始化w32k.sys这个模块时,会调用一个叫做InitInputImpl的函数
这个函数会启动两个线程,分别用来监控鼠标和键盘,这两个线程都是0环的线程
平时我们的电脑遭遇“死机”时,常常是屏幕动不了,鼠标还能动,这正式由于鼠标是有一个独立的线程在监控它的行动
//FROM ReactOS v3.12
InitInputImpl(VOID)
{
...
Status = PsCreateSystemThread(&RawInputThreadHandle, //监控鼠标
THREAD_ALL_ACCESS,
NULL,
NULL,
&RawInputThreadId,
RawInputThreadMain,
NULL);
if (!NT_SUCCESS(Status))
{
DPRINT1("Win32K: Failed to create raw thread.\n");
}
Status = PsCreateSystemThread(&KeyboardThreadHandle, //监控键盘
THREAD_ALL_ACCESS,
NULL,
NULL,
&KeyboardThreadId,
KeyboardThreadMain,
NULL);
if (!NT_SUCCESS(Status))
{
DPRINT1("Win32K: Failed to create keyboard thread.\n");
}
...
}
思考:w32k.sys如何区分用户点击的是哪个窗口/消息要去哪个线程
窗口在哪?
当调用CreateWindow时,该函数实际上是一个宏,其CreateWindowA实际对应CreateWindowExA函数,CreateWindowW对应CreateWindowExW函数,可在编辑器中跟踪观察
实验:分析CreateWindowExW
函数位于user32.dll中
总结:窗口在0环被画出(由w32k.sys实现)
窗口对象
描述:
- 每个窗口对应一个WINDOW_OBJECT结构体,位于0环,包含当前窗口所有信息
- 除了大窗口之外,窗口中的每个控件也都是一个窗口,也分别对应一个WINDOW_OBJECT结构体
- 一个窗口内包含着许多个窗口,按钮,对话框也都属于窗口,属于同一个线程
- 一个线程可以包含多个窗口,但每个窗口只能属于一个线程
//FROM ReactOS v3.12
typedef struct _WINDOW_OBJECT
{
...
PWND Wnd; //包含大量窗口信息
PTHREADINFO pti; //线程
...
}
//FROM ReactOS v3.12
typedef struct _WND
{
...
/* Style. */
DWORD style; //包含窗口风格/后一个窗口/前一个窗口/父窗口/子窗口等信息
...
} WND, *PWND;
消息进入窗口消息队列的过程:
- 当使用鼠标在某个窗口上点击时,鼠标监控线程检测到点击的窗口对象
- 根据窗口对象成员,找到窗口对应线程
- 将消息放入该线程的消息队列中
注意:在3环得到的窗口的句柄只是一个索引(参考句柄表章节)
总结
- 窗口在0环创建
- 窗口句柄是全局的
- 一个线程可以使用多个窗口,但每个窗口只能属于一个线程