《深入浅出MFC》第一章Win32基本程序观念-笔记(一)

开始之前

这是一本经典老书了,经过这么多年了,VC++/MFC的开发方式还差不多是原来那个样子,本书讲的内容在是处在Windows9x的时代,但仍然适用当今Windows 10的开发,可见万变不离其宗啊。以前(N 年前吧)以所谓的“快速阅读”的方式读过此书,这样读书现在觉得有点不靠谱。所以还是以我自己的方式读吧。

本书论述的主题是:Application Framework, Object Oriented, Runtime Type Information, Dynamic Creation, Persistence, Message Mapping, Command Routing。看到这些熟悉又神秘的名称,相信每个VC++程序员都有亲切感,哈哈。

作者认为“这些核心技术与彻底操控MFC乃同一件事”,如果精读此书再加上实践,你就是Windows/VC++/MFC内功深厚之人,对于MFC,许多人只知道怎么用,不知道为什么,本书详细解释MFC的来龙去脉,那些神秘的MFC宏,常数定义,奥妙无比的MFC黑箱操作,一旦你明白了,就如同武侠世界里的高人,摘花飞叶皆可伤人于无形

欲读此书,必须有以下四大基础:

1. 面向对象观念和C++语言

2. Windows程序基本观念(程序进入点,消息传递,窗口函数,回调)

3. Microsoft Foundation Classes (MFC)本身

4. Visual C++集成开发环境和各种开发工具

所幸,本书前面几章已经提供了必要的基础,你无需精通上述四大基础,如果已经精通了还看此书干嘛呢,哈哈

书中专业术语翻译对照,只列出不明确的,其它忽略

运算子-operator//明明叫做运算符好不好?

改写-override//我看到过的书都叫覆盖,C++子类改写父类的虚函数代码,就叫override

重载-overload//比较一下override,override纵向改写,而overload是横向改写

还有C++三大特性,复习一下这三个单词:

封装-Encapsulation

继承-Inheritance

多态-Polymorphism


第一章 Win32基本程序概念

* 这是Windows程序设计者一定要知道的基础

* Windows程序的运行本质Message Based, Event Driven从不曾改变,包括消息的产生,获取,分派,处理

Windows程序开发流程:

Windows程序包含“程序代码”和“UI(User Interface用户接口)资源”两大部分,两部分最后以RC编译器整合为一个完整的EXE文件,所谓UI资源是功能菜单、对话框外貌、程序图标、光标形状等等。这些UI资源的实际内容(二进制代码)是借助各种工具产生的,并以各种扩展名的文件存在,如.ico, .bmp, .cur等等。程序员必须在一个所谓的资源描述文档(.rc)中描述它们,RC编译器(RC.exe)读取RC文件的描述后将所有UI资源文件集中作出一个.RES文件,再与程序代码结合在一起,这才是一个完整的Windows可执行文件。


需要什么函数库(.lib)

* 并不只是.dll才是动态链接库(dynamic link library),事实上.exe、.dll、.fon、.mod、.drv、.ocx都是动态链接库

* Windows程序调用的程序可以分为C Runtimes ,以及Windows API两大部分

* 动态链接库是在执行时期才发生“链接”,但在生成.exe文件时期,链接器(Linker)仍然需要先为调用者准备一些适当信息,才能在执行时期顺利“跳”到DLL中执行,这些信息放在“import”函数库中,如GDI32.lib, User32.lib, Kernel32.lib, Comdlg32.lib, TH32.lib

需要什么头文件(.H)

* 所有Windows程序都必须载入windows.h文件,如果还用到其它的系统dll,就得载入对应的头文件,如Commdlg.h, Mapi.h, Tapi.h等

以消息为基础,以事件驱动之(Message based, event drive)

* Windows程序的进行系依靠外部发生的事件来驱动,即程序不断等待(用一个While循环)等待任何可能的输入,然后做判断,然后再做适当处理。“输入”是由操作系统捕捉到之后,以消息形式(一种数据结构)进入程序之中

