DotNet剖析系列(一)

DotNet剖析系列(一)

            ——窗口从生到死

 

如果你的第一个Windows程序是从C#开始的,并且你是使用VS.net的模板来创建程序,可能你会习惯于看到以下的代码,也许并不会想到它背后有些什么运作。

[STAThread]

 static void Main()

{

     Application.Run(new Form1());

}

但是我感到很疑惑,没有了消息循环,不需写WndProc,难道Dotnet下不再需要这些,还是MS把它们包装起来了。《Windows程序设计》开篇就是告诉你写一个Hello,World,我想下面这段代码大家都不会陌生,列在这儿只是为了让大家回顾一下。

HELLOWIN.C

 

#include <windows.h>

 

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                   PSTR szCmdLine, int iCmdShow)

{

    static TCHAR szAppName[] = TEXT ("HelloWin") ;

       

    HWND   hwnd ;

       

    MSG    msg ;

       

    WNDCLASwndclass ;

       

    wndclass.style = CS_HREDRAW | CS_VREDRAW ;       

    wndclass.lpfnWndProc  = WndProc ;

    wndclass.cbClsExtra   = 0 ;

    wndclass.cbWndExtra  = 0 ;

    wndclass.hInstance     = hInstance ;

    wndclass.hIcon        = LoadIcon (NULL, IDI_APPLICATION) ;

    wndclass.hCursor      = LoadCursor (NULL, IDC_ARROW) ;

    wndclass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH) ;

    wndclass.lpszMenuNam  = NULL ;

    wndclass.lpszClassName= szAppName ;

   

if (!RegisterClass (&wndclass))

    {

        MessageBox (  NULL, TEXT ("This program requires Windows NT!"),

                                      szAppName, MB_ICONERROR) ;

        return 0 ;

    }

       

    hwnd = CreateWindow( szAppName,      // window class name

                       TEXT ("The Hello Program"),   // window caption

                       WS_OVERLAPPEDWINDOW,  // window style

                       CW_USEDEFAULT,// initial x position

                       CW_USEDEFAULT,// initial y position

                       CW_USEDEFAULT,// initial x size

                       CW_USEDEFAULT,// initial y size

                       NULL,                 // parent window handle

                       NULL,            // window menu handle

                       hInstance,   // program instance handle

                       NULL) ;      // creation parameters

       

    ShowWindow (hwnd, iCmdShow) ;

    UpdateWindow (hwnd) ;

   

    while (GetMessage (&msg, NULL, 0, 0))

    {

         TranslateMessage (&msg) ;

         DispatchMessage (&msg) ;

    }

       

    return msg.wParam ;

    }

       

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{

    HDC                   hdc ;

    PAINTSTRUCT ps ;

    RECT          rect ;

       

    switch (message)

    {

        case WM_CREATE:

            PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;

            return 0 ;

        case WM_PAINT:

            hdc = BeginPaint (hwnd, &ps) ;

            GetClientRect (hwnd, &rect) ;

            DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect,

                         DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;

            EndPaint (hwnd, &ps) ;

            return 0 ;

        case WM_DESTROY:

            PostQuitMessage (0) ;

            return 0 ;

     }       

  return DefWindowProc (hwnd, message, wParam, lParam) ;       

}

 

上面的代码很清楚地显示传统Windows程序用消息泵来传递消息的机制。

我们还是拿出工具Reflector,仔细来看看到底在Application.Run中发生了什么?

public static void Run(Form mainForm){
      Application.ThreadContext.FromCurrent().RunMessageLoop(-1, new ApplicationContext(mainForm));}

可以看到实际上调用了Application内部定义的类ThreadContextRunMessageLoop方法,而ApplicationContext是有关应用程序线程的上下文信息,而RunMessageLoop调用了下面的内部方法:

private void RunMessageLoopInner(int reason, ApplicationContext context)

这个函数很长,我们只列出其中有关的部分(代码有删节)

if (reason == -1)

{

   this.applicationContext = context;

   this.applicationContext.ThreadExit += new EventHandler(this.OnAppThreadExit);

   if (this.applicationContext.MainForm != null)

   { 

this.applicationContext.MainForm.Visible = true

}

}

if (context != null)

{

    this.currentForm = context.MainForm;

}

 

try

{

 

flag2 = this.LocalModalMessageLoop(this.currentForm);       }

}

finally

            this.Dispose(); 

}

可以看到它完成了两个部分,一是将窗体设为可见,另一个是呼叫LocalModalMessageLoop,嗯,好象有那么点意思了。是不是对应了经典程序中的ShowWindow和消息泵呀,那我们再看看LocalModalMessageLoop是不是起到了消息泵的作用呢,还是来看源码。

bool flag2 = true;

while (flag2)

{

UnsafeNativeMethods.GetMessageW(out msg1, NativeMethods.NullHandleRef, 0, 0)//还有一些判断代码

{

    UnsafeNativeMethods.TranslateMessage(out msg1);

    UnsafeNativeMethods.DispatchMessageW(ref msg1);

}

}

同样上面的代码是一个小部分,但是关键部分,清楚地看到跟原来的处理方式是一样的。需要说明的是实际上CLR考虑了在非UnicodeWindows系统上调用不同的函数如DispatchMessageA之类的。另外UnsafeNativeMethods这个类中的方法的模块实际上就是对WinAPI的简单引用,不再详细说明。

现在消息泵有了,那么WndProc在哪里呢,消息是处理成事件的呢,还是有这个东东,在Form类里不过它是一个保护方法

protected override void WndProc(ref Message m)

代码也很清楚,跟老版的差不多,来看一小段

protected override void WndProc(ref Message m)

        {

            switch (m.Msg)

            {

                case 5:

                    this.WmSize(ref m);

                    return;

 

                case 6:

                    this.WmActivate(ref m);

                    return;

 

                case 1:

                    this.WmCreate(ref m);

                    return;

 

                case 0x10:

                case 0x11:

                case 0x16:

                    this.WmClose(ref m);

                    return;

 

                case 0x18:

                    this.WmShowWindow(ref m);

                    return;

            }

            base.WndProc(ref m);

         }

是不是还是老样子呀。呀,有一点不一样,最后是调用Control类的WndProc,实际上ControlWndProc在最后调用了ControlDefWndProc,它还恰恰是个虚函数。可以看到每个消息最后转成了WmXXXX之类的函数调用,我们来看WmClose有些什么,下面代码仍是选择了部分关键代码。

this.OnClosing(args1);

    if (m.Msg == 0x11)

    {

            m.Result = args1.Cancel ? IntPtr.Zero : ((IntPtr) 1);

    }

 

    if ( !args1.Cancel)

    {

 

        this.OnClosed(EventArgs.Empty);

        base.Dispose();

 

    }

嗯,现在是不是知道DotNet中的事件是如何产生的了吧。最后看到base.Dispose,知道我们的窗口要结束了,然后VS模板中生成的Dispose就会调用。

CLR通过一系列的包装将消息泵和消息进行包装,形成了我们现在熟悉的事件,并且对多种平台进行了综合考虑,不需要程序员再去分别考虑Unicode的问题,真是很方便了,不过它的运行机理没有改变。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值