Windows的编程模型不同于传统的C语言结构化编程,就是MS-DOS编程模型,先把两者做一比较,以对 Windows编程模型有一初步认识。
第一,用C语言编写基于MS-DOS的应用程序时,唯一绝对需要的是一个名为main的函数。当用户运行程序时,操作系统调用main,并且从这里开始可以使用任何需要的编程结构。如果程序需要获得用户键击或系统服务,便可调用适当的函数,例如getchar,或可以使用一个基于字符的窗口库。而当Windows操作系统启动一个程序时,调用的是WinMain函数。它最重要的任务是创建应用程序的主窗口,并处理系统发送给它的消息。两者一个基本区别是MS-DOS程序调用操作系统来获取用户输入,而Windows程序通过来自操作系统的消息来处理用户输入。
第二,许多MS-DOS程序直接写显存和打印机接口。这种技术的不利之处是对每一种设备需要其支持的驱动程序软件。Windows引入了一个名为图形设备接口(GDI)的抽象化外层,所以用户不必知道有关系统设备的类型。Windows程序不是寻址硬件,而是调用GDI函数,这些函数引用名为设备上下文(device context)的数据结构。Windows把设备上下文结构映射到物理设备,并且发出适当的输入/输出指令。图形设备接口几乎与直接视频访问一样快,并且它允许不同的Windows应用程序来共享显示。
第三,要在MS-DOS环境下进行数据驱动编程,必须或者为把数据编码成为初始化常量或者提供独立的数据文件让程序来读。进行Windows编程时,使用大量已经确立的格式在资源文件中存储数据。链接程序把二进制资源文件连接到C++编译器的输出来产生一个执行文件。资源文件包括位图、图标、菜单定义、对话框外观和字符串,甚至可以包括自定义的定制资源格式。
第四,在MS-DOS环境下一个程序的所有对象模块在建立过程中是静态连接的。Windows允许动态链接,这意味着特别创建的库可以在运行时加载和链接。多个应用程序可以共享动态链接库(DLLs),它节省内存和磁盘空间。动态链接增加了程序的模块性。
下图显示了Visual C++应用程序创建过程的一个综述。
前面说过标准Windows应用程序的入口函数是WinMain,WinMain函数完成下面几件事情:对应用程序初始化、创建并显示主窗口,然后进入消息分发循环,退出程序。分别解释如下:
(1)应用初始化。应用初始化包括对一些全局参数的赋值以及必要时对命令行参数进行处理等。
(2)主窗口的创建及显示。窗口是Windows的系统对象,对于程序员来说,窗口最主要的元素是窗口句柄和窗口过程。窗口句柄是一个系统全局唯一的标识,对窗口的各种操作都以句柄为基础;窗口过程是一个回调函数,它负责处理窗口中各种消息和事件,当系统接收到发给此窗口的消息或者在此窗口内发生的事件时,系统就会调用此窗口过程,所以应用程序的主体行为很大程度上有主窗口的窗口过程所控制。窗口程序中对各种消息或事件的响应形成了程序的应用逻辑。
Windows要求在创建窗口之前,先登记窗口类,窗口类型代表了一种窗口类型,窗口类指定了窗口的风格、窗口过程、窗口的背景色、窗口的菜单、图标、光标等。登记窗口成功后,创建窗口。创建窗口成功后,返回窗口句柄,以后可以利用此句柄对窗口进行各种操作,包括显示或隐藏窗口、调整窗口位置和大小。
(3)消息分发循环。消息循环式程序运行的核心所在,因为Windows时消息驱动的系统,系统与应用程序之间、程序与程序之间、程序与各种输入设备之间、甚至窗口与窗口之间都是通过消息传递进行交互,所以一个应用程序并不需要周期性检查鼠标和键盘的状态已获得各种用户输入信息,它只要建立一个消息分发循环,则系统会把所有传给此应用程序的输入信息都发过来,程序的分发循环可把消息进一步传给相应消息的窗口,由窗口过程处理消息。当应用程序受到WM_QUIT消息时,循环退出。
(4)程序结束处理。当消息循环结束后,程序完成必要的清理工作,比如释放分配的内存、把程序的配置信息保存起来、对COM库或OLE库的终结调用等,然后返回WinMain函数,应用程序正常退出。
下面说说Microsoft基础类库应用程序框架。MFC库是C++的Microsoft Windows应用程序编程接口。应用程序框架的一种定义是“提供一个一般应用程序所需要的全部面向对象软件组件的集成集合”。一个类库是可在应用程序中使用的有关C++类的集合。例如数学库可以执行一般数学操作,而一个通信类库可以支持通过串行链接的数据传输。有时构造提供的类的对象,有时派生自己的类,这取决于特定类库的设计。一个应用程序框架是一个类库的超级集合。一般的库是设计集成到任何程序的类的集合,但是一个应用程序框架自己定义了程序的结构。下面用我们都很熟悉的”hello,world!”中的代码介绍一些MFC应用程序框架的程序元素。
WinMain函数——记住,Windows要求应用程序有一个WinMain函数。在这里不能看到WinMain是因为它隐藏到应用程序框架内部。
CHelloApp类——类CHelloApp的一个对象代表一个应用程序。程序定义了一个全局CHelloApp对象theApp。CWinApp基类决定theApp的大多数行为,利用它可以进行各种全局操作,比如对注册标的操作、命令行参数处理、文档管理、光标和图标的操作等。应用类实例在MFC程序全局可访问,可以通过MFC提供的辅助函数AfxGetApp得到此实例对象。CWinApp类有几个虚函数值得提一下,首先是InitInstance和ExitInstance,它们分别完成应用的初始化以及终结处理,与前面介绍的WinMain函数结构中的初始化和结束处理相对应;另一个函数是OnIdle,消息循环在程序接受消息的间隙调用OnIdle函数;虚函数Run实现了消息循环。从这几个函数也可以看出,除了程序主窗口外,CWinApp类完成了应用程序的所有基本操作。
应用程序启动——当用户启动应用程序,Windows调用应用程序框架内置的WinMain函数,并且WinMain寻找一个由CWinApp派生出的全局构造的应用程序对象。不要忘记在C++程序中全局对象在主程序执行之前构造。
成员函数CWinApp::InitInstance——当WinMain函数找到应用程序对象,它调用虚拟成员函数InitInstance,这个成员函数调用所需要的构造和显示应用程序的主画面窗口。必须在派生的应用程序类中覆盖InitInstance,因为CWinApp基类不知道需要什么样的主画面窗口。
成员函数CWinApp::Run——函数Run隐藏在基类中,但是它发送应用程序的消息到它的窗口,这样可以保持应用程序的运行。在WinMain调用InitInstance之后,调用Run。
CHelloFrame类——类CHelloFrame的一个对象代表应用程序的主画面窗口。当构造函数调用基类CFrameWnd的成员函数Create时,Windows创建实际窗口结构,应用程序框架把它连接到C++对象。函数ShowWindow和UpdateWindow也是基类的成员函数,必须调用它们以显示窗口。
CHelloFrame::OnPaint函数——每次需要重绘窗口时,应用程序框架就调用这个重要的映射成员函数。语句CPaintDC与图形设备接口(GDI)有关,以后会作介绍。函数TextOut显示”Hello,world!”。
关闭应用程序——用户关闭主画面窗口来关闭应用程序。这个操作激发一系列事件,包括CHelloFrame对象的析构、从Run中退出、从WinMain中退出和CHelloApp对象的析构。
现在我们看到应用程序框架不但定义应用程序结构,而且它包含的不仅仅是C++基类。除了隐藏的WinMain函数,其它元素支持消息处理、诊断、动态链接库等等。