* “输入”的分类:硬件所产生的消息(鼠标移动、键盘按下)放在系统队列(system queue)中,以及由Windows系统或其它Windows程序传送过来的消息,放在程序队列(application queue)中,以程序的角度看消息来自哪里并没有太大区别,反正程序调用一个GetMessage() API就取得一个消息,程序的生命靠它来推动,所有的GUI系统,包括UNIX的X Windows以及OS/2的Presentation Manger都像这样,是以消息为基础的事件驱动系统。可想而知,每个Windows程序都应该有一个如下的循环:

MSG msg

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

TranslateMessage(&msg);

DispatchMessage(&msg);

}

消息,MSG结构,其实是Windows内设的一种数据格式

/*Queued message structure*/

typedef struct tagMSG

{

    HWND    hwnd;

    UINT        message; //WM_XXX,,例如WM_MOUSEMOVE,WM_SIZE......

    WPARAM    wParam;

    LPARAM    lParam;

    DWORD    time;

    POINT    pt;

}

接受并处理消息的主角就是窗口。每一个窗口都应该有一个函数负责处理消息,程序员必须负责设计这个所谓的“窗口函数”(windows procedure或称windows function),如果窗口获得一个消息,则这个窗口函数必须判断消息队列的类别,决定处理方式。

一个具体而微的Windows程序

* makefile的语法:如下例,冒号左边的文件(generic.res)与右边的文件(generic.rc和generic.h)比较,只要右边任一文件比左边的文件更新,就执行下一行所指定的操作(rc generic.rc),这种操作可以是任何命令行。makefile可以用NMAKE.EXE(Microsoft工具),或其它编译器套件的同等工具

generic.res : generic.rc generic.h

    rc generic.rc


Generic.mak源代码

# filename : generic.mak

# make file for generic.exe (Generic Windows Application)

# usage : nmake generic.mak (Microsoft C/C++ 9.00) (Visual C++ 2.0)

# usage : nmake generic.mak (Microsoft C/C++ 10.00) (Visual C++ 4.0)

all: generic.exe

generic.res : generic.rc generic.h

    rc generic.rc

generic.obj : generic.c generic.h

    cl -c -W3 -Gz -D_X86_ -DWIN32 generic.c

generic.exe : generic.obj generic.res

    link /MACHINE:I386 -subsystem:windows generic.res generic.obj \

         libc.lib kernel32.lib user32.lib gdi32.lib


Generic.h源代码

BOOL InitApplication(HANDLE);

BOOL InitInstance(HANDLE, int);

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

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


Generic.c源代码

#include <windows.h>   //每一个Windows程序都需要载入此头文件

#include "resource.h"  //内含各个资源ID

#include "generic.h"   //本程序的头文件

HINSTANCE _hInst;      // Instance handle

HWND      _hWnd;

char _szAppName[] = "Generic";    //程序名称

char _szTitle[]   = "Generic Sample Application"; //窗口名称

//---------------------------------------------------------------------

// WinMain - 程序进入点

//---------------------------------------------------------------------

int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

                     LPSTR lpCmdLine,     int nCmdShow)

{

MSG msg;

  UNREFERENCED_PARAMETER(lpCmdLine);      // 避免编译时的警告

  if (!hPrevInstance)

      if (!InitApplication(hInstance))

          return (FALSE);

  if (!InitInstance(hInstance, nCmdShow))

      return (FALSE);

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

      TranslateMessage(&msg);

      DispatchMessage(&msg);

  }

  return (msg.wParam); // 传回PostQuitMessage的参数

}

//---------------------------------------------------------------------

// InitApplication - 注册窗口类

//---------------------------------------------------------------------

BOOL InitApplication(HINSTANCE hInstance)

{

WNDCLASS  wc;

  wc.style         = CS_HREDRAW | CS_VREDRAW;

  wc.lpfnWndProc   = (WNDPROC)WndProc;     // 窗口函数

  wc.cbClsExtra    = 0;

  wc.cbWndExtra    = 0;

  wc.hInstance     = hInstance;

  wc.hIcon         = LoadIcon(hInstance, "jjhouricon");

  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);

  wc.hbrBackground = GetStockObject(WHITE_BRUSH);  // 窗口背景色

  wc.lpszMenuName  = "GenericMenu";                // .RC 所定义的窗体

  wc.lpszClassName = _szAppName;

  return (RegisterClass(&wc));

}

