C++ 内存处理(即函数释放)如何与Direct3D协同工作?

C++ 内存处理(即函数释放)如何与Direct3D协同工作?,c++,memory,directx,direct3d,C++,Memory,Directx,Direct3d,我在我的Direct3D应用程序中遇到了一个漏洞,我最终纠正了它,但我认为导致漏洞的原因是我对Direct3D如何处理其内存和接口的误解 我还没有找到一篇关于它的权威文章/教程(如果你有,请提供一篇),但从我收集的资料来看,它是这样工作的: 每次调用Get方法时,返回对象的引用数都会增加。因此,如果调用GetRenderTarget,则要渲染到的曲面的引用计数将增加 在接口上调用Release会减少其引用计数。前两点结合在一起基本上意味着:每次你得到一个接口,在你完成它之后释放它 当引用计数达

我在我的Direct3D应用程序中遇到了一个漏洞,我最终纠正了它,但我认为导致漏洞的原因是我对Direct3D如何处理其内存和接口的误解

我还没有找到一篇关于它的权威文章/教程(如果你有,请提供一篇),但从我收集的资料来看,它是这样工作的:

  • 每次调用
    Get
    方法时,返回对象的引用数都会增加。因此,如果调用
    GetRenderTarget
    ,则要渲染到的曲面的引用计数将增加
  • 在接口上调用
    Release
    会减少其引用计数。前两点结合在一起基本上意味着:每次你得到一个接口,在你完成它之后释放它
  • 当引用计数达到0时,实例将被删除
我不完全确定这是否正确,但在实践中似乎是可行的。如果有人能澄清/确认它是如何工作的,那就太好了


另外,在发布接口时是否实施了任何保护措施?在后缓冲区上多次调用
Release
似乎不会造成任何损坏(这是一件好事,但我不确定为什么不会)。

D3D对象是COM对象,它们使用基本的引用计数系统来管理对象的生存期。(有关的更多信息,请参阅wikipedia或MSDN文章)

引用计数仅通过
AddRef
/
Release
方法修改,某些其他函数调用这些方法

创建对象以及调用某些返回从
IUnknown
类派生的对象的
Get
方法将在内部调用
AddRef
以增加引用计数,因此在完成对象后,每次调用都需要调用
Release

如果将对象传递给另一个存储点副本的函数或类(即使是暂时的),则该类/函数应调用
AddRef
,以确保在使用对象时不会释放该对象(以及
Release
以表示该对象已完成)


当调用
Release
时,参考计数器达到0时,对象会收到信号,表示可能是删除保留资源的好时机,但可能不会立即发生。多次调用Release也没有保护。引用计数器不会变为负数,但它不会执行任何其他健全性检查(因为它实际上不能),因此您可以通过尝试释放未持有的引用来导致应用程序不稳定。

Direct3D基于COM,COM是一种至少有15年历史的技术。似乎很多人声称COM已经死了,因此很多人忽略了它,但现实是Windows中有很多东西,包括Direct3D和MS的新媒体基金会,它们都是基于COM.

我强烈建议您看看通用COM编程。有大量的书籍和资源,但其中许多都相当陈旧,但这没关系,因为技术的根源已经很久没有改变了

基本上,您观察到的是接口引用计数。COM完全基于通过接口访问对象,这些接口都源自基本接口IUnknown。IUnknown实现方法AddRef()和Release(),应用程序负责在存储指针的本地副本时调用AddRef(),并在不再需要该本地副本时调用Release()

当您有带有接口输出参数的方法(即IFoo**ppObj)时,这意味着被调用方将返回一个接口,而现在您已经拥有了它,无论何时使用它,您都有责任调用Release()


一旦掌握了窍门,我建议您开始使用CComPtr智能类来存储本地和成员变量(仍然在函数调用之间传递原始接口值,不需要智能指针参数类型)。它会照顾到你所有的参考计数。另外,不要将release称为“任意次数”。它可能在今天起作用,因为该对象是作为一个单例实现的,或者可能有其他东西在保留它,但这可能会随着下一个补丁或下一个版本而改变。始终遵守规则。如果您有一个接口,当您不需要它时,只需调用Release()一次。如果您复制了接口指针,请确保只调用AddRef()一次。

AddRef/release语义的应用比COM技术要广泛得多。有一条简单的规则:一个
CreateObject()
(或者
CreateTexture
,或者
GetRenderTarget
,或者
GetBackBuffer
,等等)必须面对一个
Release()
,一个
AddRef()
必须面对一个
Release()

在COM
IUnknown::Release()中,返回对对象的引用数。它可能会欺骗你,你会想:
“嗯……我只需调用
Release()
,直到它返回0,我就不会有泄漏了。??利润!!!!!111”GetRenderTarget(0,surf0);//曲面引用计数器递增,应为此调用Release() surf1=surf0;//曲面引用计数未递增,不应为此调用Release() pDevice->GetRenderTarget(0,surf2);//表面参考计数器递增 CComPtr surf3=surf0;//表面参考计数器递增 surf0->Release();//发布pDevice->GetRenderTarget(0,surf0); surf2.Release();/。未使用Release()->Release()-这很重要 surf4.Release();//因为surf4==0,所以不会发生任何事情 }//在surf3析构函数中调用surf3.Release()
此外,在包含direct 3d头文件和切换到调试D3D运行时之前,您还可以定义D3D调试信息。这有助于发现管道中的泄漏
void get_surface(IDirect3DDevice9 *pDevice)
{
  IDirect3DSurface9 *surf0;
  IDirect3DSurface9 *surf1;
  CComPtr<IDirect3DSurface9> surf2;
  CComPtr<IDirect3DSurface9> surf3;
  CComPtr<IDirect3DSurface9> surf4;

  pDevice->GetRenderTarget( 0, surf0 ); // surface reference counter incremented, you should call Release() for this
  surf1 = surf0; // surface reference count is not incremented, you shouldn't call Release() for this

  pDevice->GetRenderTarget( 0, surf2 ); // surface reference counter incremented
  CComPtr<IDirect3DSurface9> surf3 = surf0; // surface reference counter incremented

  surf0->Release(); // release for pDevice->GetRenderTarget( 0, surf0 );
  surf2.Release();  // .Release() used not ->Release() - it is important
  surf4.Release();  // nothing happens because surf4 == 0
} // surf3.Release() is called in surf3 destructor