丢失及恢复平面

当DirectDrawSurface这些对象的平面内存页无需再使用(或表示), 联合于DirectDrawSurface对象的平面内存可以被释放。 当一个DirectDrawSurface对象失去他的平面内存后,许多宏只会返回DDERR_SURFACELOST。

平面会在显示模式改变或因为其他程序改变或访问显卡并清除所有卡上的平面内存而丢失。 IDirectDrawSurface4::Restore宏会重新创建这些丢失的平面并重新将其连接到它们的DirectDrawSurface对象。 如果你的程序使用多于一个的平面,你可以直接调用IDirectDraw4::RestoreAllSurfaces宏来一次恢复你的所有平面。

恢复平面并不会重新读取原先丢失平面前所存在的位图图象。你必须自己完整的建立其原来的图象。


COM关联值对于平面的意义

建立在COM(构件对象模型)之上意味着DirectDraw是通过以下规则, 使用关联值来管理对象的活动。要从概念上来理解,请参阅COM文档; DirectDraw所围绕的中心是在于父和子对象的活动。

通过COM的规定,当一个界面指针通过设置它到另一个活动或传递到另一个对象来复制时, 复制就表现为对于对象的另一个关联,并且因此界面的IUnknown::Addref宏必须被调用以反映这个改变。 不仅仅在工作于DirectDraw对象时要依照COM关联值的规定,而且你必须要熟悉到底哪个DirectDraw 对象内部需要改变关联值。一些DirectDraw宏如复杂平面的切换链所影响平面的关联值, 当宏包含Clipper或调色板影响这些平面的关联值。理解这些情况可以使你的程序更稳定并可以预防内存溢出。 这部分内容分为以下主题:

注意:
对于DirectDraw对象还有一些事要理解,除了这部分附加的内容, 要了解更多请参阅DirectDraw对象的父子对象的活动时间(Lifetime)。 译者按:参见DirectDraw第四篇


当要改变关联值时

有许多DirectDraw宏会影响到一个平面的关联值,有许多会影响到连接这个平面的平面。 你可以将这种情况理解成为"只有平面改变"和"交叉对象改变":

只有平面改变Surface-Only Changes

只有平面改变,就像名字,只影响到一个平面的关联值。例如: 当你想用IDirectDraw4::EnumSurfaces来枚举符合特殊描述的平面。 当程序运行你提供的回调函数时,会传递一个指针到一个IDirectDrawSurface4界面, 但他会在你程序收到指针前为对象增加一个关联值。当你完成使用这个对象后, 释放对象就是你的责任。这通常是在你回调函数的尾部,不然以后你将继续保存对象。

大多数其他的只有平面改变会影响复杂平面的关联值,如切换链。 复杂平面的关联值稍稍有些麻烦,因为(在大多数情况)DirectDraw将复杂平面视作为一个单一对象, 即使是许多页平面。总之,IDirectDrawSurface4::GetAttachedSurface及 IDirectDrawSurface4::AddAttachedSurface宏会增加平面的关联值, 而IDirectDrawSurface4::DeleteAttachedSurface会减少关联值。 这些宏不改变附加于当前平面的平面关联值。详见这些宏的参考及复杂平面的关联值。

交叉对象改变Cross-Object Changes

交叉对象关联值改变发生于当你创建一个联合于一个平面和另一个对象来完成平面的任务, 如:一个Clipper或一个调色板。

IDirectDrawSurface4::SetClipper及IDirectDrawSurface4::SetPalette宏增加所有附加对象的关联值。 在它们附加上后,平面便管理它们;如果这个平面被释放,就会自动释放平面使用的所有对象。 (出于这个理由:一些程序在这些调用完成后为对象释放界面。这是一个完全有效的方法)

当一个剪切器(Clipper)或调色板附加到一个平面,你可以调用 IDirectDrawSurface4::GetClipper及IDirectDrawSurface4::GetPalette宏来恢复。 因为这些宏返回一个界面指针的拷贝,它们在恢复时会暗中增加对象的关联值。 当你完成使用界面,不要忘了释放它们因为对象的界面表现不会消失只要它们附加于的平面与它们仍有关系。


