开始之前
这是一本经典老书了,经过这么多年了,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的意义,因消息之不同而异
本章未完,待续......