//---------------------------------------------------------------------

// InitInstance - 产生窗口

//---------------------------------------------------------------------

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)

{

  _hInst = hInstance; // 储存为全局变量,方便使用

  _hWnd = CreateWindow(

                       _szAppName,

                       _szTitle,

                       WS_OVERLAPPEDWINDOW,

                       CW_USEDEFAULT,

                       CW_USEDEFAULT,

                       CW_USEDEFAULT,

                       CW_USEDEFAULT,

                       NULL,

                       NULL,

                       hInstance,

                       NULL

                      );

  if (!_hWnd)

      return (FALSE);

  ShowWindow(_hWnd, nCmdShow); // 显示窗口

  UpdateWindow(_hWnd);         // 送出 WM_PAINT消息

  return (TRUE);

}

//---------------------------------------------------------------------

// WndProc - 窗口函数

//---------------------------------------------------------------------

LRESULT CALLBACK WndProc(HWND hWnd,     UINT message,

                         WPARAM wParam, LPARAM lParam)

{

int wmId, wmEvent;

  switch (message) {

    case WM_COMMAND:

         wmId    = LOWORD(wParam);

         wmEvent = HIWORD(wParam);

         switch (wmId) {

           case IDM_ABOUT:

                DialogBox(_hInst,

                          "AboutBox",     // 对话框资源名称

                          hWnd,           // 父窗口

                          (DLGPROC)About  // 对话框函数名称

                         );

                break;

           case IDM_EXIT:

                //使用者想结束程序。处理方式与WM_CLOSE相同

                DestroyWindow (hWnd);

                break;

           default:

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

         }

         break;

    case WM_DESTROY:  // 窗口已经被摧毁(程序即将结束)

         PostQuitMessage(0);

         break;

    default:

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

  }

  return (0);

}

//---------------------------------------------------------------------

// About - 对话框函数

//---------------------------------------------------------------------

LRESULT CALLBACK About(HWND hDlg,     UINT message,

                       WPARAM wParam, LPARAM lParam)

{

  UNREFERENCED_PARAMETER(lParam);      //避免编译时的警告

  switch (message) {

    case WM_INITDIALOG:

         return (TRUE);      // TRUE 表示我已处理过这个消息

    case WM_COMMAND:

         if (LOWORD(wParam) == IDOK

             || LOWORD(wParam) == IDCANCEL) {

             EndDialog(hDlg, TRUE);

             return (TRUE);  // TRUE 表示我已处理过这个消息

         }

         break;

  }

  return (FALSE); // FALSE 表示我没有处理这个消息

}

//------------------------ end of file ------------------------------

Generic.rc源代码

#include "windows.h"

#include "resource.h"

jjhouricon ICON    DISCARDABLE     "jjhour.ico"

GenericMenu MENU DISCARDABLE

BEGIN

    POPUP "&File"

    BEGIN

        MENUITEM "&New",              IDM_NEW, GRAYED

        MENUITEM "&Open...",          IDM_OPEN, GRAYED

        MENUITEM "&Save",             IDM_SAVE, GRAYED

        MENUITEM "Save &As...",       IDM_SAVEAS, GRAYED

        MENUITEM SEPARATOR

        MENUITEM "&Print...",         IDM_PRINT, GRAYED

        MENUITEM "P&rint Setup...",   IDM_PRINTSETUP, GRAYED

        MENUITEM SEPARATOR

        MENUITEM "E&xit",             IDM_EXIT

    END

    POPUP "&Edit"

    BEGIN

        MENUITEM "&Undo\tCtrl+Z",     IDM_UNDO, GRAYED

        MENUITEM SEPARATOR

        MENUITEM "Cu&t\tCtrl+X",      IDM_CUT, GRAYED

        MENUITEM "&Copy\tCtrl+C",     IDM_COPY, GRAYED

        MENUITEM "&Paste\tCtrl+V",    IDM_PASTE, GRAYED

        MENUITEM "Paste &Link",       IDM_LINK, GRAYED

        MENUITEM SEPARATOR

        MENUITEM "Lin&ks...",         IDM_LINKS, GRAYED

    END

    POPUP "&Help"

    BEGIN

        MENUITEM "&Contents",               IDM_HELPCONTENTS, GRAYED

        MENUITEM "&Search for Help On...",  IDM_HELPSEARCH, GRAYED

        MENUITEM "&How to Use Help",        IDM_HELPHELP, GRAYED

        MENUITEM SEPARATOR

        MENUITEM "&About Generic...",       IDM_ABOUT

    END

