就我个人观点,C++/MFC 程序设计必须跨越四大技术障碍:
1. 对象导向观念与C++ 语言。
2. Windows 程序基本观念(程序进入点、消息流动、窗口函数、callback...)。
3. Microsoft Foundation Classes(MFC)本身。
4. Visual C++ 整合环境与各种开发工具(难度不高,但需熟练)
SDK - Software Development Kit,原指软件开发工具。每一套环境都可能有自己的SDK。
凡以Windows raw API 撰写的程序我们通常也称为SDK 程序。
MFC - Microsoft Foundation Classes 的缩写,这是一个架构在Windows API 之上的C++ 类别库(C++ Class Library),意图使Windows 程序设计过程更有效率,更符合物件导向的精神。
Windows 程序分为「程序代码」和「UI(User Interface)资源」两大部份,两部份最后以RC编译器整合为一个完整的EXE 文件。
并不是延伸档名为.dll 者才是动态联结函数库(DLL,Dynamic Link Library),事实.exe、.dll、.fon、.mod、.drv、.ocx 都是所谓的动态联结函数库。
Windows 程序调用的函数可分为C Runtimes 以及Windows API 两大部份。
另一组函数,Windows API,由操作系统本身(主要是Windows 三大模块GDI32.DLL 和USER32.DLL 和KERNEL32.DLL)提供。
所有Windows 程序都必须包含WINDOWS.H。
噢,USER 模块掌管各个外围的驱动程序,它们各有侦测回路。
如果把应用程序获得的各种「输入」分类,可以分为由硬件装置所产生的消息(如鼠标移动或键盘被按下),放在系统队列(system queue)中,以及由Windows 系统或其它Windows 程序传送过来的消息,放在程序队列(application queue)中。
接受并处理消息的主角就是窗口。每一个窗口都应该有一个函数负责处理消息,程序员必须负责设计这个所谓的「窗口函数」(window procedure,或称为window function)。
如果窗口获得一个消息,这个窗口函数必须判断消息的类别,决定处理的方式。
所谓makefile,就是让你能够设定某个文件和某个文件相比-- 比较其产生日期。由其比
较结果来决定要不要做某些你所指定的动作。
makefile 必须以NMAKE.EXE(Microsoft 工具)处理。
WinMain 则是Windows 程序的进入点:
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
...
}
// 在Win32 中CALLBACK 被定义为__stdcall,是一种函数调用习惯,关系到
// 参数挤压到堆栈的次序,以及处理堆栈的责任归属。其它的函数调用习惯还有
// _pascal 和_cdecl
因为API 函数CreateWindow 完全包办了整个巨大的工程。但是窗口产生之前,其属性必须先设定好。所谓属性包括窗口的「外貌」和「行为」,一个窗口的边框、颜色、标题、位置等等就是其外貌,而窗口接收消息后的反应就是其行为(具体地说就是指窗口函数本身)。
初始化工作完成后,WinMain 进入所谓的消息循环:
while (GetMessage(&msg,...)) {
TranslateMessage(&msg); // 转换键盘消息
DispatchMessage(&msg); // 分派消息
}
其中的TranslateMessage 是为了将键盘消息转化,DispatchMessage 会将消息传给窗口函数去处理。
程序进行过程中,消息由输入装置,经由消息循环的抓取,源源传送给窗口并进而送到窗口函数去。窗口函数的体积可能很庞大,也可能很精简,依该窗口感兴趣的消息数量多寡而定。
LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
注意,不论什么消息,都必须被处理,所以switch/case 指令中的default: 处必须调用defWindowProc,这是Windows 内部预设的消息处理函数。
Windows 的对话框依其与父窗口的关系,分为两类:
1. 「令其父窗口除能,直到对话框结束」,这种称为modal 对话框。
2. 「父窗口与对话框共同运行」,这种称为modeless 对话框。
为了做出一个对话框,程序员必须准备两样东西:
1. 对话框模板(dialog template)。这是在RC 文件中定义的一个对话框外貌,以各种方式决定对话框的大小、字形、内部有哪些控制组件、各在什么位置...等等。
2. 对话框函数(dialog procedure)。其类型非常类似窗口函数,但是它通常只处理WM_INITDIALOG 和WM_COMMAND 两个消息。
Modal 对话框的激活与结束,靠的是DialogBox 和EndDialog 两个API 函数。
对话框处理过消息之后,应该传回TRUE;如果未处理消息,则应该传回FALSE。这是因为你的对话框函数之上层还有一个系统提供的预设对话框函数。如果你传回FALSE,该预设对话框函数就会接手处理。
RC 文件是一个以文字描述资源的地方。
所谓空闲时间(idle time),是指「系统中没有任何消息等待处理」的时间。
Console 程序与DOS程序的差别:
都是所谓Win32 程序。如果程序是以main 为进入点,调用C runtime 函数和「不牵扯GUI」的Win32 API 函数,那么就是一个console 程序,console窗口将成为其标准输入和输出装置(cin 和cout)。
Win32 Console 程序设计、MFC Console 程序设计。
过去在DOS 环境下开发的程序,称为DOS 程序,它也是以main 为程序进入点,可以调用C runtime 函数。但,当然,不可能调用Win32 API 函数。
这么简单的例子中,我们看到MFC Console 程序的几个重点:
1. 程序进入点仍为main
2. 需包含所使用之类别的头文件(本例为AFX.H)
3. 可直接使用与GUI 无关的MFC 类别(本例为CStdioFile 和CString)
4. 编辑时需指定/MT,表示使用多执行线程版本的C runtime 函数库。
指定/MT的编译选项的方法:project->project settings->C/C++->code generation 运行库选项。
MFC console程序中不需要指定头文件windows.h,用_T()宏转化字符串为宽字符串。
什么是C Runtime 函数库的多线程版本?、
当C runtime 函数库于1970s 年代产生出来时,PC 的内存容量还很小,多任务是个新奇观念,更别提什么多执行线程了。因此以当时产品为基础所演化的C runtime 函数库在多线程(multithreaded)的表现上有严重问题,无法被多线程程序使用。利用各种同步机制(synchronous mechanism)如critical section、mutex、semaphore、event,可以重新开发一套支持多执行线程的runtime 函数库。问题是,加上这样的能力,可能导至程序代码大小和执行效率都遭受不良波及-- 即使你只激活了一个执行线程。
我们习惯以进程(process)表示一个执行中的程序,并且以为它是CPU 排程单位。事实上执行线程才是排程单位。
你可以说核心对象是系统的一种资源(噢,这说法对GDI 对象也适用),系统对象一旦产生,任何应用程序都可以开启并使用该对象。系统给予核心对象一个计数值(usage count)做为管理之用。
核心对象包括下列数种:
核心对象 产生方法
event CreateEvent
mutex CreateMutex
semaphore CreateSemaphore
file CreateFile
file-mapping CreateFileMapping
process CreateProcess
thread CreateThread
前三者用于执行线程的同步化:file-mapping 对象用于内存映射文件(memory mappingfile)。
CreateThread(),第五个参数如果是0,表示让执行线程立刻开始执行,如果是CREATE_SUSPENDED , 则是要求执行线程暂停执行( 那么我们必须调用ResumeThread 才能令其重新开始).