Win32 程序开发流程
Windows 程序分为「程序代码」和「UI资源」两大部份,两部份最后以RC编译器整合为一个完整的EXE 文件(图1-1)。所谓UI 资源是指功能菜单、对话框外貌、程序图标、光标形状等等东西。这些UI 资源的实际内容(二进制代码)系借助各种工具产生,并以各种扩展名存在,如.ico、.bmp、.cur 等等。程序员必须在一个所谓的资源描述档(.rc)中描述它们。RC 编译器(RC.EXE)读取RC 档的描述后将所有UI资源档集中制作出一个.RES 档,再与程序代码结合在一起,这才是一个完整的Windows可执行档。
需要什么函数库(.LIB)
众所周知Windows 支持动态联结。换句话说,应用程序所调用的Windows API 函数是在「执行时期」才联结上的。那么,「联结时期」所需的函数库做什么用?有哪些? 并不是延伸档名为.dll 者才是动态联结函数库(DLL,Dynamic Link Library)
事实上.exe、.dll、.fon、.mod、.drv、.ocx 都是所谓的动态联结函数库。
Windows 程序调用的函数可分为C Runtimes 以及Windows API 两大部份。早期的C Runtimes 并不支持动态联结,但Visual C++ 4.0 之后已支持,并且在32 位操作系统中已不再有small/medium/large 等内存模式之分。以下是它们的命名规则与使用时机:
- LIBC.LIB - 这是C Runtime 函数库的静态联结版本。
- MSVCRT.LIB - 这是C Runtime 函数库动态联结版本(MSVCRT40.DLL)的
import 函数库。如果联结此一函数库,你的程序执行时必须有MSVCRT40.DLL在场。
另一组函数,Windows API,由操作系统本身(主要是Windows 三大模块GDI32.DLL 和U SER32.DLL 和KERNEL32.DLL)提供(注)。虽说动态联结是在执行时期才发生「联结」事实,但在联结时期,联结器仍需先为调用者(应用程序本身)准备一些适当的信息,才能够在执行时期顺利「跳」到DLL 执行。如果该API 所属之函数库尚未加载, 系统也才因此知道要先行加载该函数库。这些适当的信息放在所谓的「import 函数库」中。32 位Windows 的三大模块所对应的import 函数库分别为GDI32.LIB 和USER32.LIB 和KERNEL32.LIB。
需要什么头文件(.H)
所有Windows 程序都必须包含WINDOWS.H。早期这是一个巨大的头文件,大约有5000 行左右,Visual C++ 4.0 已把它切割为各个较小的文件,但还以WINDOWS.H 总括之。除非你十分清楚什么API 动作需要什么头文件,否则为求便利,单单一个WINDOWS.H 也就是了。
不过,WINDOWS.H 只照顾三大模块所提供的API 函数,如果你用到其它system DLLs, 例如COMMDLG.DLL 或MAPI.DLL 或TAPI.DLL 等等,就得包含对应的头文件,例如COMMDLG.H 或MAPI.H 或TAPI.H 等等。
以消息为基础,以事件驱动之
Windows 程序的进行系依靠外部发生的事件来驱动。换句话说,程序不断等待(利用一个while 回路),等待任何可能的输入,然后做判断,然后再做适当的处理。上述的「输入」是由操作系统捕捉到之后,以消息形式(一种数据结构)进入程序之中。操作系统如何捕捉外围设备(如键盘和鼠标)所发生的事件呢?噢,USER 模块掌管各个外围的驱动程序,它们各有侦测回路。
如果把应用程序获得的各种「输入」分类,可以分为由硬件装置所产生的消息(如鼠标移动或键盘被按下),放在系 统队列(system queue)中,以及由Windows 系统或其它Windows 程序传送过来的消息,放在程序队列(application queue)中。以应用程序的眼光来看,消息就是消息,来自哪里或放在哪里其实并没有太大区别,反正程序调用GetMessage API 就取得一个消息,程序的生命靠它来推动。所有的GUI 系统,包括UNIX 的X Window 以及OS/2 的Presentation Manager,都像这样,是以消息为基础的事件驱动系统。
可想而知,每一个Windows 程序都应该有一个回路如下:
MSG msg;
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg); DispatchMessage(&msg);
}
// 以上出现的函数都是Windows API 函数
消息,也就是上面出现的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;
} MSG;
接受并处理消息的主角就是窗口。每一个窗口都应该有一个函数负责处理消息,程序员必须负责设计这个所谓的「窗口函数」(window procedure,或称为window function)。如果窗口获得一个消息,这个窗口函数必须判断消息的类别,决定处理的方式。
以上就是Windows 程序设计最重要的观念。至于窗口的产生与显示,十分简单,有专门的API 函数负责。稍后我们就会看到Windows 程序如何把这消息的取得、分派、处理动作表现出来。