如果把操作系统比喻成一个生产汽车的公司;应用程序在内存的那块空间比喻成为一个汽车工厂的厂房;窗口类WNDCLASS比喻成为图纸(或生产汽车用到的部件的清单);窗口Window比喻成为具体的汽车(如奥迪A6)。则整个生产汽车的过程(建立窗体的过程)如下图:

需要注意的难点是:如果注册多个窗口类并且每个窗口类都对应一个自己的窗口函数,则相当于注册了多个窗口函数(全局的函数)到操作系统,那么while消息循环中DispatchMessage(&msg); 这一句话会根据hWnd参数即窗口句柄把对应的msg发送都对应的窗口函数(至于怎么对应的,应该是hWnd找到那个窗口对象,而每个窗口对象又都是根据某个注册的窗口类来创建的,那么每个窗口对象里面应该保存有对应窗口类的名称,也就是上面图中清单的名称,所以更具hWnd就能找到那个窗口类即清单,找到了清单自然就能找到对应的窗口函数了),所以hWnd参数是很重要的。关于这个问题我已经测试过了。

总之:可以简单的认为hWnd参数和某个窗口函数对应或关联,hWnd确定了,则DispatchMessage函数就能够转向某个窗口函数了。

 

下面解释新建一个空的Win32应用程序书写代码的步骤:

首先,exe应用程序由操作系统调用进入内存,再给应用程序分配好内存后,操作系统给这个应用程序一个HINSTANCE句柄,然后操作系统调用应用程序的winmain函数开始执行。

winmain函数声明如下:

#include <windows.h>    //windows用到的相关头文件,编写win32程序必须包含这个头文件

#include<stdio.h>  //C语言的库函数所在的头文件,可能用到C语言的库函数,所以要包含进去

int WINAPI WinMain(HINSTANCE hInstance,      
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)

这个winmain函数是由操作系统调用的,所以里面的参数也都是由操作系统赋值的。其中hInstance指标志这个应用程序的句柄;hPrevInstance标志应用程序前一个应用程序的句柄,在win32下一般规定就是0;lpCmdLine是用户执行应用程序时传的命令行参数,比如dos下执行1.txt,则这个1.txt就是命令行参数,根据这个参数打开或新建对应的文件;nCmdShow指程序运行的主窗口显示的状态,是最大化、最小化还是隐藏显示。

 

第二,注册窗口类和创建具体的窗口

如果把产生一个窗体的过程比喻成生产一辆汽车,则生产这辆汽车需要地盘、轮子等部件,还可能需要汽车工程师等人员来装配。而窗口类WNDCLASS就是产生这辆汽车需要所有物件的清单,它描述了需要哪些部件才能生产出这辆汽车(也就是产生这个窗口)。在这个清单中,最重要的部件就是窗口函数的指针和应用程序实例句柄。

(1)窗口类的定制和注册,就是把清单中需要的具体部件都拿过来(或都产生出来,包括图标、光标、画刷等资源,这些部件可以被共享的,节约了内存)。

下面是把注册窗口类的所有过程封装在了一个函数MyRegisterClass中:

void MyRegisterClass(HINSTANCE hInstance) {  

     WNDCLASS wc;  //定义一个窗口类的实例,即一个空清单

     wc.style   = CS_HREDRAW | CS_VREDRAW;  //窗口类的样式,都是全局的宏定义,定义在WinUser.h中,这个样式是指较全局话的样式,比如是豪华车型还是普通车型,而不是指具体的车型如奥迪A6

     wc.lpfnWndProc = WndProc; //窗口函数的首地址记录下来

     wc.cbClsExtra  = 0;  //给这个窗口类的实例分配多大的扩展或保留内存,一般为0

     wc.cbWndExtra  = 0;  //给以后生成的每个具体窗口的实例分配多大的扩展或保留内存,一般为0

     wc.hInstance  = hInstance;  //本窗口对应的应用程序的句柄是多少

     wc.hIcon   = LoadIcon(NULL/*或hInstance*/, IDI_APPLICATION);  //图标资源,其中hInstance是winmain中传来的句柄,为null时表示使用默认的标志图标,IDI_APPLICATION是全局的宏定义

     wc.hCursor  = LoadCursor(NULL, IDC_ARROW);  //同上

     wc.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH);  //背景画刷,DKGRAY_BRUSH是全局宏定义

     wc.lpszMenuName = NULL;  //菜单名,NULL表示不用菜单

     wc.lpszClassName = "某个窗口类"; //这个窗口类实例的名字,标记这个窗口类的实例  

     RegisterClass(&wc); //窗口类实例的注册

}

窗口类实例的注册是在一个专门的内存区域进行备案,以后随时根据窗口类实例的名字挑选出来使用,用如下图可以便于理解:

(2)窗体的创建

HWND CreateWindow(

  LPCTSTR lpClassName,  //安装清单的名称,即窗口类实例的名称

  LPCTSTR lpWindowName,  //窗口实例的标题

  DWORD dwStyle,  //窗口实例的样式,具体的样式,具体的车型,比如奥迪A6

  int x,  //窗口左上角坐标值x

  int y,  //窗口左上角坐标值y

  int nWidth,  //窗口的宽度

  int nHeight,  //窗口的高度

  HWND hWndParent,  //父窗口的句柄

  HMENU hMenu,  //主菜单的句柄

  HANDLE hInstance,  //应用程序实例句柄,和窗口类实例一样都有

  LPVOID lpParam //WM_Create消息用到的附加参数,一般只在mfc中用

);

