|
楼主 |
发表于 2005-7-22 15:12:44
|
显示全部楼层
☆ DirectDraw概述
在你的程序中使用DirectDraw,你至少要做四件事,它们是:
1、建立一个DirectDraw对象。
2、设置协作等级。
3、设置显示模式和色彩深度(全屏模式)。
4、至少创建一个DirectDraw表面。
在讲怎样完成以上步骤之前,先让我们了解一下每一步的含义。第一个,要创建一个DirectDraw对象,这意味着我们要建立一个指向IDirectDraw7接口的指针。这很简单,不是吗?有三种办法可以实现它,你可以直接使用COM,或使用DirectDraw的两个函数之一。三种办法各有千秋,我们过一会儿将详细介绍。第二个,设置操作等级。这可能对你来说比较新鲜。协作是由于Windows是一个多任务操作系统而产生的概念。意思是所有运行的程序都要随时告知Windows它们将要或正在使用的资源,这将保证你所要使用的资源不会被windows再分配给别的应用程序。不用担心,有一个很简单的函数完成它。
第三个你是否有点熟悉?如果你要写一个全屏的程序,通常是游戏程序,你需要设置显示模式和色彩深度。在Windows的应用程序里做这些通常不是一个好主意,因为它能导致其它程序的同时运行出现问题。当你结束自己的程序时,你当然要恢复到改变前的状态。设置全屏模式,只是调用一个单独的DirectDraw的函数,程序结束后,要恢复原来的状态。
最后,也是最重要的,是DirectDraw表面的概念。操纵表面是DirectDraw的全部。简单的说,DirectDraw表面是一个用于存储图象数据的线性内存区域。DirectDraw表面的大小就是以象素为单位,用宽和高来定义。所以你可以认为表面是一个用来画图的矩形区域。它有自己的接口,称作IDirectDrawSurface7。有三种主要的表面,我们将在本章和下一章分别用到它们。
· 主表面:每一个DirectDraw应用程序都有一个主表面。主表面就相当于用户的显示器。它的内容是可见的。同理,主表面就是根据显示器的显示模式设定宽和高。
· 后缓冲区:后缓冲区是紧随主表面的表面,但它不可见。它是动画没有闪烁的主因,通常,你在后缓冲区画好每一帧,然后把后缓冲区的内容拷贝到主表面,使它显示出来。由于它紧随着主表面,所以它的大小同主表面相同。(你就理解为楼上和楼下的关系)
· 离屏缓冲区:它很象后缓冲区,只是它不是紧挨着主表面。尽管你可以用它作任何事,但它经常被用来存储位图。离屏缓冲区你可以任意设置大小,唯一的限制是你内存的大小。
DirectDraw表面可以在系统内存中建立,或直接建立在显示内存中。如果你都建立在显示内存中,速度效果将是最好的,如在系统内存就要慢一些了。如果你把一个表面存储在显示内存中,另一个在系统内存中,性能会有一些损失的,尤其是显示卡与主板之间有一个令人恶心的带宽。总之,如果能把表面都建立在显示内存中,你或许应该尽力做到。
OK,我们总算有了一点儿认知,让我们看看具体怎么做吧!这儿有一个计划,我们将建立一个全屏模式下,16位色彩,640×480分辨率的程序,我将告诉你全部你需要做的。但开始前,你需要对Windows编程有一点了解。想必你看过了前面几章,应该对创建窗口已经熟悉了。由于这是一个全屏的程序,你不需要任何地窗口控制,所以你的窗口风格应该用WS_POUP|WS_VISIBLE。弄好了吗?All right,出发吧!
☆ 建立DIRECTDRAW对象
象我前面说过的,有三种方法。我们可以用两个DIRECTDRAW函数中的任何一个,或者直接调用COM对象。让我们每一个都试试,使我们自己熟悉它们。我将告诉你的最后一个方法可能是目前为止最简单的,可能你会喜欢用它。至于另外两个,打眼儿一看,你会觉得有些奇怪。首先,看看DirectDrawCreate():
HRESULT WINAPI DirectDrawCreate( GUID FAR *lpGUID, LPDIRECTDRAW FAR *lplpDD, IUnknown FAR *pUnkOuter ); | 看起来是不是有点儿复杂?HRESULT返回的类型是DirectDraw函数的标准。如果成功,返回值是DD_OK。如果失败,函数将返回一个错误常量,有几个错误常量供选择,但我不想细讲,更不想列出这些常量,反正你可以通过帮助文件随时查阅它们。但有一件事儿我得告诉你,有两个非常有用的宏可以帮助你知道函数调用成功与否:SUCCEEDED()和FAILED()。从字面上你就知道它们的分工了,是不是?只要把函数放到宏里面,你就知道结果了。无论如何,我们还得看看函数的参数:
· GUID FAR *lpGUID:是一个全局唯一标识符(GUID)的地址,代表将要创建的驱动程序。如果该参数是NULL,那么该调用指向当前的显示驱动程序。新版本的DirectDraw允许向该参数传递下列两种标志之一,以控制当前显示的行为:
◎ DDCREATE_EMULATIONONLY:DirectDraw只使用仿真(HEL),不使用硬件支持特性。
◎ DDCREATE_HARDWAREONLY:DirectDraw对象不使用仿真特性。只能使用硬件抽象层(HAL),如果硬件不能支持应用程序,将不再寻求硬件仿真层(HEL)的支持而返回错误信号。
· LPDIRECTDRAW FAR *lplpDD:表示如果调用成功则返回有效的DirectDraw对象指针的地址,它是DirectDraw对象指针的指针(“DD”表示DirectDraw,“lp”表示32位长指针,“lplp”表示长指针的长指针)。应用程序一般需要使用此指针的地址(即DirectDraw对象指针)初始化DirectDraw对象。
· IUnknown FAR *pUnkOuter:这是为高级COM应用保留的参数,设置为NULL好了。
不要被我罗里罗嗦的解释吓倒,实际应用起来很简单,解释这么多,不过是为了让你明白根本道理。现在有一个问题,这个函数给你一个指向IDirectDraw接口的指针,但我们想要一个指向IDirectDraw7接口的指针,我们应该怎么做呢?一旦DirectDraw应用程序通过DirectDrawCreate()函数获得了指向DirectDraw对象的指针,COM就有一种机制可以用来查看该对象是否支持其它接口。IUnknown的QueryInterface()方法使得你能够确定一个对象是否支持一个特定的接口:
HRESULT QueryInterface( REFIID iid, // Identifier of the requested interface void **ppvObject // Address of output variable that receives the ); | 第一个参数是一个要查询的对象的引用标识符。对于IDirectDraw7来说就是IID_IDirectDraw7。使用它,你必须把dxguid.lib链接入你的项目中;第二个参数是一个变量的地址,我们应该在程序的头部先声明一个LPDIRECTDRAW7类型的指针,再把指针的地址传递给这个参数。如果你使用的是Visual C++6.0,你在这儿或许还需要一个类型强制符。如果机器支持你指定的接口,函数将返回一个指向该接口的指针。通过该指针,代码就获得对新接口的方法的访问。如果函数调用成功,返回值是S_OK。
现在我们有了两个接口指针:一个是IDirectDraw接口,另一个是IDirectDraw7。后一个是我们想要的;前一个就没有用了。我们注意,在代码中每当找到一个新的有效对象时,前一个对象就通过Release()函数被释放掉。这个函数很简单:
返回的值是一个参考数字,只有在程序测试和调试时才用得着这个数字。为了安全起见,你还应该把释放了的指针赋值为NULL。我们也通常在声明这样的指针时设置它为NULL。你跟上我的节奏了吗?可能要记忆的东西太多了,但是你不得不记忆。让我们把谈到的这些做个实例吧,实例的目的是得到IDirectDraw7接口的指针:
LPDIRECTDRAW lpdd = NULL; // pointer to IDirectDraw (temporary) LPDIRECTDRAW7 lpdd7 = NULL; // pointer to IDirectDraw7 (what we want)
// get the IDirectDraw interface pointer if (FAILED(DirectDrawCreate(NULL, &lpdd, NULL))) { // error-handling code here }
// query for IDirectDraw7 pointer if (FAILED(lpdd->QueryInterface(IID_IDirectDraw7, (void**)&lpdd7))) { // error-handling code here } else { // success! release IDirectDraw since we don't need it anymore lpdd->Release(); lpdd = NULL; } | 现在,如果你是一个C程序员,你可能被调用QueryInterface()和Release()这两个函数的方法弄得有点模糊。你以前可能看过“->”这个符号,在C语言的结构部分,当结构声明了一个指针变量,调用结构成员时,就用“结构指针名->结构成员”,同样的道理,只是这里把结构成员换成了函数。既然说到这个话题,我就介绍一下另一个C++符号,范围定义符号“::”,它是表示从属关系的符号,举个例子你就明白了:比如QueryInterface()函数是属于IUnknown类的,就可以表示为IUnknown:ueryInterface()。我们将来会经常用到这个符号的,所以记住它。
说实在的,以上的主要目的是为了演示怎样使用QueryInterface()方法,它是所有DirectX接口的一部分,所以让我们往下进行。我们将直接使用COM方法获得接口指针。这种方法的好处是你可以立即获得IDirectDraw7接口指针,不用象刚才那么麻烦。首先,你必须得初始化COM,象这样:
HRESULT CoInitialize(LPVOID pvReserved); | 不能在容易了,你必须把参数设置为NULL。当你结束COM调用,你需要抛弃它,也很简单:
void CoUninitialize(void); | 我通常在DirecX程序的一开始就调用CoInitialize()函数,在程序的最末端,当我释放了所有的DirectX对象后,使用CoUninitialize()。一旦你初始化了COM,你就可以用CoCreateInterface()函数得到你想要的指针,它看起来有点丑陋:
STDAPI CoCreateInstance( REFCLSID rclsid, // Class identifier (CLSID) of the object LPUNKNOWN pUnkOuter, // Pointer to whether object is or isn't part // of an aggregate DWORD dwClsContext, // Context for running executable code REFIID riid, // Reference to the identifier of the interface LPVOID *ppv // Address of output variable that receives ); // the interface pointer requested in riid | 如果成功,返回值是S_OK。参数需要好好解释一下,看下面:
· REFCLSID rclsid:这是一个类标识符(不要同GUID搞混了哦),有为它准备好的常量标识符供你选择。对于IDirectDraw7来说,使用CLSID_DirectDraw。注意没有版本号,因为它是类标识符,不是接口标识符。
· LPUNKNOWN pUnkOuter:这个同我们在DirectDrawCreate()中看到的一样,设置为NULL。
· DWORD dwClsContext:这个必需的值叫作执行上下文,它定义了控制新生成对象的代码将要执行的方式。这个值可以从CLSCTX列表中选取,对于我们现在的情况,我们用CLSCTX_ALL,它包含了所有可能的值。
· REIID riid:我们在QueryInterface()中看过它。这个IID是IID_DirectDraw7。
· LPVOID *ppv:依然同DirectDrawCreate()中的一样,是指向接口指针的地址。
调用这个函数将取代我们上一个方法中的DirectDrawCreate()、QueryInterface()和Release()三个函数,所以简捷一些。当然,使用哪种随便你了。直接调用COM比我们先前用的方法少了一个多于地接口指针。一旦你用CoCreateInstance()建立了一个对象,你还得调用Initialize()函数初始化这个对象。在C++里可能写成这样IDirectDraw7::Initialize()。以下是它的原形:
HRESULT Initialize(GUID FAR *lpGUID); | 将使用同DirectDrawCreate()中一样的GUID,就是NULL。在我们继续前,让我给你看一个使用COM创建DirectDraw对象的例子:
LPDIRECTDRAW7 lpdd7; // interface pointer
// initialize COM CoInitialize(NULL);
// create the object CoCreateInstance(CLSID_DirectDraw, NULL, CLSCTX_ALL, IID_IDirectDraw7, (void**)&lpdd7);
// initialize the object lpdd7->Initialize(NULL); | 直接看例子可能使你更容易理解一些。好了,建立DirectDraw对象的最难的两种方法你已经学会了,那就让我们看看最简单的方法吧! 它只有一步,没有多于的接口指针,不用设置COM,什么都没有。就是下面这个函数:
DirectDrawCreateEx( GUID FAR *lpGuid, LPVOID *lplpDD, REFIID iid, IUnknown FAR *pUnkOuter ); | 所有的参数我们看起来都比较熟悉,因为我们刚才看过它们了。第一个,第二个和第四个参数同DirectDrawCreate()中的一样,只是这里需要用(void**)来修饰一下我们接口指针的地址——别问我为什么,这不是我的主意。第三个参数,riid,是我们在函数CoCreateInstance()中传递的接口ID,所以我们就用IID_IDirectDraw7。就这样,无论用哪种方法,我们得到了我们的DirectDraw对象,我们可以继续使用这个对象了。要做的头两件事是设置协作等级和显示协议。
☆ 设置协作等级和显示模式
我不需要说太多。Windows编程设置协作级别你只需要调用IDirectDraw7::SetCooperativeLevel()函数;设置显示模式你就调用IDirectDraw7::SetDisplayMode()函数。就这么简单!先来看看协作级别。这就是函数原形:
HRESULT SetCooperativeLevel( HWND hWnd, DWORD dwFlags ); | 返回的类型是HRESULT,你应该已经熟悉它了。对于所有的DirectX函数调用,你都可以用SUCCEEDED()和FAILED()宏检测调用的结果。以下是函数SetCooperativeLevel()的参数:
· HWND hWnd:很熟悉吧!传递主窗口的句柄给它,使Windows知道谁将使用它的资源。
· DWORD dwFlags:这个也很眼熟吧!每次我们看到dwFlags参数,几乎都有一个大的标志常量列表供我们选择,并且可以用“|”组合。这次也不会让你失望的哦!
◎ DDSCL_ALLOWMODEX:启用Mode X 显示模式(如320×200,320×240或者320×400)。该标志只能用于DDSCL_EXCLUSIVE和DDSCL_FULLSCREEN模式。
◎ DDSCL_ALLOWREBOOT:在独占模式中启用Ctrl+Alt+Del组合键功能。
◎ DDSCL_EXCLUSIVE:请求独占模式,必须与DDSCL_FULLSCREEN同时使用。
◎ DDSCL_FULLSCREEN:独占模式的拥有者负责整个主表面,GDI被忽略,必须与DDSCL_EXCLUSIVE同时使用。
◎ DDSCL_NORMAL:表示常规的Windows应用程序,不能与DDSCL_ALLOWMODEX、DDSCL_EXCLUSEIVE或DDSCL_FULLSCREEN标志同时使用,在该模式下运行的应用程序不能进行页交换或者更改主调色板。
◎ DDSCL_NOWINDOWCHANGES:防止DirectDraw最小化或恢复应用程序窗口。 还有几个标志常量我们暂时用不到,就不说了。由于我们要建立一个全屏的640×480×16的显示模式,所以我们得这样设置:
lpdd7->SetCooperativeLevel(hwnd, DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
现在协作级别已经设置好了,让我们再看看改变显示模式的函数:
HRESULT SetDisplayMode( DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags ); | 别忘了用宏去检测调用函数的成功或失败!大多数的参数同你料想的差不多:
· DWORD dwWidth,dwHeight:以象素为单位,新显示模式的尺寸。
· DWORD dwBPP:新显示模式的色彩深度。就是每一个象素有多少位字节。可以设置为8,16,24或32。警告:很多显示卡不支持24-bits。
· DWORD dwRefreshRate:屏幕的刷新频率。但你最好设置为0,使用默认的刷新频率。
· DWORD dwFlags:对不起,这次没有列表了^_^,唯一的选择是DDSDM_STANDARDVGAMODE,它把显示模式设置为0x13(DOS程序员的好朋友),取代了Mode X的320×200×8的模式。如果你还想使用其它的模式(你可能经常需要),没有问题,把它设置为0好了。
这些就是显示模式的设置,事先最好了解你的显示卡支持的显示模式,它们通常都支持640×480,800×600,1024×768等等,这些都是标准的模式。但是如果你非得设置成542×366的模式,你可能就会得到错误的反馈。科技在发展吗,什么都是可能的。让我们继续吧!
|
|