zoukankan      html  css  js  c++  java
  • MFC视频教程(孙鑫)学习笔记1-Windows程序内部运行原理

      转眼间学习计算机编程已经三年多了。以前计算机编程只是我的业余爱好,学的东西挺多,忘得也很快。以前学过VB,C#,Lisp,VBA(这几种语言都与AutoCAD二次开发相关),ASP,PHP,ASP.NET,ActionScript,Python,Java,JavaScript,汇编,C,C++。学习的都很肤浅,没什么积累。感觉这是最大的遗憾。今后想以计算机编程谋生了,所以要学习积累。”好记性不如烂笔头“这句真理不能违背啊!昨天晚上还一阵恼火,学过的CentOS命令几乎都忘光了,安装个LAMP环境,遇到各种错误。不说废话了,从今天起,我要将学过的孙鑫的MFC视频教程认真记笔记。这视频有点难度,第一遍看的时候,第三集没看完就看不下去了,不懂呀。只能硬着头皮反复看了。


    (一)

    Windows系统是消息机制为基础,靠事件驱动的。换句话说,程序不断等待(利用while循环),等待任何可能的输入,然后作出判断,然后做出适当的处理。上述的“输入”是由操作系统捕捉到的,以消息形式(一种数据结构)进入到程序之中,操作系统拥有自己的一套检测循环,掌管着各个外围驱动。

    上面1和2代表Windows操作系统控制输入输出设备。1代表操作系统能控制设备,比如声卡,输出音频;2代表操作系统能感知设备的输入,比如鼠标的点击;

    向下的箭头③表示应用程序可以通知操作系统执行某个具体的动作,如操作系统能够控制声卡发出声音,但它并不知道应该何时发出何种声音,需要应用程序告诉操作系统该发出什么样的声音。这个关系好比有个机器人能够完成行走的功能,但是,如果人们不告诉它往哪个方向上走,机器人是不会主动行走的。这里的机器人就是操作系统,人们就是应用程序。 一句话,Windows应用程序能通过操作系统间接控制输入输出设备。

    向上的箭头④表示操作系统能够将输入设备的变化上传给应用程序。如用户在某个程序活动时按了一下键盘,操作系统马上能够感知到这一事件,并且能够知道用户按下的是哪一个键,操作系统并不决定对这一事件如何作出反应,而是将这一事件转交给应用程序,由应用程序决定如何对这一事件作出反应。好比有个蚊子叮了我们一口,我们的神经末梢(相当于操作系统)马上感知到这一事件,并传递给了我们的大脑(相当于应用程序),我们的大脑最终决定如何对这一事件作出反应,如将蚊子赶走,或是将蚊子拍死。对事件作出反应的过程就是消息响应。

    (二)

    操作系统是通过消息机制(Message将感知到的事件传递给应用程序。操作系统将每个事件都包装成一个称为消息的结构体MSG来传递给应用程序。MSG结构定义如下:

    typedef struct MSG {       
          HWND   hwnd; //窗口句柄,即标示消息所属的窗口     
          UINT   message;//标示消息的类别,是鼠标还是键盘等 如鼠标左键按下消息是WM_LBUTTONDOWN,键盘按下消息是WM_KEYDOWN,字符消息是WM_CHAR
          WPARAM wParam;//消息的附加信息
          LPARAM lParam;//消息的附加信息
          DWORD  time;//消息投递到消息队列中的时间
          POINT  pt;//鼠标的当前位置
       } MSG;

    这里涉及到几个概念:

    1. 窗口(Windows)和句柄(HANDLE,handle):

    句柄是资源的标识,我们程序的窗体,图标,光标等资源在程序运行时,是在内存中的,Windows就是通过句柄使用内存中的这些资源的。句柄分为:窗口句柄(HWND)图标句柄(HICON)、光标句柄(HCURSOR)和画刷句柄(HBRUSH)应用程序实例句柄(HINSTANCE)等等各种类型的句柄。

    2. 消息,消息队列,消息循环,消息响应

    消息:是操作系统与应用程序的通信。

    消息队列:每一个Windows应用程序开始执行后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该程序创建的窗口的消息。

    消息循环:Windows通过一个循环过程处理应用程序的消息,

    while(GetMessage(&msg,NULL,0,0)){//接收到WM_QUIT消息时,才返回0
         TranslateMessage(&msg);//对消息进行包装处理然后再以消息的形式投放到消息队列
         DispatchMessage(&msg);//消息回传给操作系统,由操作系统调用窗口过程函数对消息进行处理
     }

    消息响应:

    (1)操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中。

    (2)应用程序在消息循环中调用GetMessage函数从消息队列中取出一条条的消息。取出后,以对消息进行一些预处理,如放弃对某些消息的响应,或者调用TranslateMessage产生新的消息

    (3)应用程序调用DispatchMessage,将消息回传给操作系统。消息是由MSG结构体对象来表示的,其中就包含了接收消息的窗口的句柄。因此,DispatchMessage函数总能进行正确的传递。

    (4)系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理(即“系统给应用程序发送了消息”)。

    3. 窗口过程函数,这是应用程序响应消息的函数,比如单击鼠标左键,在这可以弹出对话框。
       lresult callback windowproc(
            hwnd hwnd,          // 对应消息的窗口句柄
            uint umsg,           // 消息代码,区别消息的类型
            wparam wparam,      // 消息代码的附加参数1
            lparam lparam       // 消息代码的附加参数2
           )


    (三)Windows程序的入口函数:

    Windows程序的入口函数
    int WINAPI WinMain(
      HINSTANCE hInstance,      //  该应用程序的当前实例句柄
      HINSTANCE hPrevInstance,  //  该应用程序的上一个实例句柄,
      LPSTR lpCmdLine,          // command line
      int nCmdShow              // show state,
    );

    初次接触WinMain时,我们经常看到陌生的类型,如HINSTANCE,LPSR,其实这些都是一些宏,在vs中我们可以在其上右击,选择定义就能看到其真实的类型。这些宏都是为了便于区分和记忆。


    (四)Windows应用程序的窗口创建过程:

    1). 设计一个窗口类;
    2). 注册窗口类;
    3). 创建窗口;
    4). 显示及更新窗口.

    关于WNDCLASS,我引用了另一篇精华帖:http://blog.csdn.net/jackalfly/article/details/8239188

    窗口结构体定义:

    typedef struct _WNDCLASS {
      UINT style;
      WNDPROC lpfnWndProc;
      int cbClsExtra;
      int cbWndExtra;
      HINSTANCE hInstance;
      HICON hIcon;
      HCURSOR hCursor;
      HBRUSH hbrBackground;
      LPCTSTR lpszMenuName;
      LPCTSTR lpszClassName;
      } WNDCLASS, *PWNDCLASS;

    WNDCLASS是一个由系统支持的结构,用来储存某一类窗口的信息,如ClassStyle,消息处理函数,Icon,Cursor,背景Brush等。也就是说,CreateWindow只是将某个WNDCLASS定义的窗体变成实例。要得到某一窗口的WNDCLASS数据,可以用GetClassLong();

    RegisterClass()就是在系统注册某一类型的窗体。也就是将你提供的WNDCLASS数据注册为一个窗口类,在WNDCLASS.lpszClassName中定义该WNDCLASS的标识,无论CreateWindow或CreateWindowEx创建的窗口都必须对应一个WNDCLASS,但一个WNDCLASS可以有多个窗口对象。

    有一些系统预定义的窗口类,如:ClassName=_T("BUTTON" or "COMBOBOX" or "EDIT" or "LISTBOX" or "MDICLIENT" or "SCOLLBAR" or "STATIC"),要用这些窗体,直接用CreateWindow创建相应对象就是了。要得到某一窗口的窗口类,可以用GetClassName();
      WNDCLASS中的回调函数是窗体的消息处理函数:CALLBACK WinProc(MESSAGE msg,LPARAM lparam,WPARAM wParam);

    例子:

    long CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);//声明

    //WinMain函数是所有Windows应用程序的入口,类似c语言中的main函数其功能是完成//一系列的定义和初始化,并产生消息循环。函数说明:

    int WINAPI WinMain(HINSTANCE hInstance,       // handle to current instance

                                HINSTANCE hPrevInstance, // handle to previous instance

                                LPSTR lpCmdLine,              // command line

                                  int nCmdShow                     // show state

    )

    {

           //初始化,初始化包括窗口类的定义、注册、创建窗口实例和显示窗口四部分

      HWND hwnd;

      MSG Msg;

      WNDCLASS wndclass;

      char lpszClassName[]="窗口"; //窗口类名

      char lpszTitle[]="测试窗口";  //窗口标题名

      //窗口类定义,窗口类定义了窗口的形式与功能,窗口类定义通过给窗口类数据结构WNDCLASS赋值完成

      //该数据结构中包含窗口类的各种属性

      wndclass.style =0; // 窗口类型为缺省类型CS_     Class Style

      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.lpszMenuName=NULL; //窗口无菜单

      wndclass.lpszClassName=lpszClassName; //窗口类名为“窗口”

      //以下是窗口类的注册-----------Windows系统本身提供部分预定义的窗口类,程序员也可以自定义窗口类,窗口类必须先注册后使用。

      if(!RegisterClass(&wndclass)) //如果注册失败 发出警告

           {MessageBeep(0); return FALSE;}

      //创建窗口创建一个窗口的实例由函数CreateWindow()实现

      hwnd=CreateWindow( lpszClassName, //窗口类名,创建窗口时一定要基于我们已经注册过的窗口类名,即"窗口"。

                                         lpszTitle, //窗口标题名

                                         WS_OVERLAPPEDWINDOW, //窗口的风格 WS_ Windows Style

                                         CW_USEDEFAULT, //窗口左上角坐标值为缺省值 CW_  Create Wndow

                                         CW_USEDEFAULT,

                                         CW_USEDEFAULT, //窗口的高和宽为缺省值

                                              CW_USEDEFAULT,

                                         NULL, //此窗口无父窗口

                                         NULL, //此窗口无子菜单

                                         hInstance, //创建此窗口的应用程序的当前句柄

                                         NULL //不使用该值

      );

      //显示窗口

      ShowWindow(hwnd,nCmdShow);

      //绘制用户区

      UpdateWindow(hwnd);

      //消息循环

      while(GetMessage(&Msg,NULL,0,0))  //GetMessage()函数是从调用线程的消息队列中取出一条消息;对于每一个应用程序窗口线程,操作系统都会为其建立一个消息队列,当我们的窗口有消息时(即所有与这个窗口线程相关的消息),操纵系统会把这个消息放到该线程的消息队列当中,我们的窗口程序就通过这个GetMessage()函数从自己的消息队列中取出一条一条具体的消息并进行响应操作。

      {

      TranslateMessage(&Msg);//对"消息对"的转化,如对键盘的WM_KEYDOWN和WM_KEYUP消息对转化为WM_CHAR消息,并且将转换后的新消息投递到我们的消息队列中去,这个转化操作不会影响原来的消息,只会产生一个新的消息。

      DispatchMessage(&Msg);//DispatchMessage()函数是将我们取出的消息传到窗口的回调函数去处理;可以理解为该函数将取出的消息路由给操作系统,然后操作系统去调用我们的窗口回调函数对这个消息进行处理。

            }

      return Msg.wParam; //消息循环结束 即程序结束时 将信息返回系统

     }

     

    //窗口函数,窗口函数定义了应用程序对接收到的不同消息的响应,其中包含了应用程序对各种可能接受到的消息的处理过程,时消息处理分支控制语句的集合

    long CALLBACK WndProc(HWND hwnd,

                                        UINT message,

                                          WPARAM wParam,

                                            LPARAM lParam)

    {

      switch(message)

      {

        case WM_DESTROY:

          PostQuitMessage(0);

        default: //缺省时采用系统消息缺省处理函数

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

      }

      return (0);

    }

    注:窗口回调函数的函数指针定义typedef LRESULT CALLBACK  (* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
    WNDPROC OldWndProc;
    LRESULT CALLBACK NewWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
           switch (Msg)
           {
                ......
           }
           return CallWindowProc(OldWndProc,g_Wnd,Msg,wParam,lParam);
    }
    OldWndProc = (WNDPROC)GetWindowLong(g_Wnd,GWL_WNDPROC);
    SetWindowLong(hwnd, GWL_WNDPROC,(LPARAM)(WNDPROC)NewWndProc);
    通过调用SetWindowLong函数可以修改该窗体类的回调函数。
     
    CallBack 函数中的wParam和lParam有什么区别:
      WPARAM   wParam,     定义成WORD型
      LPARAM   lParam         定义成LONG型
      在Win 3.x中,WPARAM是16位的,而LPARAM是32位的,两者有明显的区别。
      在Win32 API中,WPARAM和LPARAM都是32位,所以没有什么本质的区别。
      但是习惯上,我们愿意使用LPARAM传递地址,而WPARAM传递其他参数。
      function MouseHookProc(nCode: Integer; wPar: WPARAM; lPar: LPARAM): lResult; stdcall;
      如果我要判断鼠标左键是否按下,用wParam==WM_LBUTTONDOWN判断.
      lParam 是 (tagMOUSEHOOKSTRUCT的指针)PMouseHookStruct类型,主要是获得发送窗口句柄,鼠标坐标 ,以及其他一些信息 。
      lParam 用的时候需要强制转换,转换成PMouseHookStruct类型. 


    数据结构原型

    typedef struct _WNDCLASS{

                     UINT style;

                          WNDPROC lpfnWndProc;

                        int cbClsExtra;

                        int cbWndExtra;

                          HANDLE hInstance;

                        HICON hIcon;

                          HCURSOR hCursor;

                          HBRUSH hbrBackground;

                          LPCTSTR lpszMenuName;

                        LPCTSTR lpszClassName;

    }WNDCLASS;

    结构说明

      WNDCLASS 结构包含了RegisterClass函数注册的类属性

    分量简介

    style: 指定类风格。这些风格可通过按位或操作组合起来。风格如下:

      CS_BYTEALIGNCLIENT: 在字节边界上(在x方向上)定位窗口的用户区域的位置

      CS_BYTEALIGNWINDOW: 在字节边界上(在x方向上)定位窗口的位置

      CS_CLASSDC: 该窗口类的所有窗口实例都共享一个窗口类DC

      CS_DBLCLKS: 允许向窗口发送双击鼠标键的消息

      CS_GLOBALCLASS: 当调用CreateWindow 或 CreateWindowEx 函数来创建窗口时允许它的hInstance参数和注册窗口类时传递给

      RegisterClass 的 hInstance参数不同。如果不指定该风格,则这两个 hInstance 必须相同。

      CS_HREDRAW: 当水平长度改变或移动窗口时,重画整个窗口

      CS_NOCLOSE: 禁止系统菜单的关闭选项

      CS_OWNDC: 给予每个窗口实例它本身的DC。注意,尽管这样是很方便,但它必须慎重使用,因为每个DC大约要占800个字节的内存。

      CS_PARENTDC: 将子窗口的裁剪区域设置到父窗口的DC中去,这样子窗口便可以在父窗口上绘制自身。注意,这是子窗口还是从系统缓存中获取DC,而不是使用父窗口的DC。使用该风格可以提高系统性能。

      CS_SAVEBITS: 以位图形式保存被该窗口遮挡的屏幕部分,这样当给窗口移动以后,系统便可以用该保存的位图恢复屏幕移动的相应部分,从而系统不用向被该窗口遮挡的窗口发送 WM_PAINT 消息。该特性对于菜单类型的窗口比较合适,因为它通常是简短的显示一下之后便消失。设置该特性将增加显示该窗口的时间,因为它通常要先分配保存位图的内存。

      CS_VREDRAW: 当垂直长度改变或移动窗口时,重画整个窗口

    lpfnWndProc: 指向窗口过程

    cbClsExtra: 指定紧随在 WNDCLASS 数据结构后分配的字节数。系统将其初始化为零。

    cbWndExtra: 指定紧随在窗口实例之后分配的字节数,系统将其初始化为零。如果应用程序正在用WNDCLASS结构注册一个在RC资源描述文件中用CLASS指令创建的对话框时,它必须设置这个字段为 DLGWINDOWEXTRA。

    hInstance: 标识了该窗口类的窗口过程所在的模块实例的句柄,不能为NULL。

    hIcon: 标识了该窗口类的图标。hIcon字段必须是一个图标的句柄;若hIcon字段为NULL,则无论何时用户把应用程序缩至最小时,应用程序必须画一个图标。

    hCursor: 标识该窗口类的光标,hCursor必须是一个光标资源的句柄。若hCursor字段为NULL,则无论何时鼠标移到应用程序窗口时,应用程序必须显式设置光标形状。

    hbrBackground: 标识了该窗口类的背景画笔。hbrBackground字段必须是用于绘制背景的物理刷子的句柄,或者是一个颜色的值。如果给出一个颜色的值,它必须是下面列出的标准系统颜色之一(系统将对所选颜色加1)。如果给出了颜色值,它必须是转换成下列的HBRUSH类型之一的颜色:

      COLOR_ACTIVEBORDER

      COLOR_ACTIVECAPTION

      COLOR_APPWORKSPACE

      COLOR_BACKGROUND

      COLOR_BTNFACE

      COLOR_BTHSHADOW

      COLOR_BTNTEXT

      COLOR_CAPTIONTEXT

      COLOR_GRAYTEXT

      COLOR_HIGHLIGHT

      COLOR_HIGHLIGHTTEXT

      COLOR_INACTIVEBORDER

      COLOR_INACTIVECAPTION

      COLOR_MENU

      COLOR_MENUTEXT

      COLOR_SCROLLBAR

      COLOR_WINDOW

      COLOR_WINDOWFRAME

      COLOR_WINDOWTEXT

      当hbrBackground字段为NULL时,每当需要绘制其用户区域时,应用程序必须自己来绘制其背景。应用程序可以通过处理WM_ERASEBKGND 消息或检查由 BeginPaint 函数填写的 PAINTSTRUCT 结构的fErase 字段来确定背景什么时候需要着色。

    lpszMenuName:指向NULL结束的字符串,该字符串描述菜单的资源名,如同在资源文件里显示的名字一样。若使用一个整数标识菜单,可以使用MAKEINTRESOURCE宏。如果lpszMenuName为NULL,

      那么该窗口类的窗口将没有默认菜单。

    lpszClassName:指向NULL结束的字符串,或者是一个原型(atom)。若该参数是一个原型,它必须是一个有先前调用RegisterClass或者 RegisterClassEx函数产生的类原型。类原型必须作为lpszClassName的低字,高字必须为0.若lpszClassName是一个字符串,它描述了窗口类名。这个类名可以是由RegisterClass或者RegisterClassEx注册的名字,或者是任何预定义的控件类名。

      结构信息Header 在winuser.h声明,包含windows.h


    向下的箭头③表示应用程序可以通知操作系统执行某个具体的动作,如操作系统能够控制声卡发出声音,但它并不知道应该何时发出何种声音,需要应用程序告诉操作系统该发出什么样的声音。这个关系好比有个机器人能够完成行走的功能,但是,如果人们不告诉它往哪个方向上走,机器人是不会主动行走的。这里的机器人就是操作系统,人们就是应用程序。 


  • 相关阅读:
    php字符串相加
    elementUI的input输入一个字符就失去焦点问题
    js鸡尾酒排序算法
    js快速排序算法
    js冒泡排序算法改进
    js实现队列
    EXIF.js 读取图片的方向
    new Image().src资源重复请求问题
    canvas绘制圆图输出图片格式
    去掉"You are running Vue in development mode"提示
  • 原文地址:https://www.cnblogs.com/AlexanderZhao/p/12879005.html
Copyright © 2011-2022 走看看