Windows 9x /NT 环境下的C程序设计快速入门
先看一个十分简单的程序
//---------------------------------------------------------------------------------------------------
// winfirst.c
#include <windows.h>
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
MessageBox (NULL, TEXT ("Hello, Windows 98!"), TEXT ("HelloMsg"), 0) ;
return 0 ;
}
//------------------------------------------------------------------------------------------------------
首先我们看 #include<windows.h>,这是每一个用C写的win32程序都必须含有的头文件,申明了绝大部分的winAPI.(比如说下面的MessageBox函数)。
注:API =Application Program Interface,应用软件编程接口,DOS年代的系统中断就是API
我们都知道在DOS下有一个main函数,作为程序的主体部分。Win32 环境下则用WinMain代替。(WINAPI 代表一种调用习惯,用于约束函数的栈空间的清除。现在有2种流行的调用习惯,__cdecl和__stcall,__cdecl是C/C++语言默认的调用习惯,栈由调用者清除,支持变参数函数。__stdcall 是PASCAL 的调用习惯,栈由函数自己清除,不支持变参函数。WINAPI = __stdcall,所有的Win32函数都使用__stdcall 调用习惯。)这个函数有4个参数,其中hPrevInstance是16位Windows的遗留物,现在已经没有用了,szCmdLine则是命令行参数。
hInstance 被称为一个“实例句柄”(Instance Handle)。这个参数由操作系统传递
iCmdShow 表示了窗口显示的方式,这个参数可以控制应用程序在启动时的外观。
如何将该程序编译运行?
这是一个很多同学都会遇到的问题(落后的课本,落后的工具(BC++3.1))。我建议使用VC6 来编译这个程序。许多同学都觉得VC6 很难,其实他只是一个工具而已,可以用以屠龙之计也可来切切小菜,只要掌握一点点就可以为以后的学习带来很大的便利。
点击 |File|New|, 在“Project” 选项卡中选取“Win32 Application”进行创建。在接下来的对话框中选取“a simple Win32 Application”,Wizzard生成代码如下
#include "stdafx.h"
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
return 0;
}
只要在“TODO: Place code here”中添加代码就可以了。
至于#include “stdafx.h”,不要管他,其中有#include “windows.h”。这里我们先把 “//TODO: Place code here”替换成 MessageBox(…);然后按F7编译,Ctrl+F5运行。或则是按F5进行调试运行。
Windows是什么?
从外表上看起来Windows的元素十分的多,有工具栏,菜单,滚动条,对话框等等还有许多各种各样的UI(User Interface)元素。但是请记住,所有这些都是window,也就是说,windows的表面是由window所组成的。记住这么一句话“windows就是一块区域”,那些多姿多彩的元素只是各自的window属性不同。以后从这个角度上去想问题,许多界面问题都能快速的找到思路。(我理解到这一点用了整整10个月的时间…,残念)。至于Windows的核心部分,是由每种操作系统都必须有的进程(Process)组成的。
Windows是面向对象的
一谈到面向对象,许多人都会热血沸腾,欢呼雀跃。不错,OO(Object-Oriented)确实很棒,可是我不知道,在学校里有多少同学真正明白了OO的含义。在目前的教育影响下,许多同学一谈到OO,想到了一个一个的类,想到了继承等等语言特性。而实际上OO是语言无关的,就目前的情况来看,OO 的观念每天也都在发生着变化。当你可以跨越语言的障碍运用OO进行设计的时候,用OO描述问题的时候, OO的观念不会人云亦云的时候才算是理解了什么是真正的OO。(说远了,下次还会专门讨论这个话题,这次关键是说说Windows与OO的关系,呵呵)。
为什么说用C写的Windows是OO的,是因为在Windows的世界中,基本上是严格的遵守OO世界的规则的,是OO观念的跨语言应用。这里列出我认为最为关键的几条。
对象(object)
在OO的世界中,万物都是对象。其中最重要的一个概念就是“对象标识”(object identity),在OO的世界中,每一个对象都有一个唯一的标识符,这个标识符可以作为不同的软件实体标识、处理的特性。通常这个标识符叫做句柄(handle),每一个真正的OO世界句柄都满足2个条件。首先是“在任何情况下,对象在整个生命周期都保持同一个句柄”,另一个为“两个对象不可能具有相同的句柄”。这两个原则相当重要。在C++中,对象句柄是一个指针,通过指针可以区别两个不同的对象。
在Windows环境下的最直观的对象就是一个一个的window了。这些window通过句柄标识示(Handle),拥有了一个window的句柄,就等于拥有了这个window对象。
类(class)
在OO的世界中,每一个对象都必定属于某一个类。类可以说是创建对象的模板,从相同的类中创建出的每个对象具有相同结构和行为。(有一种理论认为OO世界中的每一个类都必须是从一个相同的基类继承的来得,不过我不这么认为。)
在windows环境下有多个类,而且这些类产生的对象分属不同的领域。(注意!由不同的类创建出来的对象在同一个领域中都具有完全不同的、独一无二的Handle)。比如说GDI界面对象和windows核心对象(关于具体GDI对象和windows核心对象的差异,以后再说)
windows通过32位的Handle标识所有的对象。上面的Winmain函数的参数hInstance就代表了一个“实例对象”。
消息(message)
在OO的世界中,对象与对象之间要进行交流就必须使用消息,而且能够接收消息是对象的基本能力。消息可以理解为对象1向对象2请求操作的“载体”。消息有4个要素:消息的发送者,消息的接收者,请求操作的名称,进行操作所需要的信息。对象A要向对象B发送消息,首先必须知道的就是对象B的对象标识。举个很简单的例子,C++中的一条语句
ObjA.data=ObjB.data;
翻译成OO中的消息为:对象ObjA向ObjB发送了一个消息,消息的操作名称为“get_data”,附加消息无。
现在我们已经知道,Windows中最基本的对象就是一个一个的window,要求这些window进行动作的方法就是给他们发送一个消息。Windows在这一点上表现的很抽象。比较容易理解的是通过调用SendMessage这个API函数向一个Window对象发送消息。
比如说一条语句
SendMessage(hObj,MESSAGE_MOVE,info,callbackfoo);
如果使用结构化的眼光去看,只是调用一个函数而已,但是使用OO的眼光看,这条语句应该解释为“向由hWnd标识的对象Obj发送一个消息,请求Obj执行操作MOVE,这个操作需要附加信息info,callbackfoo。用OO的语言描述为:“hObj.Move(info,callbackfoo)”在自己编码时,时刻保持清晰地认识,相信不管是对于OO思想的理解,还是对windows的理解,都是有帮助的。
还有大量的API的作用其实也是发送消息,只是他们API的外表容易掩饰这一点,比如说
int GetWindowText(
HWND hWnd, // handle to window or control
LPTSTR lpString, // text buffer
int nMaxCount // maximum number of characters to copy
);
这个API用OO的眼光看就是调用这个API的对象向由hWnd标识的window对象发送一个消息,请求操作“getText”,附加信息是lpString和nMaxCount。
第一个正统的windows程序
源码如下:
/*--------------------------------------------------------------------------------
HELLOWorld.C
CopyRight© waterflier
------------------------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("Cann’t RegisterClass!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, // 窗体类名称
TEXT ("The Hello Program"), // window的标题
WS_OVERLAPPEDWINDOW, // window 的外观类型
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, // 程序实例句柄
NULL) ;
ShowWindow (hwnd, iCmdShow) ;
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)
{
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
{
case WM_CREATE:
//PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
DrawText (hdc, TEXT ("Hello,world!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
windows程序的运行规则
传统的dos程序由dos加载后,操作系统就不再插手程序的运行。Windows程序则完全不同。我们知道,在OO的世界中如果要操作一个对象的行为,所使用的手段就是先其发送消息,而接收消息的对象也必须有处理消息所请求的操作的能力,所以windows程序是“消息驱动”的。windows程序设计的主要过程就是编写发送请求操作的消息代码和接收消息的对象处理操作的代码。
Windows程序在由操作系统加载后,首先运行Winmain函数,该函数的主要目的是创建对象(当然在其他的地方也可以创建对象),一般都会创建一个window对象。然后Winmain进入一个消息循环
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
用户的操作(主要来自硬件)通过操作系统转化为某一消息,操作系统把这个消息放到某个对象的消息队列中去(操作系统当然知道要放到哪个对象,具体机制以后再详细介绍),应用程序通过GetMessage(&msg,NULL,0,0)从消息队列中抓取一个消息,能后对这个消息进行必要的转换(TranslateMessage(&msg));,再通过DispatchMessage(&msg)把消息发送给特定的对象。(由于DispatchMessage这个API是操作系统实现的,所以此时程序的控制权又交还给操作系统。操作系统会把这个消息发给特定的对象。)所以windows程序在运行时时刻都需要操作系统的介入,来维护oo世界的各种规则。
生成第一个window对象
创建一个对象首先需要知道这个对象所属的类。虽然都是window对象,但是这些对象都具有不同的个性,所以需要从不同的window类创建。
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("Cann’t RegisterClass!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
这一段就是定义一个window类并且把这个类注册到系统的“运行时类型库”(类型库也是oo世界的一个组成部分,比较好理解)。Wndclass的各个字段都很有意思,也很重要,这些字段定义了将来从这个类创建的对象的许多最基本的性质。最重要的是lpfnWndProc字段,类型为回调函数指针。这个函数定义了该window类处理“请求的操作”的能力。另外一个重要的字段为style,定义了该类产生的对象能够接收到那些消息。还有一个重要的字段是lpszClassName,定义了该类的名称,以后要创建一个该类的对象,这个值很重要。(我个人觉得用字符串作为一个有很多类的OO世界中的类标识很不妥,为什么不用GUID呢)。其他字段同学们可以自己去查MSDN.