[WPF] Felix 的线程学习笔记(一)——从Win32的消息循环说起
大学上了三年了,越来越觉得自己原来学的是皮毛。静下心来看看线程的时候才发现发现操作系统方面的知识对一个学编程的人来说时多么的重要,幸好当时教OS的时候没翘课……废话不多说了,还是写我的笔记吧。
第一篇 从Win32的消息循环说起
消息循环是Win32处理消息的机制。然而随着面向对象的不断发展,这些消息的激发,处理逐渐被封装到Framework中,从而减少了我们了解它们的机会。但如果不刨根问底儿地把它搞清楚的话,我们想灵活运用framework中的类就比较困难。至少我是这么觉着的……
Win32程序可以说是一个不断等待消息,处理消息的过程。我们将消息分为两类:队列消息和非队列消息。
Win32处理消息使用的是队列机制,消息队列分为两种:系统消息队列(System Message Queue)和线程消息队列(Thread-specific Message Queue)。下面一一道来:
系统消息队列(System Message Queue)是系统唯一的队列,它由Windows来维护。
线程消息队列(Thread-specific Message Queue)则由每个GUI线程自己进行维护,为避免给non-GUI现成创建消息队列,所有线程产生时并没有消息队列,仅当线程第一次调用GDI函数数系统给线程创建一个消息队列。
工作过程:当用户有输入时,设备驱动(mouse, keyboard)会把操作输入转化成消息存在系统队列中,然后系统会把此消息放到目标窗口所在的线程的消息队列(thread-specific message queue)中。线程再从自己的线程消息队列中提取出这个消息,进行处理。一般来讲,系统总是将消息Post在消息队列的末尾。这样保证窗口以先进先出的顺序接受消息。然而,WM_PAINT是一个例外,同一个窗口的多个 WM_PAINT被合并成一个 WM_PAINT 消息,合并WM_PAIN的目的是为了减少刷新窗口的次数。
队列消息:由用户的鼠标移动所产生的消息(WM_MOUSERMOVE),键盘上非功能键的敲击所产生的消息,都是队列消息(WM_CHAR),以及WM_PAINT等都是队列消息。这些消息都会按照上面所说的工作过程被处理。
非队列消息:这些消息的处理则跳过上述两个队列,一旦产生,则立刻被发送到窗口处理过程。如窗口激活时用的消息(WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR)。
我们以队列消息为例,刚才说到队列消息会进入线程消息对联等待处理。那么如何处理呢?消息循环又是什么?我们从一段用win32 API写的代码说起:
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow){
MSG msg;
if (!InitInstance (hInstance, nCmdShow)){
return FALSE;
}
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0)){
TranslateMessage(&msg); DispatchMessage(&msg);
}
return (int) msg.wParam;
}
// 函数: WndProc(HWND, unsigned, WORD, LONG)
// 用途: 处理主窗口的消息。
LRESULT CALLBACK WndProc(
HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
int wmId, wmEvent; PAINTSTRUCT ps;
HDC hdc;
switch (message){
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// 剖析菜单选取项目:
switch (wmId){
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX,hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message,wParam,lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此加入任何绘图程序代码...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message,wParam, lParam);
}
return 0;
}
// [关于] 方块的消息处理例程。
LRESULT CALLBACK About(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam){
switch (message){
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL){
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
}
注:代码非原创,引自 开发者在线 Builder.com.cn ,原文链接:
http://www.builder.com.cn/2007/1103/602710.shtml
_tWinMain是入口函数,经过初始化后,进入的就是消息循环代码while (GetMessage(&msg, NULL, 0, 0))。因此消息循环就是windows以循环的方式来不断地探知消息队列中有无消息的过程,一旦有,就进行处理。如之前所说,当一个队列消息产生时,这个消息经由系统消息队列来到了线程消息队列。GetMessage()从线程队列中取得这个消息,然后自然就是while循环体的事情了。
从循环体中的代码我们可以看出,它要做的是翻译消息,根据消息内容来决定消息应该送给哪个Windows过程(Windows Procedure)(WndProc),这就称为消息分发(Message Dispatch)。通常“每一种”窗口或控件(control)都有一个Windows Procedure,来处理该种窗口/控件的行为。
被调用的Windows Procedure根据消息内容来决定应该调用哪个函数(利用Switch/Case语法)。
这样等待消息,提取消息,处理消息的过程不断重复。当GetMessage()获得的消息是WM_QUIT时,消息循环(也就是这里的while)将会被退出。
过程用途表示如下:
那么,在.net framework中,一个窗体的消息处理机制又是如何呢?那些消息处理的API都被封装成了些啥呢?我们从.net form程序说起:
/// Form1 的摘要描述。
public class Form1 : Form{
/// 设计工具所需的变数。
private Container components = null;
public Form1(){
AutoScaleBaseSize = new Size(5, 15);
ClientSize = new Size(292, 266);
Name = "Form1";
Text = "Form1";
Paint += new PaintEventHandler(this.Form1_Paint);
Paint += new PaintEventHandler(this.Form1_Paint2);
}
/// 应用程序的主进入点。
[STAThread]
static void Main(){
Application.Run(new Form1());
}
protected override void OnPaint(PaintEventArgs e){
base.OnPaint (e); // 2
}
private void Form1_Paint(object sender, PaintEventArgs e){
// 3
}
private void Form1_Paint2(object sender, PaintEventArgs e){
// 4
}
protected override void WndProc(ref Message m){
base.WndProc (ref m); // 1
}
}
}
注:此代码及下面的解说引用自 开发者在线 Builder.com.cn
原文链接:http://www.builder.com.cn/2007/1103/602726.shtml
1、在Main()中,利用Application.Run()来将Form1窗口显示出来,并进入消息循环。[STAThread]表示该应用程序使用了STA模型,即应用程序的状态为单线程。
2、OS在此Process的消息队列内放进一个WM_PAINT消息,好让窗口被显示出来。
3、WM_PAINT被Application.Run()内的消息循环取出来,分发到WndProc()。由于WndProc()被覆写(override),此次调用(invoke)到的WndProc()是属于Form1的WndProc(),也就是上述程序中批注”1”的地方,而不是调用到 Control.WndProc()。
4、在Form1.WndProc()的最后,有调用base.WndProc(),这实际上调用到Control.WndProc()。
5、Control.WndProc()从Message参数中得知此消息是WM_PAINT,于是调用OnPaint()。由于OnPaint()也被覆写,此次调用到的OnPaint()是属于Form1的OnPaint(),也就是上述程序中批注2的地方,而不是调用到 Control.OnPaint()。
6、在Form1.OnPaint()的最后,有调用base.OnPaint(),这实际上调用到Control.OnPaint()。
7、我们曾经在Form1的构造函数(constructor)中将Form1_Paint()与Form1_Paint2()登记成为Paint事件处理函数(Event Handler)。Control.OnPaint()会去依序去调用这两个函数,也就是上述程序中批注3与4的地方。
综上,我的理解是.net framework消息处理中已经没有消息循环的显示体现,即代码中没有while(),这个过程被隐藏了。消息的产生,处理被封装成event,eventHandler,即通过事件,处理事件的委托,和委托所绑定的函数来体现消息的处理机制。这是我自己的理解,估计比较片面,呵呵。
这样,第一篇笔记就到这里了,做为我以后的复习只用,也希望对看到的人有所帮助。
------------------------------------------------
Felix原创,转载请注明出处,感谢博客园!
posted on 2010-04-09 22:24 Felix Fang 阅读(749) 评论(1) 编辑 收藏 举报