复杂平面的关联值

你所使用来操作复杂平面的宏如交换链都使用平面界面指针,因此他们都改变平面的关联值。 因为一个复杂平面实际上是一组平面,关联值就需要更多的考虑到。你可能认为, IDirectDrawSurface4::GetAttachedSurface宏返回当前平面所附加平面的平面界面。 它是在界面被恢复后再增加关联值;这需要你在不用界面后来释放它。 IDirectDrawSurface4::AddAttachedSurface宏附加一个平面到当前平面。同样的, AddAttachedSurface在附加时就会增加平面的关联值。你要使用 IDirectDrawSurface4::DeleteAttachedSurface宏来从链中删除平面并减少关联值。

尽可能不立即清除这些宏,因为它们不会改变与复杂平面合成的其他对象的关联值。 GetAttachedSurface宏只简单在恢复时增加平面的关联值,这并不改变它依靠平面的关联值。 (同样运用于一个外在的IUnknown::AddRef调用)这意味着复杂平面的主平面关联值可能比其下属平面关联值先达到0。 当主平面的关联值达到0时,所有附加平面都将被释放而不管其关联值是多少。 (这就像一棵树:如果你砍了树基,整棵树就倒了。在这种情况下,主平面就是树基。) 尝试在主平面被解除后访问其附属平面会造成内存错误。

要避免这类错误,就要确认你的程序要在释放主平面前释放所有从属平面。 跟踪你程序所握有的关联会有帮助,并在确认程序还在握有主平面关联的情况下只访问其从属平面。


释放平面

像所有的COM界面,你无需再用它们时必须通过调用它们的IDirectDrawSurface4::Release 宏来释放它们。每个你创建的平面都可以被分别释放。不管如何,如果你通过一次调用 IDirectDraw4::CreateSurface创建的复杂平面,如交换链,你只需要释放前景缓冲。 在这种情况下,任何你所有的屏后缓冲指针也在暗中被释放并不能再使用。

明确释放一个屏后缓冲平面并不影响链中的其他平面。


枚举平面

通过调用IDirectDraw4::EnumSurfaces宏,你可以通过各种方法让DirectDraw来枚举平面。 EnumSurfaces宏使你通过提供平面描述找到符合条件的平面或不符合条件的平面。 DirectDraw调用每次你调用枚举平面的EnumSurfacesCallback的回调函数。

有两个主要的方法来搜索--你可以搜索DirectDraw已经创建好的平面, 或DirectDraw对象当时有能力创建的平面(给予平面的描述及足够的内存)。 你在宏的dwFlags参数中指定何种类型平面要搜索的兼有标志。

枚举已在平面

这是最普通的枚举类型。通过调用EnumSurfaces你来枚举已创建平面, 并在dwFlags参数中指定包含DDENUMSURFACES_DOESEXIST搜索类型标志及一个匹配标志 (DDENUMSURFACES_MATCH,DDENUMSURFACES_NOMATCH或DDENUMSURFACES_ALL)。 如果你要枚举所有的已创建的平面,你可以将lpDDSD参数设置为NULL, 否则将地址设置为一个描述你要搜索平面的已初始化的DDSURFACEDESC2结构。 你可以设置第三个参数lpContext,指向一个地址,将会传递到你指定第四个参数 lpEnumSurfacesCallback的回调枚举函数。

以下代码部分是要枚举所有DirectDraw对象已创建的平面。

    HRESULT ddrval;
    ddrval = lpDD->EnumSurfaces(DDENUMSURFACES_DOESEXIST |
                                DDENUMSURFACES_ALL, NULL, NULL,
                                EnumCallback);
    if (FAILED(ddrval))
        return FALSE;

当搜索一个指定描述的平面,DirectDraw会将提供的平面描述和已创建的平面逐一比较。 只有完全匹配的会被枚举。DirectDraw增加枚举了的平面的关联值,所以注意在不使用时释放它(或已经使用好)。

枚举可实现平面

