第二天课程 — 建立项目,创建窗口
在本章,你有机会运行另外一个短程序,一个完整的 Windows 程序。通过它可以了解到如何建立 Windows 项目。如果你对这个简单的 Windows 程序进行正确的编译和运行有困难的话,这一章正是解决这些问题的。 在本章你将学会如何完成下列工作:
- 在 Visual C++ 中建立项目
- 使用 makefile 文件
- 使用模块定义文件(DEF文件)
- 弹出一个传统的窗口,它可以改变大小,缩成图标和最大化
本章的目标是使你避开大量的配置问题而集中精力于编写 Windows 代码。
2.1 模块定义文件(DEF)和 Makefile 文件
标准的 Windows 程序通常至少由3个文件组成:
1.程序的一个这个主模块或多个模块,扩展名为 .CPP。
2.一个模块定义文件(module-defination file),扩展名为 .DEF。
3.一个项目文件(Project)或 Makefile 文件,扩展名为 .MAK。
另外,C/C++ 程序设计人员也大量地使用资源文件(resource file),它的源文件扩展名为 .RC。现 在我还不准备讨论这个题目。当然,许多程序还包括头文件(head file)。
几乎所有的 Windows 程序设计人员,尤其是 Windows 3.x 的设计人员都使用模块定义文件(DEF 文 件),它所要做的事情正如其名字所代表的意思那样,用来定义一个程序主模块的特征,包括:
- 文件名
- 它的用途或主要特征的简单描述
- 它的栈和堆的大小(WIN 32 的选项)
- 用来定义程序处理内存方式的几条语句(WIN 32 中不需要这样定义)
不过,在定义模块定义文件之前就创建一个 Windows 应用程序也是可以的。事实上,在 WIN 32 的应用 程序中,这种做法简直就成了一种习惯。当然,缺少模块定义文件程序会给出一个警告信息,但是一般来说这种 事情并不严重。在发出警告后,编译程序会用默认值来代替应在 DEF 文件中出现的值。在大多数情况下,让编 译程序使用这些默认值是可行的。
makefile 文件帮助你把一个项目中各自独立的源文件编译成一个可执行文件。因为在 Microsoft 的 IDE 环境中,这些文件的创建全是自动进行的,在此我就不探讨 makefile 的语法了。
2.2 用 Microsoft 工具建立项目和 Makefile 文件
用 Microsoft 工具的用户在 IDE 中可以很容易地自动建立 makefile 文件,只是用这种方法生成的文 件长而且复杂。
要在 Microsoft 的 IDE 中建立一个 makefile 文件,应先进入 Microsoft IDE 并选择 File|New, 在 New 对话框中选择 Project,在 New Project 对话框中,按下列步骤做:
1. 在 Project Name 框中输入你的工程文件名。
2. 把 Project Type 设置成 WIN32 Application。
3. 点击 OK。
然后你可以选择 Project|Add to Project|Files 项,将你事先编辑好的 .CPP(或者 .RC 文件,如果 有的话)文件插入。
接着,你可以选择 Build|Rebuild All 来编译你的程序,然后按 Ctrl+F5 来运行它。
2.3 创建窗口
到目前为止,你已经看到了几个不同的 Windows 程序,但你还没有看到一个真正的窗口。搭起一个窗口 的操作也有三步处理过程。只要编写一次这样程序,在以后的许多不同程序中就都可以重复使用,几乎完全不 需要做任何修改。
下面你可以看到一个 50 行的短程序(程序清单 2.1),我用它建立了一个真正的窗口,它能完成你期 望一个窗口能完成的动作。在这个阶段,你没有必要理解它,只要知道它大概是如何工作的,也不必为其中的 任何细节而烦心。你先按程序清单 2.1 将程序建立起来并运行它,你就会看到这些代码所做的全部事情,也 就是一个真正的 Windows 程序所做的事情。这就是说,你可以把窗口最大化、最小化或改变它的尺寸,这些 正是任何专业的 Windows 程序所应具备的功能。
程序清单 2.1 为 MakeWin 程序,该程序建立了一个传统的窗口,它有一个标题、边界和系统菜单。
程序清单 2.1 MakeWin 程序 // Program MakeWin.cpp #define STRICT #include<windows.h> #include<windowsx.h> #include<string.h> char Name[]="MakeWin"; LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); #pragma warning (disable:4068) #pragma argsused int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hPrevInstance, LPSTR lpszCmdParam,int nCmdShow) { HWND hwnd; MSG Msg; WNDCLASS WndClass; memset(&WndClass,0,sizeof(WNDCLASS)); WndClass.style = CS_HREDRAW|CS_VREDRAW; WndClass.lpfnWndProc = WndProc; WndClass.hInstance = hInst; WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); WndClass.lpszClassName = Name; RegisterClass(&WndClass); hwnd = CreateWindow(Name,Name,WS_OVERLAPPEDWINDOW, 10,10,600,400,NULL,NULL,hInst,NULL); ShowWindow(hwnd,nCmdShow); UpdateWindow(hwnd); while(GetMessage(&Msg,NULL,0,0)) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd,UINT Message, WPARAM wParam,LPARAM lParam) { if(Message==WM_DESTROY) { PostQuitMessage(0); return 0; } return DefWindowProc(hwnd,Message,wParam,lParam); } MakeWin 程序包含两个主要部分: 1. WinMain 函数 2. WndProc 函数 WinMain 函数可分为三个部分:
- 第一部分(18 到 24 行)用来指明窗口在哪儿注册:
18: memset(&WndClass,0,sizeof(WNDCLASS)); 19: WndClass.style = CS_HREDRAW|CS_VREDRAW; 20: WndClass.lpfnWndProc = WndProc; 21: WndClass.hInstance = hInst; 22: WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); 23: WndClass.lpszClassName = Name; 24: RegisterClass(&WndClass);
Register 过程告诉 Windows 一个窗口类的有关特性。但目前你还是暂时不要关心为什么要这样做以及 每一步的用途。你只需知道,这是典型的 WinMain 过程要做的第一件事。在下一章中注册一个复杂动作将被 分隔成独立的一个过程。
- 第二部分(26 到 30 行)是创建窗口:
26: hwnd = CreateWindow(Name,Name,WS_OVERLAPPEDWINDOW, 27: 10,10,600,400,NULL,NULL,hInst,NULL); 28: 29: ShowWindow(hwnd,nCmdShow); 30: UpdateWindow(hwnd);
创建一个窗口分成两步,第一步是调用 CreateWindow 函数(第 26、27 行),第二步分别 ShowWindow 函数(第 29 行)和 UpdateWindow(第 30 行)。创建一个窗口的动作如同注册窗口过程一样,通常也处理 成一个单独的过程,这样做是为了分解一个复杂的问题并建立一个良好的结构化的健壮的程序。但现在我把所以 的这些事情放在一起是为了让你感觉到政治家们常说的“大场面”。
- WinMain 过程的第三部分是消息循环(32 到 36 行):
32: while(GetMessage(&Msg,NULL,0,0)) 33: { 34: TranslateMessage(&Msg); 35: DispatchMessage(&Msg); 36: }
当用户移动鼠标或按键时,消息就被发送给消息循环,这相当于一个 Windows 程序的“驾驶员座位”, 是命令中心,一个循环保持着一个程序生命的重复性经历。
到此为止,如果你还没弄懂什么,也不必焦急。本章最后这一段的目的只是简单地给你一个经典的 Windows 程序的概貌,下一章你会了解到你所期望知道的细节。
现在你只要记住这个明确的结论:一个 Windows 程序过程有三个基本部分: 1. 注册窗口 2. 创建窗口 3. 进入消息循环
这就像数 1,2,3 那样简单!
MakeWin 程序的另一个关键部分是 WndProc 过程,它响应程序收到的消息。
41: LRESULT CALLBACK WndProc(HWND hwnd,UINT Message, 42: WPARAM wParam,LPARAM lParam) 43: { 44: if(Message==WM_DESTROY) 45: { 46: PostQuitMessage(0); 47: return 0; 48: } 49: return DefWindowProc(hwnd,Message,wParam,lParam); 50: }
MakeWin 程序只直接响应 WM_DESTROY 消息(第 44 行),其它所有消息都转给 DefWindowProc 去 处理。在下一章你会看到,DefWindowProc 过程只处理与窗口有关的默认行为。也就是说,当你要最大化一 个窗口或最小化一个窗口时,DefWindowProc 过程会处理你发来的消息也知道该怎么处理。
但是 DefWindowProc 并不处理 WM_DESTROY 消息,这是程序员的职责。当消息来到时,只调用 PostQuitMessage(46 行)。这样做可以退出 WinProc,其返回值为 0(47 行)。
我知道,在读本章时有些读者对什么是消息,消息在 Windows 程序设计中的重要性不清楚。如果你是 新手,不熟悉这个题材也不必着急。现在,只要接受这样一个事实:Windows 程序对消息是响应而不是中断。 这与许多程序设计员的习惯不同,你会发现面向消息的操作系统具有非常简单而且精巧的设计。
在下一章中,你会看到如何用宏来处理标准的 Windows 消息,所以你可以把消息响应函数放在 WndProc 之外。这如同把 Create 和 Register 函数移到 WinMain 过程之外一样。这样,使得 Windows 程序设计 人员能够建立结构良好的程序,这种程序便于维护和调试。这里我把这些过程放回到 WinMain 和 WndProc 函数中是使得你能看到一个典型的 Windows 程序的全貌。
至此,对 MakeWin 程序你只需知道这些,让我们总结一下:
·程序中有两个主要过程:WinMain 过程和 WndProc 过程。
·WinMain 过程有三个部分:首先注册窗口,然后创建窗口,最后是通过一个循环把消息发送给窗口。
·发送给窗口的任何消息都通过 WndProc 传送。Window 过程可以直接处理消息或将消息传递给 DefWindowProc 函数,它是默认消息处理器。事实上,某些程序首先由它自己处理消息,然后再将它传给 DefWindowProc 作进一步的处理。
这就是现阶段你需要了解的有关一个 Windows 程序的全部内容。关键是要掌握一个典型的 Windows 应 用程序的整个流程,在你的头脑中对全局有清晰的概念,这样在学习下一章时才能弄清细节。