DircetDraw c/c++ 使用指导
310cdt 译
边看边译,译完就拿了上来,见笑了
这是一系列的DirectDraw的指南,教你一步步的去构建一个简单的DirectDraw应用.这个指南用到了sdk包提供的很多DirectDraw的例子.这些例子展示了怎样设置DirectDraw,怎样用DirectDraw方法实现一般任务:
注意:这些指南中的例子是用c++写的.如果你使用的是c编译器,请进行适当的改变,以能进行成功的编译.你需要把vtable和this指针添加到接口方法中.
1.DirectDraw基础用法
要使用DirectDraw,你必须先创建一个代表计算机显示接口的DirectDraw实例.然后,你就可以通过接口的方法来操纵这个对象.你可能会需要创建一个或更多的DirectDraw平面对象(DirectDraw surface object)的实例
来在图形设备上显示你的应用程序.
为了演示这个,例子DDEx1 sample((SDK root)\Samples\Multimedia\DDraw\Src\Ddex1)演示了以下几步:
step 1:创建一个DirectDraw对象
想创建一个DirectDraw对象的实例,你的应用程序必须在InitApp函数中使用DirectDrawCreateEx函数,就像例程ddex1中一样.DirectDrawCreateEx函数包括4个参数.第一个是:显示设备全局唯一标识(GUID).GUID大部分情况下是设为NULL,选择系统默认的显示设备.第二个参数是:包含的是标示DirectDraw对象是否建立的指针的地址.第三个参数是IDirectDraw7接口的参考标示.必须设为IID_IDirectDraw7.第四个参数是设置为NULL的,是为了将来的扩展做准备的.
以下的例子展示了怎样创建DirectDraw对象,并判断创建是否成功.
hRet = DirectDrawCreateEx(NULL, (VOID**)&g_pDD, IID_IDirectDraw7, NULL);
if(hRet == DD_OK)
{
// g_pDD is a valid DirectDraw object.
}
else
{
// The DirectDraw object could not be created.
}
step2:决定程序的行为
在你改变显示方法前,你必须至少在IDirectDraw7::SetCooperativeLevel函数中指定dwFlags参数(DDSCL_EXCLUSIVE 和 DDSCL_FULLSCREEN).这样,你的应用程序就得到了显示设备的全部控制权,其他的程序不能共享.DDSCL_FULLSCREEN让你的应用程序在全屏幕模式下运行.你的程序覆盖了桌面,并且只有你的程序能在屏幕上输出.但是,桌面仍然是有效的.(按ALT+TAB切换到桌面)
下面的例子演示了SetCooperativeLevel函数的用法.
HRESULT hRet;
LPDIRECTDRAW7 g_pDD; // already created by DirectDrawCreateEx
hRet = g_pDD->SetCooperativeLevel(hWnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
if (hRet != DD_OK)
{
// Exclusive mode was successful.
}
else
{
// Exclusive mode was not successful.
// The application can still run, however.
}
如果 SetCooperativeLevel不返回DD_OK,你可以指定为DDSCL_NORMAL继续运行你的程序.但是,你的程序不再是独占模式,而且,可能会无法完成你的一部分要求.在这种情况下,你最好显示一个对话框让用户决定是否要继续.
如果你设置的是全屏幕独占合作级别,你必须把程序的窗口句柄传递给SetCooperativeLevel,这样可以让Windows有能力决定你的程序是否异常终止.
step3 :改变显示模式
接下来,你可以用IDirectDraw7::SetDisplayMode函数来改变显示模式.下面的例子将演示怎样把显示模式设置为640 × 480 × 8 bits per pixel (bpp).
HRESULT hRet;
LPDIRECTDRAW7 g_pDD; // already created by DirectDrawCreateEx
hRet = g_pDD->SetDisplayMode(640, 480, 8, 0, 0);
if (hRet != DD_OK)
{
// The display mode changed successfully.
}
else
{
// The display mode cannot be changed.
// The mode is either not supported or
// another application has exclusive mode.
}
当你设置显示模式的时候,你应该确定用户的硬件是否支持这样的模式.你可以设置一个能被大部分显示适配器支持的标准的模式.例如:你的程序可以以640 × 480 × 8作为备选模式,设计成为可以在所有系统上运行.
注意:IDirectDraw7::SetDisplayMode返回一个DDERR INVALIDMODE错误值,如果显示适配器无法切换到想要的模式时.你可以在试图改变显示模式前,用IDirectDraw7::EnumDisplayModes函数看一下用户显示适配器的能力.
step4:创建交换页(flipping surface)
设置完显示模式以后,你应该创建一个平面.例程:DDEx1用IDirectDraw7::SetCooperativeLevel 设置为独占模式.所以,你可以创建交换页(flipping surfaces).如果你设置的是DDSCL_NORMAL模式,你可以创建一个可以块移动(blit)的平面.创建交换页(flipping surfaces)需要以下步骤:
(4.1)定义需求的平面:
第一步是以DDSURFACEDESC2结构定义一个需求的平面.下面的例子演示了结构的定义和标志位的设定:
// Create the primary surface with one back buffer.
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP |
DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
在这个例子中,dwSize成员是DDSURFACEDESC2结构的大小.这是防止你用到的DirectDraw方法返回无效成员的错误.(dwSize是准备给将来的DDSURFACEDESC2结构的扩展用的)
dwFlags成员决定的DDSURFACEDESC2结构中那些成员将被填充有效的信息.例如在DDEx1中,dwFlags被设为你想要用DDSCAPS结构(DDSD_CAPS)和你想创建一个后台缓冲(back buffer)(DDSD_BACKBUFFERCOUNT)
dwCaps成员在例子中标示一个将要在DDSCAPS结构中使用的标志位.在这种情况下,他指定一个主平面(primary surface DDSCAPS_PRIMARYSURFACE),一个交换页(flipping surface DDSCAPS_FLIP),一个合成表面(complex surface DDSCAPS_COMPLEX).
最后,例子指定了一个后台缓冲.后台缓冲就是实际的绘图操作先在那里完成,然后,再快速的翻动(flip)到主平面(primary surface)上.在DDEx1中,后台缓冲的数目是1.其实,你要你的显存允许,你想建几个就建几个.你想知道更多的关于创建大于1块缓冲的信息,可以去看 "triple buffering".
创建的"平面"占用的存储空间,可以是系统内存也可以是显存.如果应用程序使用的空间超出了显存,DirectDraw就会使用系统内存.(例如你指定多块缓存在一个仅有1MB显存的是配器上).你也可以这样设置DDSCAPS结构的dwCaps成员,设成DDSCAPS_VIDEOMEMEORY或DDCAPS_SYSTEMMEMORY以达到只用显存或只用内存.(如指定用显存,而显存不够,IDirectDraw7::CreateSurface返回一个DDERR_OUTOFVIDEOMEMORY错误)
(4.2)创建平面
在填充完DDSURFACEDESC2结构后,你就可以用他和g_pDD指针(DirectDrawCreateEx函数创建的DirectDraw对象的指针)调用IDirectDraw7::CreateSurface方法,就像下面 :
hRet = g_pDD->CreateSurface(&ddsd, &g_pDDSPrimary, NULL);
if (hRet != DD_OK)
{
// g_pDDSPrimary points to the new surface.
}
else
{
// The surface was not created.
return FALSE;
}
g_pDDSPrimary参数将指向由CreateSurface函数返回的主平面(primary surface)的地址,如果调用成功的话.
指向主平面(primary surface)的指针有效后,就可以调用IDirectDrawSurface7::GetAttachedSurface方法去得到一个指向缓冲的指针.如下:
ZeroMemory(&ddscaps, sizeof(ddscaps));
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
hRet = g_pDDSPrimary->GetAttachedSurface(&ddscaps, &g_pDDSBack);
if (hRet != DD_OK)
{
// g_pDDSBack points to the back buffer.
}
else
{
return FALSE;
}
如果IDirectDrawSurface7::GetAttachedSurface调用成功,g_pDDSBack参数将指向缓存区.
step5:在平面上输出
在主平面(primary surface)和后台缓冲(back buffer)创建完成后,例子DDEx1用windows标准GDI函数输出了一些文本在缓冲上.如下:
if (g_pDDSBack->GetDC(&hdc) == DD_OK)
{
SetBkColor(hdc, RGB(0, 0, 255));
SetTextColor(hdc, RGB(255, 255, 0));
if (phase)
{
GetClientRect(hWnd, &rc);
GetTextExtentPoint(hdc, szMsg, lstrlen(szMsg), &size);
TextOut(hdc, (rc.right - size.cx) / 2, (rc.bottom - size.cy) / 2,
szMsg, sizeof(szMsg) - 1);
TextOut(hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg));
phase = 0;
}
else
{
TextOut(hdc, 0, 0, szBackMsg, lstrlen(szBackMsg));
phase = 1;
}
g_pDDSBack->ReleaseDC(hdc);
}
这个例子使用the IDirectDrawSurface7::GetDC方法获得设备上下文的句柄,并且,为了准备写入,将缓冲上锁.如果你不准备用需要设备上下文句柄的windows函数,你可以使用IDirectDrawSurface7::Lock IDirectDrawSurface7::Unlock方法加解锁.
接下来,phase变量决定了应该输出到主缓存消息(primary buffer message)还是后台缓存消息(back buffer message).如果phase=1,输出
主缓存消息,且设phase=0.如果phase=0,输出后台缓存消息,且设phase=1.
当消息输出到缓存区后,用IDirectDrawSurface7::ReleaseDC 方法将缓存区解锁.
对创建平面的内存上锁,是保证你的程序和系统不能同时对此内存进行存取.这防止你写入"平面"内存事发生错误.另外,不对"平面"内存解锁,你的程序将无法翻转平面.
平面被加锁之后,例子中用了标准windowsGDI函数:SetBkColor设置背景色,SetTextColor设置在背景上显示的字的颜色,用TextOut在"表面"上输出文字和背景色.
当文字被输出到缓存之后,例子应用IDirectDrawSurface7::ReleaseDC方法解锁"表面"并且释放句柄.不论在什么时候你的程序完成了对缓存的写入,一定要调用IDirectDrawSurface7::ReleaseDC 或IDirectDrawSurface7::Unlock之一,具体用哪个,由你的程序而定.不解锁,你的程序将不能翻转平面.
注意:用IDirectDrawSurface7::Unlock对"平面"解锁后,指向"平面"的指针将会无效.你必须再用IDirectDrawSurface7::lock去获得一个有效的指针.
step:6 翻转平面
在DDEx1中,WM_TIMER消息引发从缓存到主平面的翻转.当"平面"内存解锁后,你就可以用IDirectDrawSurface7::Flip方法实现从缓存到主平面的翻转.如下:
case WM_TIMER:
// Update and flip surfaces
if (g_bActive && TIMER_ID == wParam)
{
UpdateFrame(hWnd);
while (TRUE)
{
hRet = g_pDDSPrimary->Flip(NULL, 0);
if (hRet == DD_OK)
break;
if (hRet == DDERR_SURFACELOST)
{
hRet = g_pDDSPrimary->Restore();
if (hRet != DD_OK)
break;
}
if (hRet != DDERR_WASSTILLDRAWING)
break;
}
}
break;
在例子中,g_pDDSPrimary参数指示主平面和与他连接的缓存.当IDirectDrawSurface7::Flip被调用,前后平面将会交换(只是交换指针,没有实际的数据交换).如果翻转成功,返回DD_OK,跳出循环.
如果翻转返回的是DDERR_SURFACELOST值,就试图用IDirectDrawSurface7::Restore 方法保存平面.如果保存成功,程序循环到调用IDirectDrawSurface7::Flip,再试一次.如果保存不成功,程序跳出循环,返回一个错误.
注意:当你调用IDirectDrawSurface7::Flip 时,翻转动作并不能马上完成.如果,前一个翻转动作还没有完成,IDirectDrawSurface7::Flip 会返回DDERR_WASSTILLDRAWING.在这个例子中,IDirectDrawSurface7::Flip会一直调用直到返回DD_OK.
step7:释放DirectDraw对象
当你按下F12键时,例子DDEx1将在退出程序前处理WM_DESTROY消息.这个消息将调用ReleaseAllObjects函数,这个函数包括了多个Release调用.像下面一样:
static void
ReleaseAllObjects(void)
{
if (g_pDD != NULL)
{
if (g_pDDSPrimary != NULL)
{
g_pDDSPrimary->Release();
g_pDDSPrimary = NULL;
}
g_pDD->Release();
g_pDD = NULL;
}
}
这个程序检测DirectDraw对象的指针g_pDD和DirectDraw平面对象的指针g_pDDSPrimary是否为NULL.然后,调用IDirectDrawSurface7::Release方法令DirectDraw平面对象的reference count(可以认为是创建的对象的数目)数减1,这使得reference count减为0,DirectDraw平面对象就释放了.然后,还需将他的指针值设为NULL.接下来,程序调用IDirectDraw7::Release,同样是令DirectDraw的reference count减1,然后然后....全销毁.