这种类型的枚举和枚举已创建平面不同,但它可以决定你在尝试创建一个平面前得到它是否被支持。 要执行这个搜索,要在你调用IDirectDraw4::EnumSurfaces时包含DDENUMSURFACES_CANBECREATED及 DDENUMSURFACES_MATCH标志(其他标志都无效)。已初始化DDSURFACEDESC2结构必须包含在调用中, 包含DirectDraw要使用的平面特性。

要枚举平面使用一个特殊的像素格式,在DDSURFACEDESC2结构中的dwFlags中要包含 DDSD_PIXELFORMAT标志。另外,在平面描述中初始化DDPIXELFORMAT结构并设置dwFlags 成员来包含需要的像素格式的标志--DDPF_RGB,DDPF_YUV,或都要。你无须设置其他像素格式值。 如果你在DDSURFACEDESC2结构中包含DDSD_HEIGHT及DDSD_WIDTH标志, 你可以在dwHeight及dwWidth中指定需要的尺寸。如果你不包含这些标志,DirectDraw则使用主平面的尺寸。

以下代码部分是要枚举有效的平面特性:96' 96 RGB或YUV平面:

    DDSURFACEDESC2 ddsd;
    HRESULT        ddrval;
    ZeroMemory(&ddsd, sizeof(ddsd));
 
    ddsd.dwSize  = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_PIXELFORMAT | 
                   DDSD_HEIGHT | DDSD_WIDTH;
    ddsd.ddpfPixelFormat.dwFlags = DDPF_YUV | DDPF_RGB;
    ddsd.dwHeight = 96;
    ddsd.dwWidth  = 96;
 
    ddrval = lpDD->EnumSurfaces(
                     DDENUMSURFACES_CANBECREATED | DDENUMSURFACES_MATCH,
                     &ddsd, NULL, EnumCallback);
    if (ddrval != DD_OK)
        return FALSE;

DirectDraw枚举可实现平面,其实它尝创建一个依据描述的临时平面。如果尝试成功, DirectDraw会只在特性可工作的情况下调用提供的EnumSurfacesCallback的回调函数。 不要认为无法枚举的平面是不被支持的。DirectDraw的尝试创建临时平面会因调试时的内存限制而失败, 结果这些特性的平面不被枚举,即使驱动程序支持它们。


更改平面特性

你可以通过IDirectDrawSurface4::SetSurfaceDesc宏来更改已创建平面的特性。 通过这个宏,你可以改变你程序已经分配的一个DirectDrawSurface 对向的平面内存到系统内存及像素格式。这在允许一个平面在无须复制情况下使用以前分配缓冲数据时很重要。 新的平面内存由客户程序分配,就这一点而论,客户程序必须也要解除它。

当调用IDirectDrawSurface4::SetSurfaceDesc宏,lpddsd 参数必须是用来描述平面内存及该内存的指针的DDSURFACEDESC2结构地址。在这个结构中, 你只要设置dwFlags成员为反映平面内存位置,尺度,占用长度及像素格式的有效成员。 因此dwFlags只能包含DDSD_WIDTH,DDSD_HEIGHT,DDSD_PITCH,DDSD_LPSURFACE及 DDSD_PIXELFORMAT标志,用来指出有效的结构成员。

在你设置结构的值,你必须事先分配内存来装入平面。你分配内存的尺寸很重要。 不仅要分配足够的内存来适应平面的宽和高,还要有足够的占用长度(Pitch)空间, 必须是QWORD(8个字节)的倍数。记住,占用长度是基于字节,而非像素。

当设置平面的结构值,lpSurface成员是一个指向你分配内存的指针, dwHeight及dwWidth成员以像素来描述平面的尺度。如果你指定平面的尺度, 你必须也填写lPitch成员来指定平面的占用长度。占用长度必须是DWORD的倍数。 同样的,如果你指定了占用长度,你也必须指定一个宽度值。最后ddpfPixelFormat 成员用来描述平面的像素格式。除了lpSurface成员,如果你不指定其他后面的成员, 宏就默认使用当前平面的值。

当你在使用IDirectDrawSurface4::SetSurfaceDesc时,有一些限制及共同点你必须了解。 例如:DDSURFACEDESC2结构的lpSurface成员必须是一个指向系统内存的有效指针(宏现在还不支持显存指针)。 还有,dwWidth及dwHeight成员必须是非零的值。最后,你不能再设置主平面或任何和主平面连接的切换链。 你可以为多DirectDrawSurface对象设置相同的内存,但你必须注意,当指定到任何平面对象时, 内存是不会被解除的。

