第三章 DirectX SDK简介
第一节 关于DirectX SDK
Microsoft DirectX提供了一套非常优秀的应用程序接口,包含了设计高性能、实时应用程序的源代码。DirectX技术将帮助您建构下一代的电脑游戏和多媒体应用程序。它的内容包括了DirectDraw、DirectSound、DirectPlay、Direct3D和DirectInput等部分,它们分别主要应用在图形程序、声音程序等方面。
由于DirectX,使在Windows下运行应用程序的性能可以与在DOS或游戏平台下运行的应用程序性能相媲美,甚至超过它们。它将为您的Windows游戏开发提供一个具有鲁棒性的、标准化的操作环境。
DirectX包括两部分:运行期部分(Runtime)和SDK。在DirectX开发时,这两部分都要用到,但在DirectX应用程序运行时只用运行期部分。在Windows NT 4.0及以上版本中含有DirectX运行期部分,Win95则没有。但Win95可以很容易获得DirectX运行期部分。而Windows NT 4.0以前的版本不能运行DirectX程序。许多基于DirectX的应用程序和游戏都包含了DirectX运行期部分。
它目前有五个版本:1、2、3、5和6(没有版本4)。不同版本具有不同的运行期部分,但新版本的运行期部分可与旧版本的应用程序配合,即向上兼容。当前大部分流行的游戏都是基于版本5开发的。
第二节 DirectX5 SDK的获得
DirectX SDK包括开发DirectX应用程序所需要到的全部示例和帮助文件,但这些都是可
选资源,必须的文件是头文件(.h文件)和库文件(.lib文件)。获得DirectX SDK比获得运行期部分要困难一些。Windows NT 4.0和Win95都不带DirectX SDK,要获得SDK可通过以下3种办法:
* 购买Visual c++5.0(包括DirectX SDK)
* 访问Microsoft Web站点的DirectX下载页
* 成为MSDN(Microsoft开发网络)用户
SDK 也可以从Microsoft Web站点上获得,下载量很大,尤其是在拨号连接时,有可能需要一整夜的时间。
成为MSDN用户是获取SDK的好办法,除非您反对通过Microsoft付费的方式获得升级及其操作系统的程序开发特权。SDK由MSDN level 2及以上提供。
第三节 元件对象模型(COM)
DirectX根据Microsoft的COM(Component Object Model,即元件对象模型)规格得以实现。COM设计成用来提供完全便携的、安全的、可升级的软件结构。COM使用一个面向对象的模型,这要比像C++等语言所用的模型更严格。例如,COM对象经常通过成员函数进行存取,而且并不具备公共数据成员。COM对继承性的支持与C++相比也是有限的。
很多DirectX中API是作为COM对象的实例来创建的。您可以这样看:对象就象一个黑盒子,它代表了硬件,从而需要通过一个接口与应用程序进行联络。通过COM接口发送和接收的命令被称为“方式(method)”。例如,方式IDirectDraw2::GetDisplayMode是通过接口IDirectDraw2发送,从而能够从DirectDraw对象得到当前显示适配器的显示模式。
COM对象提供实际函数性,而COM接口则提供存取函数性的方法。COM对象不能被直接存取,相反,所有的存取者通过接口来完成。这条规则是如此强有力的得到遵守,以致于我们不能给任何COM对象以名称。我们只能给用来存取对象的接口名称。
所有的COM接口都是从IUnknown接口中衍生出来的。“I”标志经常用于命名COM接口(它代表Interface即接口)。
IUnknown接口提供3个成员函数,所有COM接口因而继承这些函数:
●AddRef():使对象内部引用值加一。例如,当您创建一个新的接口时,构造函数将自动调用AddRef()。
●QueryInterface():让COM对象就它们是否支持特定接口进行查询。例如,升级的COM对象提供附加的接口,而非现有接口的修改版。QueryInterface()函数可以用来确定旧的接口,以决定新的接口是否被支持。如果被查询的对象不支持有问题的接口,则更替接口的指针就返回。
●Release():使对象内部引用值减一。您应该在接口的指针将要超出范围时或者要通过使用接口指针来结束时使用这个函数。
对象的每一个DirectX接口都代表了一种设备,例如IDirectDraw2、IDirectSound和IDirectPlay。DirectX对象模型象征性地为每一个设备提供了一个主对象,其它所支持的功能对象由主对象衍生而来。例如,DirectDraw对象代表了显示适配器,您可以由它创建DirectDrawSurface对象来代表显存。同样地,DirectSound对象代表声卡并可以创建DirectSoundBuffer对象代表声卡上的声音资料。
在C程序中可以调用任意的COM接口方式。下面的例子创建一个平面,使用DirectDraw对象的IDirectDraw2::CreateSurface方式:
ret = lpDD->lpVtbl->CreateSurface (lpDD, &ddsd, &lpDDS,
NULL);
这里lpDD表示与新建平面相关的DirectDraw对象。该方式填充一个平面类型的结构(&ddsd)并返回一个指向平面的指针(&lpDDS)。
同样功能用C++的实现为:
ret = lpDD->CreateSurface(&ddsd, &lpDDS, NULL)
第四节 DirectDraw
DirectDraw是DirectX SDK中的一个组件,它允许用户直接地操作显存,支持硬件覆盖、内存与显存反转。DirectDraw在提供这些功能的同时并支持基于Microsoft Windows的应用程序和设备驱动。DirectDraw作为一种软件接口,支持Windows的GDI(图像设备接口),并提供直接操作显示设备的功能。它提供了与设备无关的游戏、Windows子系统等软件开发方式。
它还能够支持许多种显示硬件,包括从简单的SVGA显示器到高级硬件性能(支持图像剪切、延伸和非RGB颜色格式)。接口的设计使您的应用程序能够知道所使用硬件的特性,从而可以支持它们的硬件加速功能。
关于DirectDraw,在以后还将有更加详细的讲解。
第五节 DirectSound
Microsoft DirectSound的API是DirectX平台软件开发工具(SDK)程序员指南的声音组件。DirectSound提供了硬件加速、对声音设备的直接操作。在提供这些功能的同时,它还保持了与当前设备驱动的兼容性。
新版本的DirectSound能够在运行中查询硬件性能以确定最优的操作方式,还具有抓获声音、低延迟混音等功能特性。它的新功能还包括支持属性集(Porperty Sets),从而即使DirectSound不直接支持新硬件的特性,它也能够利用并受益。
DirectSound是通过“硬件抽象层(HAL)”来操作声音硬件的,这是一个由声音设备驱动来实现的接口。HAL提供了以下功能:
要求和释放对声音硬件的控制;
描述声音硬件的特性;
在硬件允许的条件下实现特定的操作;
在硬件不允许的时候报告操作失败。
DirectSound能够自动地利用硬件加速,包括硬件混音和硬件声音缓冲。您的应用程序无须查询硬件和程序特性,而可以在运行期部分查询DirectSound来获得对声音设备特性的充分描述,然后根据各个特性的存在或缺少来使用不同的方法进行优化。您还可以指定接受硬件加速的声音缓冲区。
下图显示了DirectSound与系统中其它声音组件的关系:
关于DirectSound,在以后还将有更加详细的讲解。
第六节 DirectPlay
DirectPlay的主要作用在于使应用程序间通讯方式得以简单化。DirectPlay的技术不仅提供了一个方案,实现了通讯与传送协议及在线服务的无关性,而且还实现了与传输服务器和游戏服务器的无关性。万一所基于的网络不支持某一方式,DirectPlay包含了必要的代码来仿效它。
DirectPlay的service provider结构使应用程序与网络隔绝开来。应用程序通过查询DirectPlay来获得所基于网络功能特性(例如延迟、带宽等),从而相应地调整通讯方式。下面的图显示了DirectPlay service provider的结构:
使用DirectPlay的第一步是选择使用哪一种service provider,它决定了将用于通讯的网络或协议。协议可以是遍布Internet的TCP/IP、局域网中的IPX或两台计算机间的连接电缆。
DirectPlay提供了两种很有用的连接管理方式:
IDirectPlay3::EnumConnections列举了应用程序可以获得的所有连接;
IDirectPlay3::InitializeConnection初始化一种特定的连接。
在确定了网络的连接后,为了方便理解下面的内容,先来看看这样几个概念:
首先从session讲起。DirectPlay session是若干台计算机之间的通讯通道。一个应用程序只有在成为了某个session的一部分后才能够开始与其它计算机进行通讯。有两种方法可以实现:列举一个网络中所有存在的session然后加入其中的一个;或新建一个session并等待其它计算机的加入。当应用程序成为session的一部分后,它就可以创建一个player并与session中其它的player相互传递信息。
每一个session中都有一台计算机作为主机。主机是session的所有者并拥有唯一的改动session属性的权力。
下图显示了一个session模型:
Session有两种类型:对等类型和客户/服务器类型。
Player是session中逻辑概念上的对象,能够发送和接收消息。在DirectPlay Session中没有对实体计算机的表示方法。Player可以作为一个本地player(在您自己的计算机上),或作为一个远程player(在其它计算机上)。只有在拥有了至少一个player后,计算机才能够发送和接收消息。每一台计算机可以拥有不止一个本地player。
Group是在逻辑概念上是一些player的集合。通过创建一个group,一个应用程序可以向它发送消息使其中所有的player都收到。DirectPlay提供了管理group和成员关系的方式。
下图显示了一个session内容的逻辑结构:
假如,一台计算机在完成了连接之后,就可以调用IDirectPlay3::EnumSessions列举所有可以得到的session。然后,IDirectPlay3::Open根据session cache中的内容加入一个session,或新建一个session。
接下来,可以创建player和group。而在游戏中,更多的是通过Message Managment中的各个方式进行频繁地、大量的消息传送。
在不同类型的session中,消息的流向是不同的。在对等类型中,消息的传送是各个player之间直接进行的。对于使用multicast向group中传送消息时,则要在group中指定一个multicast server,通过它再将消息传送给group中其它的计算机。
在客户/服务器类型的session中,所有的player只与服务器进行直接通讯,再由它与其它各个客户机进行消息传送。下图表示了这样的关系:
第七节 DirectInput
DirectInput用以支持包括鼠标、键盘和游戏杆等在内的输入设备,甚至还有力度反馈的高级输入/输出设备。它也是基于COM的。
DirectInput对象由输入设备提供数据。每一个相应设备都有所谓“对象实例”,即个别的控件,如按键、按纽和游戏杆的轴向等。键盘上的每一个按键都可以作为键盘对象的一个对象实例。即使程序在后台运行,DirectInput也能够对输入设备做出响应。
一般来说,先要创建一个DirectInput对象(通过DirectInputCreat方式),表示一个DirectInput子系统。然后,列举出系统中可以得到的输入设备,再为这些个别设备创建DirectInputDevice对象(通过IDirectInput::CreateDevice方式)。这种对象的方式用于获取关于设备的信息,设置它们的属性并从它们那里得到数据。
下面给出一个使用鼠标的例子。您可以在光盘Example目录下的Scrawl.c文件中找到这些代码。
首先创建DirectInput鼠标设备
// LPDIRECTINPUT g_pdi; // 已被初始化 LPDIRECTINPUTDEVICE g_pMouse; HRESULT hr; hr = g_pdi->CreateDevice(GUID_SysMouse, &g_pMouse, NULL); if (FAILED(hr)) { Complain(hwnd, hr, "CreateDevice(SysMouse)"); return FALSE; }
CreateDevice方式有三个参数:第一个是全局独有标志符GUID_SysMouse,这里表明为鼠标;第二个是IDirectInputDevice接口指针类型,如果这个调用成功,它就指向可用的鼠标;一般不使用COM聚合体,第三个参数为NULL。
然后,设置鼠标信息的格式:
hr = g_pMouse->SetDataFormat(&c_dfDIMouse); if (FAILED(hr)) { Complain(hwnd, hr, "SetDataFormat(SysMouse, dfDIMouse)"); return FALSE; }
设置鼠标行为:
hr = g_pMouse->SetCooperativeLevel(hwnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND); //后面的两个标志符实现了前台运行时对输入设备的独占 if (FAILED(hr)) { Complain(hwnd, hr, "SetCooperativeLevel(SysMouse)"); return FALSE; }
要使用事件通告(event notification)来发觉鼠标的动作并将经过缓冲的鼠标输入读进来,这都需要进行一些设置。首先,创建一个事件,将它与鼠标联系起来。当发生硬件中断时,就可以告诉鼠标设备对象有新的数据来到。
// HANDLE g_hevtMouse; // 全局变量 g_hevtMouse = CreateEvent(0, 0, 0, 0); if (g_hevtMouse == NULL) { Complain(hwnd, GetLastError(), "CreateEvent"); return FALSE; } hr = g_pMouse->SetEventNotification(g_hevtMouse); if (FAILED(hr)) { Complain(hwnd, hr, "SetEventNotification(SysMouse)"); return FALSE; }
现在您需要设定缓冲区大小。在此之前要初始化一个DIPROPDWORD结构,这当中很多数量是无关紧要的,关键是最后一个,dwData,它决定了缓冲区中可容纳项目的数量。
#define DINPUT_BUFFERSIZE 16 DIPROPDWORD dipdw = { // the header { sizeof(DIPROPDWORD), // diph.dwSize sizeof(DIPROPHEADER), // diph.dwHeaderSize 0, // diph.dwObj DIPH_DEVICE, // diph.dwHow }, // the data DINPUT_BUFFERSIZE, // dwData };
接着按照您希望改变的属性的标志符,将头地址传送给IDirectInputDevice::SetProperty方式:
hr = g_pMouse->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph); if (FAILED(hr)) { Complain(hwnd, hr, "Set buffer size(SysMouse)"); return FALSE; }
至此,设置结束。然后开始根据程序的要求操作鼠标。
第八节 Direct3D
Direct3D提供了与硬件无关的的3D视频硬件操作方式。您可以在两种模式(立即模式和保留模式)中选择其中之一使用。
立即模式较保留模式是一个低层的3D API,后者建构于前者之上。立即模式适合于开发高品质的多媒体应用程序,它在底层与加速硬件进行通讯。保留模式应用于对3D场景进行实时的操纵。
对3D场景的操作是基于矩阵和多边形的运算,这是创建和管理3D的基础知识,在很多资料中均可获得。
使用立即模式可以选择通过调用DrawPrimitive方式或利用执行缓冲区。对于初学者一般从调用DrawPrimitive方式。不过,千万不要认为两者有优劣之分。在熟悉了Direct3D后,使用哪个方法取决于您应用程序的要求。前者的方法与其它COM雷同;在利用执行缓冲区的方法时,要创建DirectDraw和Direct3D对象,设置渲染状态,填充执行缓冲区等。
无论哪种模式,应用程序与硬件的通讯都很类似。正如下图所示。由于Direct3D相当于DirectDraw对象的一个接口,这里的HAL被表示为DirectDraw/Direct3D HAL。
对保留模式的操作是通过使用一些对象来实现的。Direct3D和DirectDraw是紧密联系在一起的。一个DirectDraw对象将DirectDraw和Direct3D状态封装起来,通过IDirectDraw::QueryInterface方式将IDirect3D接口转换为DirectDraw对象。
有一个很重要的概念是z缓冲区。它决定了将很多显示内容如何覆盖和裁剪。如果没有z缓冲区,保留模式无法给覆盖层排序。没有指定z顺序的覆盖层被缺省设定为0,处于最底层。一共可以有四十亿个覆盖层(应该够用了吧!),z顺序为2的层将可能遮掩了z顺序为1的层的某些内容。记住,不能有两个层的z顺序相同。
关于3D场景,还有诸如材资、光源等概念,在此不再一一聱述。
第九节 Vc++中引入Direct SDK
一旦安装了SDK,就得马上通知Visual C++ SDK的位置。默认状态下,SDK安装在dxsdk目录下。头文件放在dxsdk/inc目录下,库文件放在dxsdk/lib目录下。
可利用下述两种方法之一通知visual C++ SDK的位置。一种方法是在使用文件时给出完整的文件路径;另一种法是将这些目录加到Visual C++的搜索路径中。第二种方法更好一些,可以通过Tools[Options]Directories对话框实现。
增加dxsdk/lib目录的方法大体上同增加dxsdk/inc目录的方法相同。
如果你获得了一个含有比Visual C++的DirectX SDK新的版本,您需要将DirectX SDK目录置于常规的Visual C++目录上面。否则就得使用旧版本。(Visual C++从上至下查找目录)
根据我们已经讨论过的内容,你应该能够编辑DirectX程序了。然而还有最后一个潜在的障碍。除非INITGUID符号已经被定义,否则在DirectX GUIDs下调用Query-Interface()函数的程序同DirectX2 SDK的链接会失败。INITGUID符号只能由一个源文件定义,并且必须出现在#include语句之前,如下列代码所示:
#define INITGUID #include〈ddraw.h〉 //…other includes…
对于DirectX3及以上版本,这种解决方法都是有效的,但还有一个更好的方法,即将dxguid.lib文件链接到你的工程上(Build[Settings]Link对话框),以替代INITGUID符号的定义。
此最后两个参数的内容依赖于消息的类型。
产生消息
消息传送概念使Windows能够实现多任务。消息有四个基本来源。应用程序可以从用户那儿接受消息,也可以是Windows本身,应用程序本身或者是其它应用程序。
用户消息包括按键消息、鼠标移动、鼠标指点或单击、菜单选择、滚动条的定位等。应用程序必须花费大量的时间来处理用户消息。用户产生的消息表明运行程序的人希望改变应用程序的表现方式。
无论何时,如果状态发生改变,将会有一个消息被发往应用程序。一个例子是用户单击了应用程序的图标,表明他们想要将此应用程序变为活动的应用程序。在这种情况下,Windows告诉应用程序它的主窗口被打开了,它的大小和位置被改变了等等Windows产生的消息可以被处理,也可以被忽略,这跟应用程序当前的状态有关。
相应消息
在传统的面向过程的C语言Windows应用程序中,对于遇到的每一种消息,它都有一个相应的过程来处理这消息。不同的窗口对相同的消息会产生不同的响应。Windows把每个消息发送到应用程序的不同窗口,而不同的窗口对相同的消息会有不同解释。不令应用程序需要不同的过程来处理每一种消息,每一个窗口也应有不同的过程来处理不同的消息。窗口过程集合了应用程序的所有消息处理过程。
消息循环
所有Windows应用程序的一个基本组成就是消息处理循环。每一个C应用程序都在内部执行这个操作。C应用程序包含了创建并初始化窗口的过程,随后是消息处理循环,最后是结束应用程序所需的一些代码。消息循环负责处理Windows发给主程序的消息。在这儿,程序知道有了消息,并且要求Windows将消息发送到合适的窗口过程以供处理。当消息被接受时,窗口过程就执行希望的动作。
第八节 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类的时候都使用这种方法,使得从传统函数调用到继承类库对象的转移非常简单。
第二章 windows编程基础
第一节 引言
为了跟上潮流,我们抛弃了已快被淘汰的DOS操作系统,所有的讲解和例程都是基于微软的Windows操作系统的。考虑到很多的用户并没有Windows编程基础,所以我们设置了这一专门讲述、讨论Windows的术语、概念的部分,以使这部分用户能较快地理解和掌握我们所讲述、讨论的编程思想和编程方法。这一部分中主要讲述的是Windows中十分基本的东西,所以用户应根据自己的情况有选择的进行学习。好!现在就让我们进入艰苦而又精彩有趣的游戏编程之路吧!
第二节 windows的介绍
Windows应用程序可以采用面向过程的实现方法。也可以使用面向对象的结构。所有的实现方法都集成了点击控制和弹出菜单,能够运行特别为Windows编写的应用程序。
Windows是一种基于图形界面的多任务操作系统。为这个环境开发的程序(那些专门为Windows设计的)有着相同的外观和命令结构。对用户来说,这使得学习使用Windows应用程序变得容易了。为了帮助开发Windows应用程序,Windows提供了大量的内建函数以方便地使用弹出菜单、滚动条、对话框、图标和其他一些友好的用户界面应该具有的特性。
Windows运行应用程序以硬件无关的方式来处理视频显示、键盘、鼠标、打印机、串行口以及系统时钟。
最值得注意的Windows特性就是其标准化的图形用户界面。统一的界面使用图片或图标来代表磁盘驱动器、文件、子目录以及其它操作系统的命令和动作。 统一的用户界面也为程序员带来了好处。例如,你可以很方便地使用常见菜单和对话框的内建函数。所有的菜单都具有相同风格的键盘和鼠标接口,因为是Windows而不是程序员在实现它。
Windows的多任务环境允许用户在同一时刻运行多个应用程序或同一个应用程序的多个实例。一个应用程序可能处于激活状态。激活的应用程序是指它正接收用户的输入。因为每一个瞬间仅有一个程序能够被处理,因此同一时间也只能有一个应用程序处于激活状态。但是,可以有任意个数的并行运行的任务。
第三节 windows的基本概念
Windows消息和面向对象编程
Windows实现了一种仿OOP(面向对象编程)环境。Windows下的消息系统负责在多任务环境中分解信息。从应用程序的角度来看,消息是关于发生的事件的通知。用户可以通过按下或移动鼠标来产生这些事件,也可以是通过改变窗口大小或选择一个菜单项等。这些事件也可以由应用程序本身产生。Windows本身也能产生消息。如“关闭Windows”消息,Windows通过这个消息来通知所有的应用程序,Windows将被关闭。
内存管理
在Windows系统中系统内存是最重要的共享资源之一。当同一时刻有多个应用程序在运行时,为了不耗尽系统资源,每个应用程序必须合作以共享内存。同时,当启动新的程序和关闭老的程序时,内存会变得碎片化。通过移动内存中的代码和数据块,Windows能够使内存空闲空间连起来。在Windows下也有可能超量使用内存。例如,应用程序可以比内存容量大。Windows能够废弃当前不使用的代码,在以后需要时再从应用程序中将之读入内存。Windows应用程序可以共享可执行文件中的例程。包含可共享的例程的文件称为动态链接库(DLL)。Windows包括了运行时将DLL例程链入程序的机制。
硬件无关性
Windows同时提供了硬件或设备无关性,使你免于在生成程序的时候不得不考虑所有可能使用的显示器、打印机或输入设备。在Windows下面,每种硬件设备的驱动程序只编写一次。硬件无关性使编程对应用程序开发者来说更为简单。应用程序与Windows而不是各种设备打交道。
动态键接库
动态键接库提供了更多的Windows功能。它们通过一个有力而灵活的图形用户界面增强了基本的操作系统。动态键接库包括一些预定义的函数,它们可以在一个应用程序被调入时与之键接(动态地),而不是在应用程序被创建时(静态地)。动态键接库使用DLL后缀。函数库将每一个程序员从重复开发诸如读取字符或格式化输出之类的通用例程中解放出来。程序员可以方便地构造它们自己的库以包含更多的功能,比如改变字体或检验文本。把函数变为通用工具减少了冗余设计,这是OOP的一个关键特性。
Windows的库是被动态地键接的。或者说,键接器并不把函数拷贝到程序的可执行文件中去。相反,当程序运行时,它产生对库函数的调用。自然,这样做节约了内存。不管有多少应用程序在运行,在RAM中总是只有库的一份考贝,而这个库可以被共享。
Windows的可执行文件格式
Windows具有一种新的可执行文件的格式,称为New Excutable格式。它包括新型的文件头,能够保存有关DLL函数的信息。
第四节 windows的窗口
Windows的窗口
窗口看起来就是显示设备中的一个矩形区域,它的外观与特定的应用程序无关,可是,对于一个应用程序来说,窗口是屏幕上应用程序能够直接控制的矩形区域。应用程序能够创建并控制主窗口的一切,如大小和形状。当用户启动一个程序时,一个窗口就被创建了。用户每次单击窗口,应用程序作出响应。关闭一个窗口会使应用程序结束。多窗口带给用户Windows的多任务能力。通过将屏幕分为不同的窗口,用户能够使用键盘或鼠标选择一个并行运行的应用程序,以此对多任务环境中的一个特定程序进行输入,Windows截取了用户的输入并分配必要的资源(例如微处理器)。
Windows的布局
所有的Windows应用程序都具有诸如边框、控制菜单、About对话框之类的共同特征。这些特征使得各个Windows应用程序非常类似。
边框
Windows的窗口被边框所包围。边框由围出窗口的线条组成。对于新手而言,边框看起来仅仅是为了将一个应用程序的屏幕视口与其它的区别开。但是,对于熟练者,边框有着不同的作用。例如,如果将鼠标指针放在边框上并按下鼠标的左键,用户就可以改变窗口的大小。
标题条
应用程序的名字显示在窗口顶部的标题条中。标题条总是在相关窗口顶部的中央。标题条非常有用,它可以帮助你记住正在运行哪个应用程序。活动应用的标题条以不同于非活动应用程序的颜色显示。
控制图标
控制图标是每个窗口左上方的小图片,每个应用程序都使用它。在控制图标上单击鼠标键会使Windows显示系统菜单。
系统菜单
当用鼠标单击控制图标时就打开了控制菜单。它提供了诸如Restore,Move,Size,Minimize,Maximize以及Close这样的标准操作。
最小化图标
每个Windows 95或Windows NT应用程序都在窗口的右上角显示三个图标。最左边的图标是一段短下划线,这就是最小化图标。它可以使用程序被最小化。
最大化图标
最大化图标是三个图标中中间的那一个,看起来象两个小窗口。使用最大化图标可以使用应用程序占满整个屏幕。如果选择了这个图标,其它应用程序窗口都会被盖住。
垂直滚动条
如果有必要,应用程序可以显示一个垂直滚动条。垂直流动条显示在应用程序窗口的右边,在两端有两个方向相反的箭头。它还有一个着色的棒和一个透明的窗口块。后者被用于显示当前显示内容与整个文档(着色的棒)的关系。你可以用滚动条来选择显示哪一页。一般在任何一个箭头上单击一下会使显示内容移动一行。单击向上箭头下方的窗口块并拖动它会使屏幕输出快速更新到应用程序屏幕输出的任意位置。
水平滚动条
也可以显示一个水平滚动条。水平滚动条显示在窗口的底部,具有与垂直滚动条类似的功能。你用它来选择要显示哪些列。一般在任何一个箭头上单击一个会使显示内容移动一列。单击向左箭头右边的窗口块并拖动它会使屏幕输出快速更新到应用程序屏幕输出的任意位置。
菜单条
一个可选择的菜单条可以显示在标题条的下方。通过菜单条来选择菜单和子菜单。这种选择可以通过用鼠标单击,也可以用热键组合来实现。热键组合经常是ALT与命令中带下划线的字母的组合,比如File命令中的“F”。
用户区
通常用户区占据了窗口最大的部分。这是应用程序的基本输出区域。应当由应用程序来复杂管理用户区。另外,应用程序可以输出到用户区。
第五节 windows的类
窗口的基本组件有助于说明应用程序的外观。有的时候应用程序需要创建两个外观和表现都相似的窗口。Windows的Paint就是一个例子。借助于同时运行Paint的两个实例(或拷贝),Paint允许用户剪贴或拷贝图片的一部分。然后信息就可以从一个实例拷贝到另一个实例。Paint的每个运行实例的外观和表现都与其他的相同。这就需要每个实例创建自己的外观和功能类似的窗口。
在这种情况下被创建的外观和功能都很类似的窗口被称为是属于同一个窗口类的。但是,你创建的窗口可以有不同的特征。它们可以有不同的大小,不同的位置,不同的颜色或不同的标题,也可以使用不同的光标。
每个被创建的窗都基于一个窗口类。在用C语言开发撕于的基于传统的函数调用方式的应用程序中,一些窗口为在Windows应用程序初始化的进修注册。你的应用程序可以注册属于自己的窗口类。为了能够使几个窗口在同一个窗口类的基础上创建,Windows定义了一些窗口特征,如CreateWindows()的参数,另一些定义的窗口类的结构。当你注册一个窗口类的时候,这个类可以被Windows下运行着的任何程序所使用。对于使用MFC的应用程序来说,多数注册工作已经由预定义的对象完成了。
具有相似的外观和表现的窗口可以被组合成一个类,以此来减少需要维护的信息。因为每个窗口类都有自己的可共享的类结构,不需要复制不必要的窗口类参数。同时,同类的两个窗口使用相同的函数以及相关的例程。这样可以节省时间和空间,因为不存在代码复制。
第六节 windows中的面向对象编程
在Windows下传统的C程序吸收了一些面向对象编程的特性。对象是一种包含数据结构和对这些数据结构进行操作的函数的抽象数据类型。而且,对象接收会引起它们不同动作的消息。
比如,一个Windows的图形对象是可以被作为一个实体来操纵的一些数据的集合,对于用户它是可视界面的一部分。特别地,一个对象意味这数据和数据的功能。菜单、标题条、控制块以及滚动条等都是图形对象的例子。下一部分描述一些影响应用程序外观的新的图形对象。
图标
图标是用来使用记住特定操作、想法或产品的小图形对象。比如,一个电子表格程序被最小化时可以显示一个很小的柱状图以提醒用户这个程序还在运行之中。在柱状图上双击鼠标会使Windows激活这个应用程序。图标是非常有力的工具。它很适合用来引起用户的注意,比如在发出错误警告或者是向用户提供选择时。
光标
光标是Windows用来跟踪指点设备的运动的图形符号。这种图形符号可以改变形状以指明特定的Windows操作。比如,当标准的箭头光标变为沙漏光标时说明Windows正在执行一个命令,需要暂停。
编辑光标
应用程序在窗口中显示编辑光标以告诉用户在哪儿输入。编辑光标与其他屏幕符号显然不同,因为它是闪烁的。多数时候,鼠标输入与光标相连,而键盘输入与编辑光标相连。但是,可以用鼠标来改变编辑光标的输入点。
消息框
消息框是另一类Windows图形对象。消息框是一种包含标题、图标和消息的弹出式窗口。图(?)是关闭Windows Notepad程序时出现的一个标准的消息框。
-------------------------------------------------------------------------
| |
------------------------------------------------------------------------
Windows的对话框
对话框与消息框相似的地方在于它也是一种弹出式窗口。但是对话框主要用于接受用户输入而不仅仅是显示一些输出。对话框允许应用程序接受输入,每次一个域或是一个框的内容,而不是每次一个字符。图(?)显示了一个典型的Windows对话框。对知框的图形设计由Windows为你自动完成。对话框的布局通常用编译器中的资源编辑器完成。
-----------------------------------------------------------------------
| |
-----------------------------------------------------------------------
字体
字体是一种图形对象或资源,它定义了完整的字符集合的字样。这些字符都有一个特定的大小和风格,可以使文本具有不同的外观。字样是字符的一种基本属性,它定义了字符的衬线和笔画宽度。
位图
位图是一种显示图片(按像素组织),存储于内存。当应用程序需要快速显示图片时可以使用位图。因为位图直接从内存中传送,所以它比用程序重新画出图片要快得多。位图有两个基本用途。首先,它可以在屏幕上显示图片。其次位图也用于创建刷子。刷子使你可以在屏幕上画出并填充对象。
使用位图有两个缺点。首先,与其尺寸有关,位图会占据难以预估的大量内存。每个被显示的像素都要在内存中占据相应的空间。在彩色显示器上显示一个像素会比在单色显示器上占据更多的内存。在单色显示器上,只需一位(bit)就可以表示出像素的状态。可是在可以显示16种颜色的彩色显示器上,需要四位才能表示一个像素的特征。同样地,随着显示设备分辨率的增加,位图对内存的需求也增加了。位图的另一个缺点是它只包括静态的图片。比如,如果用位图来代表一辆汽车,就没有办法来访问图片的不同部分,如轮踏、顶盖、窗等。但是,如果汽车是有一系列基本绘图例程来生成的,应用程序就可以改变向这些例程传送的数据从而改变图片的不同部分。例如,应用程序可以修饰顶蓬线并把一辆轿车变为敞蓬车。
画笔
当Windows在屏幕上显示一个图形时,它使用当前画笔的信息。画笔用于画出线条或轮廊。画笔具有三个基本特征:线宽、线型(虚线、短线、实线)以及颜色。Windows永远保留着用于画白线和黑线的画笔,任何应用程序可以使用它。你也可以创建自己的画笔。
刷子
Windows用刷子来画出颜色并以预定义的样式来填充一个区域。刷子至少有8×8个像素大小。刷子有三个基本特征:样式和颜色。由于它们至少有8×8的大小,刷子被称作具有样式而不象画笔,称为线型。样式可以是纯的颜色,也可以是阴影线、斜线或其它用户自定义的组合
第七节 windows的消息
Windows的消息
在Windows中,应用程序并不直接写屏幕、处理硬件中断或直接对打印机输出。相反,应用程序使用合适的Windows函数或者等待一个适当的消息被发出。
Windows消息系统负责在多任务环境中分派消息。从应用程序的角度来看,消息可以看作是发生的事件的通知,有些需要作出特定的反应,有些就不需要。这些事件可能由用户产生,比如按下了鼠标或移动了鼠标,改变了窗口的大小或者选择了一个菜单。同时,这些事件也可能由应用程序本身所产生。
这个过程使你的应用程序必须完全面向消息处理。当接收到消息时,应用程序必须能激活并决定正确的动作,完成这个动作之后回到等待状态。
通过检查消息的格式和来源,下一部分将更仔细地讨论消息系统。
消息的格式
消息通知一个应用程序发生了一个事件。从技术上来讲,消息不仅仅是与应用程序相关,而且是与应用程序的某一特定窗口有关。因此,所有的消息都被发往窗口。
在Windows下只有一个消息系统-即系统消息队列。但是,每个正在Windows下运行的应用程序都有它自己的消息队列。系统消息队列中的每个消息最终都要被USER模块传送到应用程序的消息队列中去。应用程序的消息队列中存储了程序的所有窗口的全部消息。
不管消息具有什么类型,它们都有四个参数:一个窗口句柄,一个消息类型,两个附加的32位参数。窗口消息中定义的第一个参数是消息所关联的窗口句柄。
在编写Windows应用程序的时候经常使用句柄。句柄是一个唯一的数字,它被用于标识许多类型的对象,如菜单、图标、画笔和刷子、内存分配、输出设备甚至窗口实例。在Windows 95和Windows NT下面,程序的每个运行着的拷贝叫做实例。
因为Windows 95和Windows NT允许你同时运行一个程序的多个实例,操作系统就有必要保持对这些实例的追踪。这是通过赋予每个运行实例一个唯一的实例句柄来实现的。
实例句柄通常被用作一个内部维护着的表的索引。通过引用表中的元素而不是实际的内存地址,Windows 95和Windows NT可以动态地调整所有的资源,而只需在此资源所对应的表格位置中插入一个新的地址。
根据一个应用程序的多个实例被处理的方式,内存资源由Windows 95和Windows NT保存。
应用程序的实例具有很重要的作用。应用程序的实例定义了程序的函数所需的所有对象。这包括控件、菜单、对话框以及更多的新Windows类。
消息中的第二个参数是消息类型。这是在Windows独有的一些头文件中定义的标识符。这些头文件可以通过WINDOWS.H来使用。在Windows下,每个消息由两个字符的助记符开始,跟着是下划线,最后是一个描述符。
最后的两个参数提供了解释消息所需的附加信息。因此最后两个参数的内容依赖于消息的类型。
产生消息
消息传送概念使Windows能够实现多任务。消息有四个基本来源。应用程序可以从用户那儿接受消息,也可以是Windows本身,应用程序本身或者是其它应用程序。
用户消息包括按键消息、鼠标移动、鼠标指点或单击、菜单选择、滚动条的定位等。应用程序必须花费大量的时间来处理用户消息。用户产生的消息表明运行程序的人希望改变应用程序的表现方式。
无论何时,如果状态发生改变,将会有一个消息被发往应用程序。一个例子是用户单击了应用程序的图标,表明他们想要将此应用程序变为活动的应用程序。在这种情况下,Windows告诉应用程序它的主窗口被打开了,它的大小和位置被改变了等等Windows产生的消息可以被处理,也可以被忽略,这跟应用程序当前的状态有关。
相应消息
在传统的面向过程的C语言Windows应用程序中,对于遇到的每一种消息,它都有一个相应的过程来处理这消息。不同的窗口对相同的消息会产生不同的响应。Windows把每个消息发送到应用程序的不同窗口,而不同的窗口对相同的消息会有不同解释。不令应用程序需要不同的过程来处理每一种消息,每一个窗口也应有不同的过程来处理不同的消息。窗口过程集合了应用程序的所有消息处理过程。
消息循环
所有Windows应用程序的一个基本组成就是消息处理循环。每一个C应用程序都在内部执行这个操作。C应用程序包含了创建并初始化窗口的过程,随后是消息处理循环,最后是结束应用程序所需的一些代码。消息循环负责处理Windows发给主程序的消息。在这儿,程序知道有了消息,并且要求Windows将消息发送到合适的窗口过程以供处理。当消息被接受时,窗口过程就执行希望的动作。
第八节 windows的函数
Windows向应用程序开发人员提供了数以百计的函数。这些函数的例子包括DispatchMes-sage(),PostMessage(),RegisterWindowMessage()以及SetActiveWindow()。对于使用基础类库的C++程序员,许多函数自动被运行。
在16位的Windows 3.x下的函数声明包括一个pascal修饰符,这在DOS下更为有效Windows95和Windows NT下的32位应用程序不再使用这个修饰符。如你所知,所有Windows函数的参数是通过系统来传递的。函数的参数从最右边的参数开始向左压入栈,这是标准的C方式。在从函数返回之前,调用过程必须按原来压入栈的字节数调整栈指针。
第九节 windows应用程序框架
Windows头文件:WINDOWS.H
WINWS.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语言的 } 鄾{ F:\游戏资料集合\游戏核心编程\Chapter3.txt F:\游戏资料集合\游戏核心编程\.Chapter3.txt.map F:\游戏资料集合\游戏核心编程\.Chapter3.txt.blk 鄍m ㄡg T! ? ㄡg h ㄡg ㄡg ` ㄡg 悪 ?H v ?p } `
第三章 DirectX SDK简介
第一节 关于DirectX SDK
Microsoft DirectX提供了一套非常优秀的应用程序接口,包含了设计高性能、实时应用程序的源代码。DirectX技术将帮助您建构下一代的电脑游戏和多媒体应用程序。它的内容包括了DirectDraw、DirectSound、DirectPlay、Direct3D和DirectInput等部分,它们分别主要应用在图形程序、声音程序等方面。
由于DirectX,使在Windows下运行应用程序的性能可以与在DOS或游戏平台下运行的应用程序性能相媲美,甚至超过它们。它将为您的Windows游戏开发提供一个具有鲁棒性的、标准化的操作环境。
DirectX包括两部分:运行期部分(Runtime)和SDK。在DirectX开发时,这两部分都要用到,但在DirectX应用程序运行时只用运行期部分。在Windows NT 4.0及以上版本中含有DirectX运行期部分,Win95则没有。但Win95可以很容易获得DirectX运行期部分。而Windows NT 4.0以前的版本不能运行DirectX程序。许多基于DirectX的应用程序和游戏都包含了DirectX运行期部分。
它目前有五个版本:1、2、3、5和6(没有版本4)。不同版本具有不同的运行期部分,但新版本的运行期部分可与旧版本的应用程序配合,即向上兼容。当前大部分流行的游戏都是基于版本5开发的。
第二节 DirectX5 SDK的获得
DirectX SDK包括开发DirectX应用程序所需要到的全部示例和帮助文件,但这些都是可
选资源,必须的文件是头文件(.h文件)和库文件(.lib文件)。获得DirectX SDK比获得运行期部分要困难一些。Windows NT 4.0和Win95都不带DirectX SDK,要获得SDK可通过以下3种办法:
* 购买Visual c++5.0(包括DirectX SDK)
* 访问Microsoft Web站点的DirectX下载页
* 成为MSDN(Microsoft开发网络)用户
SDK 也可以从Microsoft Web站点上获得,下载量很大,尤其是在拨号连接时,有可能需要一整夜的时间。
成为MSDN用户是获取SDK的好办法,除非您反对通过Microsoft付费的方式获得升级及其操作系统的程序开发特权。SDK由MSDN level 2及以上提供。
第三节 元件对象模型(COM)
DirectX根据Microsoft的COM(Component Object Model,即元件对象模型)规格得以实现。COM设计成用来提供完全便携的、安全的、可升级的软件结构。COM使用一个面向对象的模型,这要比像C++等语言所用的模型更严格。例如,COM对象经常通过成员函数进行存取,而且并不具备公共数据成员。COM对继承性的支持与C++相比也是有限的。
很多DirectX中API是作为COM对象的实例来创建的。您可以这样看:对象就象一个黑盒子,它代表了硬件,从而需要通过一个接口与应用程序进行联络。通过COM接口发送和接收的命令被称为“方式(method)”。例如,方式IDirectDraw2::GetDisplayMode是通过接口IDirectDraw2发送,从而能够从DirectDraw对象得到当前显示适配器的显示模式。
COM对象提供实际函数性,而COM接口则提供存取函数性的方法。COM对象不能被直接存取,相反,所有的存取者通过接口来完成。这条规则是如此强有力的得到遵守,以致于我们不能给任何COM对象以名称。我们只能给用来存取对象的接口名称。
所有的COM接口都是从IUnknown接口中衍生出来的。“I”标志经常用于命名COM接口(它代表Interface即接口)。
IUnknown接口提供3个成员函数,所有COM接口因而继承这些函数:
●AddRef():使对象内部引用值加一。例如,当您创建一个新的接口时,构造函数将自动调用AddRef()。
●QueryInterface():让COM对象就它们是否支持特定接口进行查询。例如,升级的COM对象提供附加的接口,而非现有接口的修改版。QueryInterface()函数可以用来确定旧的接口,以决定新的接口是否被支持。如果被查询的对象不支持有问题的接口,则更替接口的指针就返回。
●Release():使对象内部引用值减一。您应该在接口的指针将要超出范围时或者要通过使用接口指针来结束时使用这个函数。
对象的每一个DirectX接口都代表了一种设备,例如IDirectDraw2、IDirectSound和IDirectPlay。DirectX对象模型象征性地为每一个设备提供了一个主对象,其它所支持的功能对象由主对象衍生而来。例如,DirectDraw对象代表了显示适配器,您可以由它创建DirectDrawSurface对象来代表显存。同样地,DirectSound对象代表声卡并可以创建DirectSoundBuffer对象代表声卡上的声音资料。
在C程序中可以调用任意的COM接口方式。下面的例子创建一个平面,使用DirectDraw对象的IDirectDraw2::CreateSurface方式:
ret = lpDD->lpVtbl->CreateSurface (lpDD, &ddsd, &lpDDS,
NULL);
这里lpDD表示与新建平面相关的DirectDraw对象。该方式填充一个平面类型的结构(&ddsd)并返回一个指向平面的指针(&lpDDS)。
同样功能用C++的实现为:
ret = lpDD->CreateSurface(&ddsd, &lpDDS, NULL)
第四节 DirectDraw
DirectDraw是DirectX SDK中的一个组件,它允许用户直接地操作显存,支持硬件覆盖、内存与显存反转。DirectDraw在提供这些功能的同时并支持基于Microsoft Windows的应用程序和设备驱动。DirectDraw作为一种软件接口,支持Windows的GDI(图像设备接口),并提供直接操作显示设备的功能。它提供了与设备无关的游戏、Windows子系统等软件开发方式。
它还能够支持许多种显示硬件,包括从简单的SVGA显示器到高级硬件性能(支持图像剪切、延伸和非RGB颜色格式)。接口的设计使您的应用程序能够知道所使用硬件的特性,从而可以支持它们的硬件加速功能。
关于DirectDraw,在以后还将有更加详细的讲解。
第五节 DirectSound
Microsoft DirectSound的API是DirectX平台软件开发工具(SDK)程序员指南的声音组件。DirectSound提供了硬件加速、对声音设备的直接操作。在提供这些功能的同时,它还保持了与当前设备驱动的兼容性。
新版本的DirectSound能够在运行中查询硬件性能以确定最优的操作方式,还具有抓获声音、低延迟混音等功能特性。它的新功能还包括支持属性集(Porperty Sets),从而即使DirectSound不直接支持新硬件的特性,它也能够利用并受益。
DirectSound是通过“硬件抽象层(HAL)”来操作声音硬件的,这是一个由声音设备驱动来实现的接口。HAL提供了以下功能:
要求和释放对声音硬件的控制;
描述声音硬件的特性;
在硬件允许的条件下实现特定的操作;
在硬件不允许的时候报告操作失败。
DirectSound能够自动地利用硬件加速,包括硬件混音和硬件声音缓冲。您的应用程序无须查询硬件和程序特性,而可以在运行期部分查询DirectSound来获得对声音设备特性的充分描述,然后根据各个特性的存在或缺少来使用不同的方法进行优化。您还可以指定接受硬件加速的声音缓冲区。
下图显示了DirectSound与系统中其它声音组件的关系:
关于DirectSound,在以后还将有更加详细的讲解。
第六节 DirectPlay
DirectPlay的主要作用在于使应用程序间通讯方式得以简单化。DirectPlay的技术不仅提供了一个方案,实现了通讯与传送协议及在线服务的无关性,而且还实现了与传输服务器和游戏服务器的无关性。万一所基于的网络不支持某一方式,DirectPlay包含了必要的代码来仿效它。
DirectPlay的service provider结构使应用程序与网络隔绝开来。应用程序通过查询DirectPlay来获得所基于网络功能特性(例如延迟、带宽等),从而相应地调整通讯方式。下面的图显示了DirectPlay service provider的结构:
使用DirectPlay的第一步是选择使用哪一种service provider,它决定了将用于通讯的网络或协议。协议可以是遍布Internet的TCP/IP、局域网中的IPX或两台计算机间的连接电缆。
DirectPlay提供了两种很有用的连接管理方式:
IDirectPlay3::EnumConnections列举了应用程序可以获得的所有连接;
IDirectPlay3::InitializeConnection初始化一种特定的连接。
在确定了网络的连接后,为了方便理解下面的内容,先来看看这样几个概念:
首先从session讲起。DirectPlay session是若干台计算机之间的通讯通道。一个应用程序只有在成为了某个session的一部分后才能够开始与其它计算机进行通讯。有两种方法可以实现:列举一个网络中