我遇到了我的Direct3D应用程序中的泄漏,我最终纠正了它,但我认为泄漏的原因是由于我误解了Direct3D如何处理其内存和接口。
我无法找到关于它的权威文章/教程(如果你有一篇文章,请提供一篇),但是从我收集到的文章/教程可以这样做:
Get
方法时,返回的对象的引用数量都会递增。因此,如果我调用GetRenderTarget
,则渲染的表面的引用计数会递增。Release
会减少其引用计数。前两个点的组合基本上意味着:每次获得界面时,请在完成后释放它。我不完全确定这是否正确,但似乎在实践中有效。如果有人能澄清/确认它是如何运作的,那就太好了。
P.S,在发布接口时是否有任何保护措施?在后台缓冲区上多次调用Release
似乎没有造成任何损害(这是一件好事,但我不确定为什么不这样做。)
答案 0 :(得分:7)
Direct3D基于COM,这是一项至少15年的技术。似乎很多人声称COM已经死了,因此许多人忽略了它,但现实是Windows中有很多东西,包括Direct3D和MS的新媒体基金会都是基于COM。
我强烈建议你看看一般的COM编程。有大量的书籍和资源,但其中许多都相当陈旧但是没关系,因为技术的根源在很长一段时间内都没有改变。
基本上你观察到的是界面引用计数。 COM完全基于通过接口访问对象,这些接口都来自基本接口IUnknown。 IUnknown实现方法AddRef()和Release(),只要存储指针的本地副本,并且每当不再需要本地副本时调用Release(),应用程序就有责任调用AddRef()。
如果你有带接口输出参数的方法(即IFoo ** ppObj),这意味着被叫方给你一个接口,现在你已经拥有它,你完成任务时仍然有责任调用Release()用它。
一旦掌握了它,我建议您开始使用CComPtr智能类来存储本地和成员变量(仍然在函数调用之间传递原始接口值,不需要智能指针参数类型)。它会照顾你所有的引用计数。也不要将其称为“任意数量”的释放。今天它可能会起作用,因为对象是作为单例实现的,或者可能还有其他东西,但是这可能会随着下一个补丁或下一个版本而改变。始终遵守规则。如果你有一个接口,当你不需要它时,只需调用Release()一次。如果你制作了一个接口指针的副本,请确保只调用一次AddRef()。
答案 1 :(得分:4)
addref / release语义的应用比COM技术要广泛得多。有一个简单的规则一个 CreateObject()
(或CreateTexture
,或GetRenderTarget
,或GetBackBuffer
等......)必须面对< em>一个 Release()
,一个 AddRef()
必须面对一个 Release()
。
在COM IUnknown::Release()
中返回对象的引用数。它可能会欺骗你,你可以想:
“嗯......我只是打电话给Release()
,直到它返回0并且我没有泄漏。????利润!!!!! 111”&lt; - 那是错的! {3}可能会被Direct3D本身或您传递此对象的3rd_party库调用,或者应用程序之外的其他内容。 一个 AddRef
一个 Release
。当您不再需要对象时,应该调用AddRef
,不要浪费系统资源。
你说:
在后台缓冲区上多次调用释放似乎没有造成任何损害
这意味着什么。可能是像你这样的宇宙,或者你太幸运了,不能从D3D获得例外。
智能指针(例如Release
)可以让您的生活更加轻松。在这种情况下,您无需显式调用CComPtr
,如果将其分配给某个对象,则会在Release
dtor中调用它。
CComPtr
在包含直接3d标头并切换到调试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
。它有助于在d3d app中找到泄漏。
愿#define D3D_DEBUG_INFO
力量与你同在。
答案 2 :(得分:2)
D3D对象是COM对象,它们使用基本引用计数系统来管理对象的生命周期。 (有关Component Object Model或MSDN文章Managing Object Lifetimes的详细信息,请参阅维基百科)
引用计数仅通过AddRef
/ Release
方法进行修改,而某些其他函数则调用这些方法。
创建对象以及调用返回从Get
类派生的对象的某些IUnknown
方法将在内部调用AddRef
以增加引用计数,因此您需要调用完成对象后,每次调用Release
。
如果将对象传递给另一个存储该点副本的函数或类(即使是临时的),该类/函数应调用AddRef
以确保该对象在使用时不会被释放(和Release
发出信号表示已完成。
当参考计数器从调用Release
到达0时,对象被发出信号表示可能是删除所保留资源的好时机,但可能不会立即发生。多次调用Release也没有保护。引用计数器不会变为负数,但它不会执行任何其他健全性检查(因为它不能真正),因此您可以通过尝试释放您未持有的引用来导致应用程序不稳定。
答案 3 :(得分:1)
是的,你是对的。这称为引用计数,它确保对象只要它们被使用,就不再存在。您可以使用各种智能指针来强制执行此规则 - shared_ptr
和(C ++ 11)unique_ptr
允许自定义删除者调用Release()
。这样可以轻松控制Direct3D对象的生命周期,就像对应用程序中的任何其他对象一样。您不需要开始包含ATL库和CComPtr来使用带COM接口的智能指针。