代码改变世界

噢,我的第一个基于SDK的窗口

2004-09-10 18:14  FantasySoft  阅读(...)  评论(...编辑  收藏

        昨天看WinMain函数的修饰符WINAPI,看得眩晕,今天专注于WinMain函数的过程也不轻松。为了弄明白一个窗口从创建到销毁的过程,也是大费周章。不过还好,能够看到自己建造的第一个最原始的窗口,总算有了点满足感。
        创建一个窗口涉及了几个概念:HWND、WNDCLASS(WNDCLASSEX)、RegisterClass(RegisterClassEx)和CreateWindow
(CreateWindowEx)。HWND就是Window Handle,它的实质就是一个指针,指向被创建的窗口对象;WNDCLASS(WNDCLASSEX)本身是一个结构体,代表着窗口对象,里面包含了与窗口对象紧密相关的属性;RegisterClass(RegisterClassEx)是用以注册窗口对象的函数;CreateWindow(CreateWindowEx)则是用以创建窗口对象,并且返回一个HWND。其中括号的内容是一一对应,就是说如果定义了WNCLASSEX,就必须用RegisterClassEx去注册,用CreateWindowEx去创建WNDCLASSEX定义的窗口对象。
       创建一个看得见摸得着的窗口,主要有以下四步:
       1、利用WNDCLASS(WNDCLASSEX)定义窗口对象;
       2、调用RegisterClass(RegisterClassEx)函数注册窗口对象;
       3、调用CreateWindow(CreateWindowEx)方法创建已经定义并且注册的窗口对象;
       4、定义负责处理消息队列的Window Procedure。
       以下为一个很简单的例子:

#include <windows.h>   
LRESULT CALLBACK WindowProc(HWND hwnd,UINT msg,WPARAM wparam, LPARAM lparam);

int WINAPI WinMain(HINSTANCE hinstance,
     HINSTANCE hprevinstance,LPSTR lpcmdline,
int
 ncmdshow)
{

 WNDCLASSEX winclass 
= {0}

    HWND hwnd;  
    MSG  msg;   

winclass.cbSize 
= sizeof
(WNDCLASSEX);
winclass.style 
= CS_HREDRAW |
 CS_VREDRAW;
winclass.lpfnWndProc 
=
 WindowProc;
winclass.hInstance 
=
 hinstance;
winclass.hbrBackground 
=
 (HBRUSH)GetStockObject(BLACK_BRUSH);
winclass.lpszClassName 
= "WinInit"
;

RegisterClassEx(
&
winclass);

hwnd 
= CreateWindowEx(NULL, "WinInit""First Window" 

       WS_OVERLAPPEDWINDOW | WS_VISIBLE, 
00
       200200, NULL, NULL, hinstance, NULL);
for
(;;)
 
{
 
if (PeekMessage(&msg,NULL,0,0
,PM_REMOVE))
    

       
if (msg.message ==
 WM_QUIT)
           
break
;   
    TranslateMessage(
&
msg);  
    DispatchMessage(
&
msg);
    }
 
 }
 
return
(msg.wParam);
}
 

LRESULT CALLBACK WindowProc(HWND hwnd,UINT msg,WPARAM wparam, LPARAM lparam)
{  
 
return
 DefWindowProc(hwnd, msg, wparam, lparam);
}
 

          针对以上的例子,再做一些说明。WNDCLASSEX是一个结构体,具体定义如下:

typedef struct { UINT cbSize; UINT style;WNDPROC lpfnWndProc;
           
int cbClsExtra;int
 cbWndExtra;HINSTANCE hInstance;
           HICON hIcon;HCURSOR hCursor;HBRUSH hbrBackground;
           LPCTSTR lpszMenuName;LPCTSTR lpszClassName;HICON hIconSm;
           }
 WNDCLASSEX, *PWNDCLASSEX;

从MSDN上的WNDCLASSEX的Reference可以找到每个属性的具体含义,这里就不赘述了。在所有属性中,有三个属性要注意的,一个就是cbSize,一个是lpfnWndProc,另外一个是lpszClassName。cbSize是指WNDCLASSEX结构体本身的大小,这个属性就象提示信息一样,当一个指向该结构体本身的指针被传递至一个函数的时候,函数可以检查这个字段以获得该结构体的大小,我们使用sizeof(WNDCLASSEX)的返回值来设定该属性;lpfnWndProc则是处理该WNDCLASS的window procedure指针,例子中就是回调函数WindowProc的指针了。一般的,window procedure就是一个处理发送至某个窗口的消息的函数;lpszClassName则是一个维系着RegisterClassEx和CreateWindowEx的桥梁。当RegisterClassEx函数去注册一个WNDCLASSEX的时候,会将WNDCLASSEX中的lpszClassName进行注册。而后CreateWindowEx函数创建窗口对象的时候也是通过这个lpszClassName跟已经定义好的WNDCLASSEX相关联。
RegisterClassEx函数相对就比较简单,就只有一个参数:WNDCLASSEX的地址;CreateWindowEx函数的具体定义如下

HWND CreateWindowEx(DWORD dwExStyle,LPCTSTR lpClassName,LPCTSTR lpWindowName,DWORD dwStyle,
       
int x,int y,int nWidth,int
 nHeight,HWND hWndParent,HMENU hMenu,
       HINSTANCE hInstance,LPVOID lpParam);

在MSDN上的CreateWindowEx的Reference也能找到你想要的所有信息。在这里,要注意的就是lpClasName跟前面的lpszClassName是对应的,也就是例子中的"WinInit"。在回调函数WinProc中,我们调用了DefWindowProc方法。这个方法会调用默认的window procedure去处理发送至窗口的每一条WinProc函数没有处理的消息,在例子中,是将所有的消息处理都交给了DefWindowProc。好了,将这段代码在VC++上编译并且运行就可以看到一个窗口显示在屏幕上了。
        由于昨天一位大侠指出我的示例代码中,用了new分配内存,却没有使用delete去释放,着实让我脸红了好久。这
次,我特别注意的review了一下自己写的代码,咦,怎么只有窗体创建的代码,却没有销毁窗体并且释放占用的内存的代码呢?难道,当我点击了窗口上小交叉(关闭)按钮,系统会自动完成这样的工作?我想这应该是不可能的。于是,我将这段程序反复运行了几次,关了窗口再运行,然后通过任务管理器去查看,果然发现了几个相同的进程。
        经过查阅资料以及自己的试验,找到了问题的原因。原来是因为点击关闭按钮,系统想窗体的线程队列发送了
WM_CLOSE消息,而DefWindowProc接收到WM_CLOSE消息之后,确实是调用了DestroyWindow API以销毁窗体并释放窗体所占用的资源,并向窗体的线程队列发送WM_DESTROY消息。由此就可以看出窗体被销毁了,但是系统为窗体创建的线程队列仍然是存在的,WinMain函数的无限循环仍然会继续。而DefWindowProc函数并不会去处理WM_DESTROY消息,因此我们就需要自己去写处理该消息的代码了。只要将回调函数WindowProc中增加处理WM_DESTROY的代码,利用PostQuitMessage函数向消息队列中发送WM_QUIT即可。

LRESULT CALLBACK WindowProc(HWND hwnd,UINT msg,WPARAM wparam, LPARAM lparam)
{
  
switch
(msg)
  
{
   
case
 WM_DESTROY:
     PostQuitMessage(
0
);
     
return 0
;
     
break
;
  }

 
return DefWindowProc(hwnd, msg, wparam, lparam);
}