Windows程序设计1
Windows程序设计1
2011年03月27日
第八节 windows的函数
Windows向应用程序开发人员提供了数以百计的函数。这些函数的例子包括DispatchMes-sage(),PostMessage(),RegisterWindowMessage()以及SetActiveWindow()。对于使用基础类库的C++程序员,许多函数自动被运行。
在16位的Windows 3.x下的函数声明包括一个pascal修饰符,这在DOS下更为有效Windows95和Windows NT下的32位应用程序不再使用这个修饰符。如你所知,所有Windows函数的参数是通过系统来传递的。函数的参数从最右边的参数开始向左压入栈,这是标准的C方式。在从函数返回之前,调用过程必须按原来压入栈的字节数调整栈指针。
第九节 windows应用程序框架
Windows头文件:WINDOWS.H
WINDOWS.H头文件(以及其它相关文件)是所有程序的内在部分。传统上,WINDOWS.H是所有C语言编写的Windows应用程序必需的一部分。当在C++中使用基础类库时,WINDOWS.H包括在AFXWIN.H头文件中。
Windows应用程序的组成
在开发Windows应用程序的过程中有一些重要的步骤:
*用C语言编写WinMain()函数和相关的窗口函数,或者在C++中使用基础类,比如CWinApp等。
*创建菜单、对话框和其它资源并把它们放入资源描述文件。
*(可选)使用Vinsual C++编译器中的企业编辑器来创建对话框。
*(可选)使用Vinsual C++编译器中的企业编辑器来创建对话框。
*用项目文件来编译并链接所有的C/C++源程序和资源文件
Windows应用程序中的组成部分
1. WinMain()函数
Windows 95和Windows NT需要一个WinMain()函数。这是应用程序开始执行和结束的地方。
从Windows向WinMain()传递四个参数。下面的代码段演示了这些参数的使用:
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPreInst,
LPSTR 1pszCmdLine, int nCmdShow)
第一个参数hInst包含了应用程序的实例句柄。当应用程序在Windows下运行时,这个数字唯一标识了应用程序。
第二个参数hPreInst将始终是一个NULL值,表明没有这个应用程序的其它实例正在运行,因为在Windows 95和Windows NT下每个应用程序都在它自己单独的地址空间中运行。
第三个参数1pszCmdLine是指向一个以'/0'结尾的字符串的长指针,这个字符串代表了应用程序的命令行参数。
WinMain()的第四个参数是nCmdShow。在nCmdShow中存储的整数代表了Windows预定义的许多常量中的一个,它决定了窗口显示的方式。
2. WNDCLASS
WinMain()负责注册应用程序的主窗口类。每个窗口类都建立在一些用户选择的风格、字体、标题字、图标、大小、位置等的基础上。窗口类实际上是定义这些属性的一个模板。
基本上,所有的Windows类定义都使用相同的标准C/C++结构。下面的例子是一个说明WNDCLASSW结构的typedef语句,WNDCLASS是从这儿继承的:
typedef struct tagWNDCLASSW
UINT style;
WNDPROC 1pfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBR8USH hbrBackground;
LPCWSTR 1pszMenuName;
LPCWSTR 1pszClassName;
WNDCLASSW,*PWNDCLASSW,NEAR*NPWNDCLASSW, FAR*LPWNDCLASSW;
下面的部分讨论了WNDCLASS结构中的不同的域。其中有些域可以被赋予NULL,告诉Windows使用缺省的预定义值。
style:style域指明了类风格。
1pfnWndProc:接受一个指向窗口函数的指针,它将执行所有的窗口任务。
cbClsExtra:指定了必须在窗口类结构后面分配的字节数,它可以是NULL。
cbWndExtra:指定了必须在窗口实例后面分配的字节数,它可以是NULL。
hInstance:定义了注册窗口类的应用程序实例。它必须是一个实例句柄,不得是NULL。
hIconhIcon:划定利用窗口最小化时显示的图标。它可以是NULL。
hCursorhCursor:定义了应用程序使用的光标。这个句柄可以是NULL。
hbrBackground:提供了背景刷子的标识符。
1pszMenuName:是指向一个以空字符结尾的字符串的指针。这个字符串是菜单的资源名。这一项可以为NULL。
1pszClassName:是指向一个以空字符结尾的字符串的指针。这个字符串是窗口类的名字。
3.WNDCLASSEX
Windows提供了一种扩展的WNDCLASS定义,名为WNDCLASSEX,它允许应用程序使用小图标。下面是WNDCLASSEX结构的定义:
typedef struct WNDCLASSEX
UINT style;
WNDPROC 1pfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hbrBackground;
LPCTSTR 1pszMenuName;
LPCTSTR 1pszClassName;
HICON hIconSm;
WNDCLASSEX;
你可以看到这两个结构是相同的,除了WNDCLASSEX包括了hIconSm成员,这是与窗口类有关的小图标的句柄。
4.定义窗口类
应用程序可以定义它们自己的窗口类,只要先定义一个合适类型的结构,然后用窗口类的信息来填充结构的域。
下面的代码示范了如何定义并初始化一个WNDCLASS结构。
char szProgName[]="ProgName";
.
.
.
WNDCLASS wcApp;
.
.
.
wcApp.1pszClassName=szProgName;
wcApp.hInstance=hInst;
wcApp.1pfnWndProc=WndProc;
wcApp.hCursor=LoadCursor(NULL,IDC-ARROW);
wcApp.hIcon=NULL;
wcApp.1pszMenuName=szAppIName;
wcApp.hbrBackground=GetStockObject(WHITE-BRUSH);
wcApp.style=CS-HREDRAW| CS-VREDRAW;
wcApp.cbClsExtra=0;
wcApp.cbWndExtra=0;
if(!RegisterClass (&wcApp))
return 0;
WNDCLASS结构中的第二个域是wcApp.hInstance,它被赋予了WinMain()被激活后返回的hInst的值。这指明了应用程序的当前实例。1pfnWndProc被赋予执行所有窗口任务的窗口函数的指针地址。对于大部分应用程序,这个函数叫做WndProc()。
注意:WndProc()是一个用户定义而不是预定义的函数名。在赋值语句之前必须给出函数原型。
wcApp.hCursor域被赋予实例的光标句柄。
当wcApp.1pszMenuName被赋予NULL值的时候,Windows就认为这个窗口类没有菜单。 如果有,菜单必须有一个名字,它必须出现在引号里面。GetStockOject()函数返回一个刷子句柄,用于在这个类创建的窗口用户区中画出背景色。
wcApp.style窗口类风格被设为CS-HREDRAW或CS-VREDRAW。
最后的两个域,weApp.cbClsExtra以及wcApp.cbWndExtra经常被设为0。这些域可以被选用以指明窗口结构和窗口数据结构后面应该保留的附加字节数。
下面这段代码用于注册窗口类:
if(!hpreInst)
.
.
.
if(! RegisterClass(&wcApp))
return FALSE;
Windows 95和Windows NT通过检查hPreInst的值来确定多少个实例,而hPreInst总是NULL,所以就注册窗口类.
5.创建窗口
窗口通过调用CreateWindow()函数来创建。这个过程对所有版本的Windows都是一样的。窗口类定义了窗口的一般特征,允许同一个窗口类被用于多个不同的窗口,CreateWin-dow()函数的参数指明了关于窗口的更详细的信息。
CreateWindow()函数的参数信息包括以下内容:窗口类、窗口标题、窗口风格、幕位置、窗口的父句柄、菜单句柄、实例句柄以及32位的附加信息。在大部分应用程序中 ,这个函数会是下面这个样子:
hWnd=CreateWindow(szProgName,"Simple Windows Program",
WS-OVERLAPPEDWINDOW,CW-USEDEFAULT,
CW-USEDEFAULT,CW-USEDEFAULT,
CW-USEDEFAULT,(HWND)NULL,(HMENU)NULL,
(HANDLE)hInst,(LPSTR)NULL);
第一个域szProgName(已赋过值)定义了窗口的类,后面是窗口标题条上使用的标题。窗口的风格是第三个参数
下面的六个参数代表了窗口的x、y坐标和x、y方向的大小,然后是父窗口句柄和窗口菜单句柄。每个域都被赋予一个缺省值。hInst域包含了程序的实例句柄,后面是一个附加参数(NULL)。
显示和更新窗口
在Windows下,ShowWindow()函数被用来实际显示一个窗口。下面的代码示范了这个函数:
Show Window(hWnd,nCmdShow);
在调用CreateWindow()时生成的窗口句柄被用作hWnd参数。ShowWindow()的第二个参数是nCmdShow,决定了窗口被如何显示。这个显示状态也被称为窗口的可视状态。
显示窗口的最后一步是调用Windows的Update Window()函数。
UpdateWindow(hWnd);
6.消息循环
一旦调用Win-Main()函数并显示了窗口,应用程序就需要一个消息处理循环。最常用的实现方法是使用一个标准的while循环:
while (GetMessage (&lpMsg,NULL,0,0))
{
TranslateMessage(&lpMsg);
DispatchMessage(&lpMsg);
}
GETMESSAGE()函数:应用程序要处理的下一个消息可以通过调用Windows的GetMessage()函数来取得。
NULL参数指示函数取回这个应用程序的任何窗口的任何消息。最后两个参数0和0告诉GetMessage()不要使用任何消息过滤器。消息过滤器能够将接收到的消息限制在一个明确的范围之内,如键盘消息或鼠标消息等。
一般应用程序应该确认通向消息循环的所有步骤都已经正确地执行过了。这包括确认每个窗口类都已经注册过,都已经被创建。否则,一旦进入了消息循环,只有一个消息能够结束这个循环。无论何时处理了WM-QUIT消息,返回值是FALSE。这会引发主循环关闭例程。WM-QUIT消息是应用程序退出消息循环的唯一途径。
TRANSLATEMESSAGE()函数:通过TranslateMessage()函数,虚拟消息可以被转换为字符消息。
DISPATCHMESSAGE()函数:Windows通过DispatchMessage()函数将当前的消息发送到正确的窗口过程。
******* 窗口函数
所有的应用程序都必须包括一个WinMain()函数和一个回调窗口函数。因为一Win-dows应用程序从不直接访问任何窗口函数,每个应用程序都必须向Windows提出请求以执行规定的操作。
一个回调函数在Windows中注册,当Windows要对一个窗口进行操作时,它就被调用。各个应用程序的回调函数的实际代码长度会大不相同。窗口函数本身可以非常小,只处理一个或两个消息,也可以非常大而且复杂。
下面的代码段(不完整的应用程序说明语句)显示了在应用程序中的回调窗口函数WndProc()的一个范例:
LRESULT CALLBACK WndProc(HWND hWnd,UNIT messg,WPARAM wParam,LPARAM 1Param)
HDC hdc;
PAINTSTRUCT ps;
switch(messg)
case WM-PAINT:
hdc=BeginPaint(hWnd,&ps);
.
.
.
ValidateRect(hWnd,NULL);
EndPaint(hWnd,&ps);
break;
case WM-DESTROY:
postQuitMessage(0);
break;
default:
return(DefWindowProc(hWnd,messg,wParam,1param));
return(0);
Windows希望窗口类结构定义中wcApp,1pfnWndProc域的名字能够与回调函数的名
字匹配。后面用这个窗口类创建的所有窗口的回调函数都应该用WndProc()的名字。
下面的代码段讨论一个窗口类结构中回调函数名的位置和赋值:
.
.
.
wcApp.1pszClassName=szProgName;
wcApp.hInstance=hInst;
wcApp.1pfnWndProc=WndProc;
.
.
.
Windows有向百个消息可以发送给窗口函数。这些消息用“WM-”打头的标识符来
标识。
WndProc()的第一个参数是hWnd。hWnd包含了Windows发送消息的窗口句柄。
函数的第二个参数messg按WINUSER.H中的定义指明了即将被处理的实际消息。最后的两个参数wParam以及1Param,指明了处理每个消息所需的附加信息。
WndProc()函数继续定义了两个变量:hdc指明了显示设备句柄,ps指明了存储用户区
信息所需的一个PAINTSTRUCT结构。
回调函数被用于检查将被处理的消息并选择执行适当的动作。这个选择过程通常在一个标准的C语言的switch语句中完成。
模块定义文件
正如你在前面学到的,LINK提供了所有Windows应用程序需要的模块定义文件在命令行方式下的替代品。模块定义文件向链接器提供有关的定义和描述信息,这样链接器就可以知道如何来组织Windows应用程序的可执行文件。这些信息已经成为新执行文件格式的文件头的一部分。
注意:在Windows 95和Windows NT下面,你不太可能需要创建一个模块定义文件。这些信息是为了完整性和向后兼容。
第十节 VC++提供的windows编程工具
Visual C++编译器包含几个资源编辑器。单独的编辑器可以通过编译器主菜单中的Insert Resource菜单来运行。图形对象都是资源,象图标、光标、消息框、对话框、字体、位图、画笔、刷子等。资源代表应用程序的可执行文件中包含的数据。
资源编译器RC.EXE是一个Windows资源的编译器。。
资源以及附加的编译器的使用增加了应用程序开发的复杂性。但是它容易在项目工具中使用。
项目文件
项目文件提供了概览资源和程序代码编译过程的手段,同时也可以使应用程序的可执行版本保持最新。它们跟踪源文件的日期和时间以实现这些增强的功能。项目文件包含了有关特定程序的编译链过程的信息。项目文件是在集成的C或C++编辑环境中创建的。项目文件还支持增强的编译和链接。
资源
当你使用VisualC++编译器提供的资源编辑器时,用自己的图标、指针和位图来定制Windows应用程序非常容易。这些编辑器给你提供了一个开发图形资源的完整环境。这些编辑器同时也能帮助你开发菜单和对话框-Windows下数据输入的基本手段。这些编辑器还能帮你操纵单独的位图、加速键和字符串。。
资源编辑器
每一种编辑器都在VisualC++环境中提供,都是编译器的一个集成的部分。这样,每种编辑器都是在Windows下运行的完全集成的资源开发工具。你可以通过选择Insert Resource来启动每一种编辑器。
下面我们将通过教程演示资源编辑器的使用。请单击返回,然后运行教程。
? 第十一节 MFC的基本概念
基础类库为你提供了易于使用的对象。将Windows与C++联系起来是很自然的,这样就可以充分利用面向对象技术的优点。MFC开发组实现了大量的Windows应用程序编程接口(API)。这个C++库在一些可重用的类中封装了最重要的数据结构和API函数调用。
类似MFC这样的类库比起前面两章讨论的C程序员使用的函数库有很多优点。
下面列出了C++类的一些优点,比如:
*用类对数据和代码进行封装
*继承性
*消除函数和变量名的冲突
*类是语言的自然扩展
*通常,精心设计的库减少了代码量
利用基础类库,创建一个窗口所需的代码大约只占传统应用程序的三分之一。这就可以使程序员只用花很少的时间与Windows打交道,把更多的精力集中在开发自己的程序代码上。
22.2 MFC的设计考虑
基础类库设计小组定义了严格的设计规则,在设计MFC库时必须遵循这些规则。这些规则和方针如下:
*利用C++的威力,但不能把程序员吓倒
*使从标准API调用到类库的转换尽可能简单
*允许混合使用传统的函数调用和新的类库
*在设计类库的时候综合考虑功能和效率
*建成的类库必须能够方便地在不同平台间移植,如Windows 95和Windows NT
设计小组感到要开发高质量的代码必须从MFC库本身开始。C++基础类库必须又小又快。它的简单性使它易于使用,而执行速度与庞大的C函数库接近。
这些类的设计方式应该让熟练的Windows程序员不必重新学习各种函数的名字。通过仔细的命名和设计可以实现这一点。Microsoft认为这一点是MFC区别于其它类库的一个特征。
MFC小组还把基础类库设计为是允许以混合方式编程的。这就是说,在同一个源文件里,既可以使用类也可以使用传统的函数调用。即使是在使用MFC时,类似SetCursor()和GetSystemMetrics()这样的函数还是需要直接调用。
Microsoft也知道类库必须方便使用。其它厂商提供的一些类库设计得太抽象。按Microsoft的说法,这些笨重的类企图生成又大又慢的应用程序。MFC库提供了合理的抽象,保证代码很小。
开发小组将原始的MFC库设计为动态的而不是静态的。动态的结构是这些类可以适应我们现在使用的Windows 95和Windows NT环境。
22.3 MFC库的关键特性
从其它编译器厂商那儿也可以获得Windows类库,但Microsoft宣称他们的MFC类库具有许多真正的优点:
*全面支持所有的Windows函数、控件、消息、GDI(图形设备接口)绘图原语、菜单以及对话框。
*使用与Windows API相同的命名约定。因此,从名字上就可以直接知道类的功能。
*消除了一个错误源,即大量的switch/case语句。所有的消息都被映射到类的成员函数。这种消息-方法的映射方法应用于所有的消息。
*能够把对象的信息输出到文件,这提供了更好的诊断支持。同时还提供了验证成员变量的能力。
*增强的例外处理设计,使得程序代码失败的可能性更小。能够解决“内存不足”以及其它一些问题。
*可以在运行时决定数据对象的类型。这允许对类的域进行动态操纵。
*小而快速的代码。前面已经提到,MFC库只添加了很少一些代码,执行起来几乎与传统的C语言Windows应用程序一样快。
*对组件对象模型(COM)的支持。
有经验的Windows程序员会立刻喜欢上其中的两个特性:熟悉的命名约定和消息-方法映射机制。如果你重新检查一下在第二十一章中开发的应用程序的源代码,你会看到大量用于处理错误的switch/case语句。还应该注意这些应用程序调用了大量的API函数。当你使用MFC库的时候,这两种现象都消失或减少了。
专业程序员肯定会欣赏在MFC库中实现的更好的诊断和很小的代码。现在程序员就可以利用MFC库的好处而不必担心他们的应用程序的代码大小了。
最后,MFC是唯一真正有用的类库。
22.4 一切从CObject类开始
类似MFC这样的类库通常都来自很少的几个基类。然后,另外的类就可以从这些基类中继承而来。CObject是在开发Windows应用程序时大量使用的一个基类。在MFC/INCLUDE子目录下提供的MFC库头文件包括了许多类定义信息。
我们来简单地看一下,CObject,它在头文件AFX。H中有定义:
///////////////
//class CObject is the root of all compliant objects
class CObject
public:
//Object model(types,destruction,allocation)
virtual CRuntimeClass*GetRuntimeClass () const;
virtual~CObject();//virtual destructors are necessary
//Diagnostic allocations
void*PASCAL operator new(size-t nSize);
void*pascal operator new(size-t,void*p);
void PASCAL operator delete(void*p);
#if defined(-DEBUG)&&!defined(-AFX-NO-DEBUG-CRT)
//for file name/line number tracking using DEBUG-NEW
void* PASCAL operator new(size-t nSize,LPCSTR 1pszFileName,int nLine);
//Disable the copy constructor and assignment by default
//so you will get compiler errors instead of unexpected
//behavior if you pass objects by value or assign objects.
protected:
CObject();
private:
CObject(const CObject& objectSrc);//no implementation
void operator=(const CObject& objectSrc);
//Attributes
public:
BOOL IsSerializable()const;
BOOL IsKindOf(const CRuntimeClass*pClass)const;
//Overridables
virtual void Serialize (CArchive& ar);
//Diagnostic Support
virtual void AssertValid()const;
virtual void Dump(CDumpContext& dc)const;
//Implementation
public:
static const AFX-DATA CRuntimeClass classCObject;
#ifdef-AFXDLL
static CRuntimeClass*PASCAL-GetBaseClass();
#endif
;
为了清楚起见,对这段代码作了一些细微的改动。但和你在头文件AFX.H可以找到的代码基本一样。
检查CObject的代码,注意构成这个类定义的成分。首先,CObject被分为公有、保护和私有三个部分。CObject还提供了一般的和动态的类型检查以及串行化的功能。回忆一下,动态类型检查使你可以在运行时确定对象的类型。借助于永久性的概念,对象的状态可以被保存到存储介质中,比如磁盘。对象的永久性使对象成员函数也可以是永久的,允许对象数据的恢复。
子类从基类继承而来。例如,CGdiObject是一个从CObject类继承来的类。这儿是AFXWIN。H中找到的CGdiObject类定义。同样,为了清楚起见,对其作了一些改动。
//////////////////////
//CGdiObjet abstract class for CDC SelectObject
class CGdiObject:public CObject
DECLARE-DYNCREATE(CGdiObject)
public:
//Attributes
HGDIOBJ m-hObject;//must be first data member
operator HGDIOBJ()const;
static CGdiObject*PASCAL FromHandle(HGDIOBJ hObject);
static void PASCAL Delete TempMap();
BOOL Attach (HGDIOBJ hObject);
HGDIOBJ Detach();
//Constructors
CGdiobject();//must create a derived class object
BOOL DeleteObject();
//Operations
int GetObject (int nCount,LPVOID 1pObject)const;
UINT GetObjectType()const;
BOOL CreateStockObject(int nIndex);
BOOL UnrealizeObject();
BOOL operator==(const CGdiObject& obj)const;
BOOL operator!=(const CGdiObject& obj)const;
//Implementation
public:
virtual~CGdiObject();
#ifdef-DEBUG
virtual void Dump(CDumpContext& dc)const;
virtual void AssertValid()const;
#endif
;
CGdiObject和它的成员函数允许在Windows应用程序中创建并使用绘画对象,如自定义画笔、刷子和字体等。诸如CPen之类的类是进一步从CGdiObject类继承而来的。
Microsoft提供了MFC库的全部源代码,以尽可能地增加编程的灵活性。但是,对于初学者,没有必要去了解不同的类是如何定义的。
例如,在传统的C语言Windows应用程序中,DeleteObject()函数按下面的语法调用:
DeleteObject(hBRUSH);/*hBRUSH is the brush handle*/
在C++中,利用MFC库,可以按下面的语法访问类成员函数以实现同样的目的:
newbrush.DeleteObject();//new brush is current brush
正如你可以看到的,从C语言Windows函数调用转向类库对象是简单的。Microsoft在开发所有Windows类的时候都使用这种方法,使得从传统函数调用到继承类库对象的转移非常简单