END

AboutBox DIALOG DISCARDABLE  22, 17, 144, 75

STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU

CAPTION "About Generic"

BEGIN

    CTEXT           "Windows 95",         -1,0, 5,144,8

    CTEXT           "Generic Application",-1,0,14,144,8

    CTEXT           "Version 1.0",        -1,0,34,144,8

    DEFPUSHBUTTON   "OK",              IDOK,53,59,32,14,WS_GROUP

END


程序进入点WinMain

* main是一般C程序的进入点

int main(int argc, char *argv[], char *envp[])

* WinMain则是Windows程序的进入点

int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hprevInstance, LPSTR lpCmdLine, int nCmdShow)

* 当Windows的shell侦测到用户要执行一个程序(如双击),于是用加载器把此程序加载,然后调用C startup code,C startup code再调用WinMain,开始执行程序,WinMain的四个参数由操作系统传递进来


窗口类之注册与窗口之诞生

* 窗口的注册和产生由两个API完成,但是其属性必须先设定好,包括窗口的“外貌”和“行为”,如边框、颜色、标题、位置是窗口的外貌,而窗口接收消息后的反应就是其行为,具体地说就是指窗口函数本身。API RegisterClass需要一个大型数据结构WNDCLASS作为参数,API CreateWindow则另需要11个参数

* 上述两个函数的名称设计有特别含义,注册窗口放在InitApplication()中表示只有第一个实例才会进入,至于这一进程是否是某个程序的第一个实例,可以由WinMain的参数hPrevInstance判断(此为旧参数,目前已抛弃,留着是为兼容,以后传入的值永远为0);产生窗口放在InitInstance()在表示任何实例都会进入,之所以特别提到自定义的两个函数InitApplication()和InitInstance()是因为MFC中有,后续章节会涉及到。

消息循环

/*GetMessage()获取消息,收到消息才返回true,否则一直等待,当收到的消息是WM_QUIT时返回false,结束循环*/

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

      TranslateMessage(&msg);//转换键盘消息

      DispatchMessage(&msg);//分派消息

  }

* 其中TranslateMessage()是为了将键盘消息转换,DispatchMessage()会将消息传给窗口函数去处理。没有指定函数名称却可以将消息传送过去岂不是玄?这是因为消息发生之时,操作系统已经根据当时状态,为它标明了所属窗口,而窗口所属的窗口类又已经明白标示了窗口函数(也就是wc.lpfnWndProc所指的函数),所以DispatchMessage()自有脉络可寻,DispatchMessage()经过USER模块的协助,才把消息交到窗口函数手中。

窗口的生命中枢:窗口函数

* 消息循环中的DispatchMessage()通过USER模块的协助把消息送到窗口函数,窗口函数通过swithch/case方式判断消息种类,以决定处置方式,窗口函数是被Windows系统调用的,此为回调函数(call back),意思是:代码在你的程序中,被Windows系统调用,这些函数虽然由你设计,但是永远不会也不该被你调用,它是为Windows系统准备的,所以窗口函数的接口必然是一致的:

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

注意的是不论什么消息都必须被处理,所以switch/case指令中的default:处必须调用DefWindowProc(),这是Windows内部默认的消息处理函数。之所以设计成call back函数是因为很多情况除了你要处理消息,系统也必须处理,设计成call back系统就有机会处理了。

* 窗口函数中的参数wParam和lParam的意义,因消息之不同而异


本章未完,待续......

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。