(3)窗口的显示

bool ShowWindow(

    HWND hwnd, //窗口实例的句柄

    int nCmdShow  //窗口的显示方式,最大化等

);

bool UpdateWindow(

    HWND hWnd  //窗口实例的句柄

);

 

第三、消息循环

消息循环是属于整个应用程序的(所以放在main函数中),因为消息循环要从消息队列中取消息,而消息队列是属于整个应用程序的。

消息结构的定义如下:

typedef struct tagMSG{

  HWND hwnd;  //产生消息的那个窗口实例的句柄

  UINT message;  //消息的类型(即事件的类型)

  WPARAM wParam;  //消息的附加信息1,比如存放WM_CHAR消息时的对应按键的ASCII码

  LPARAM lParam;  //消息的附加信息2,具体功能还不知道

  DWORD time;  //消息被投递到应用程序队列的时间

  POINT pt;  //当消息被投递时,光标在屏幕上的位置

}MSG;

消息循环的代码如下,在main函数中:

while(GetMessage(&,NULL,NULL,Null)){ //GetMessage函数只有收到WM_QUIT消息时才返回false

  TranslateMessage(&msg);  //对消息进行转换,如解释键盘消息,即当键盘按下时是WM_KeyDown消息,这个函数转换产生一个WM_CHAR消息并放入到应用程序消息队列中

  DispatchMessage(&msg);  //把消息路由给(发送给)操作系统,最后由操作系统负责调用具体的消息处理函数(窗口函数)

  //我猜测分发消息时,是根据msg中的产生消息的那个窗口实例的句柄找到对应的窗口类的实例(清单),再根据这个窗口类实例(清单)找到具体的窗口函数

}

下面讲讲GetMessage函数

bool GetMessage(

  LPMSG lpMsg,  //装具体的消息

  HWND hWnd,  //窗口句柄,指示我要获取那个窗口的消息并装入到第一个参数lpMsg中,为NULL表示我要获取所有窗口的消息

  UINT wMsgFilterMin,  //消息范围的最小值,表示我要获取比这个值大的消息然后装入lpMsg中

  Uint wMsgFilterMax  //消息范围的最大值,如果最小和最大范围都设为0则表示不设置范围,获取所有范围的消息。

);

 第四、窗口过程(函数)

LRESULT CALLBACK WndProc_XXX(  //LRESULT是个LONG型,CALLBACK是_stdcall调用约定。 下面的四个参数其实就是消息结构体MSG的前四个参数(去掉了时间和光标当前位置)

  HWND hwnd,   //窗口句柄,由操作系统赋值

  UINT uMsg,  //消息类型,由操作系统赋值

  WPARAM wParam,  //消息附加参数,由操作系统赋值

  LPARAM lParam  //同上

)

{

  HDC hdc;

  PAINTSTRUCT ps;

  switch (uMsg)

  {

    case WM_PAINT:  //窗体重绘消息。如果在这里设置一个断点,则由于窗口从无到有都执行到这里,所以窗口就永无法显示出来

      hdc=BeginPaint(hwnd,&ps);  //所有的DC都是和窗口相关的

      TextOut(hdc,0,0,"初始显示的文字",strlen("初始显示的文字"));

      EndPaint(hwnd,&ps); //和BeginPaint是一对。另外BeginPaint函数只能用在WM_PAINT窗口重绘消息中,不能用在其他消息中

      break;

    case WM_CHAR:

      char szChar[20];

      spintf(szChar,"char is %d",wParam);  //格式化字符串到内存szChar中

      MessageBox(hwnd,szChar,"标题",MB_OK);

      break;

    case WM_LBUTTONDOWN:

      MessageBox(hwnd,"您按下了鼠标左键","标题",MB_OK);

      HDC hDC;  //看作是一个和具体窗口关联了的画布

      hDC=GetDC(hwnd);

      TextOut(hDC,0,50,"画出了一些文字",strlen("画出了一些文字"));  //在画布hDC上画文字

      ReleaseDC(hwnd,hDC); //和GetDC是一对。另外GetDC函数只能用在其他消息中,不能用在WM_PAINT窗口重绘消息中

      break;   

    case WM_CLOSE:

      if(IDYES==MessageBox(hwnd,"是否退出程序?","标题",MB_YESNO));

      {

        DestroyWindow(hwnd); //这个函数会销毁窗口并发送一个WM_DESTROY和WM_NCDESTROY消息,从而转到下面的case语句

      }

      break;

    case WM_DESTROY:

      PostQuitMessage(0);  //向消息队列中投递一个WM_QUIT消息,从而是while循环退出。参数0是作为WM_QUIT消息的附加参数,意义不大

      break;

    default:

      return DefWindowProc(hwnd,uMsg,wParam,lParam);  //对其他的喜爱系进行缺省处理,不可缺少,否则窗口可能不显示出来。

   }

  return 0;

}