不正确使用SetSurfaceDesc宏会导致无法预料的后果。DirectDrawSurface 对象将不会解除它未占用的平面内存。因此,当平面内存不再使用时,你有责任来释放它。 不管怎样,当SetSurfaceDesc被调用,DirectDraw将会释放在创建平面时所分配的平面内存。


直接操作访问平面内存

你可以通过IDirectDrawSurface4::Lock宏来直接操作访问帧缓冲或屏下平面内存。 当你调用这个宏,lpDestRect是一个指向用于描述你要直接访问的矩形的RECT结构的指针。 要整个平面都被锁定,设置lpDestRect为NULL。同样,你可以指定RECT只包含平面的一部分。 假如没有矩形是重叠的,两个线程或进程可以在一个平面内同时锁定多个矩形。

Lock宏会填写一个DDSURFACEDESC2结构,其中带有你要访问平面内存的信息。 结构包含平面的占用长度Pitch(或Stride)及像素格式信息,及像素格式是否和主平面一样。 当你完成访问平面内存,调用IDirectDrawSurface4::Unlock宏来解锁。

当你锁定一个平面,你可以直接操作其内容。以下是一些Tip以来避免一些在直接绘制平面内存的一般问题:

不要假设一个不变的显示占用长度。要重视每次IDirectDrawSurface4::Lock返回的占用长度信息。 这个占用长度正是其中之一,其他还包含平面内存的位置,显示卡类型, 甚至有DirectDraw驱动程序的版本。详见Width vs. Pitch宽度与占用长度。 译者按:参见DirectDraw文档第六篇

确定在Blit之前先解锁平面。如果调用于一个锁定的平面, DirectDraw Blit宏就会失败,并返回DDERR_SURFACEBUSY或DDERR_LOCKEDSURFACES。 类似的,GDI Blit函数如果使用于一个已创建于显存中的被锁定的平面会失败而且不会返回错误值。

在平面被锁定后限制你程序的活动。当一个平面被锁定,DirectDraw总是握住Win16Mutex(也称Win16Lock 译者按:Win16Mutex是控制通往16位内核中不可重试组件的入口的软件信号灯。), 从而增加访问平面内存的安全性。Win16Mutex会连接访问GDI及USER动态衔接库(DLL), 并在调用IDirectDrawSurface4::Lock及IDirectDrawSurface4::Unlock期间暂时关闭Windows。 IDirectDrawSurface4::GetDC宏会暗中调用Lock,而IDirectDrawSurface4::ReleaseDC会暗中解除Lock。

总是在复制数据时与显存对齐。(Windows95 及Windows 98使用一个页错误处理, Vflatd.386,来通过域转换内存为显卡产生一个虚拟的平面帧缓冲区。 处理允许在DirectDraw中产生一个线性帧缓冲。复制数据与显存不对齐, 如果复制长度超过内存域会使系统终止操作。)

除非你在调用Lock宏时包含DDLOCK_NOSYSLOCK标志,不然所定平面会导致DirectDraw 使用Win16Mutex。在Win16Mutex期间,所有其他程序,包括Windows,都终止活动。 直到Win16Mutex被程序执行部分停掉,不然标准的Debug(除虫)在锁定时将无法使用。 Kernel Debugger可以在此时使用。DirectDraw在锁定主平面时总是使用Win16Mutex。 如果在调用IDirectDrawSurface4::Lock时有一个Blit在进程中,宏会立即带错返回, 因为锁定无法进行。要预防错误,使用DDLOCK_WAIT标志使宏一直等待直到锁定完成。

锁定部分主平面会影响软件显示光标。如果光标进入锁定区域,它会被隐藏。 如果光标在锁定区域之外,它会被冻结。如果整个平面被锁定时就不会出现这种情况。


上篇|返回|下篇

Lucker 1999.8.1
E-Mail: fred_cai@kali.com.cn


云风工作室 制作