【问题标题】:How exactly does memory handling (i.e, the function Release) work with Direct3D?内存处理(即函数 Release)如何与 Direct3D 一起工作?
【发布时间】:2011-05-15 20:16:34
【问题描述】:

我在自己的 Direct3D 应用程序中遇到了泄漏,我最终纠正了它,但我认为泄漏的原因是由于我对 Direct3D 如何处理其内存和接口的误解。

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

  • 每次调用Get 方法时,返回的对象的引用数都会增加。因此,如果我调用GetRenderTarget,被渲染到的表面的引用计数会增加。
  • 在接口上调用Release 会减少其引用计数。前两点结合起来本质上意味着:每次获得接口时,在完成后释放它。
  • 当引用计数达到 0 时,删除实例。

我不完全确定这是否正确,但它似乎在实践中有效。如果有人能澄清/确认它是如何工作的,那就太好了。

P.S,在发布接口时是否实施了任何保护措施?在后台缓冲区上多次调用Release 似乎不会造成任何损害(这是一件好事,但我不知道为什么它没有)。

【问题讨论】:

    标签: c++ memory directx direct3d


    【解决方案1】:

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

    我强烈建议您看一下一般的 COM 编程。有很多书籍和资源,但其中很多都比较老,但没关系,因为技术的根源很长时间没有改变。

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

    当你有带有接口输出参数的方法时(即 IFoo** ppObj ),这意味着被调用者正在给你一个接口,现在你有了它,你仍然有责任在完成时调用 Release()用它。

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

    【讨论】:

    • 很好的答案,谢谢。在实践中,我永远不会多次致电Release,我只是想知道为什么当我试图弄清楚并尝试它时它没有将程序拆开。
    【解决方案2】:

    addref/release语义的应用比COM技术要广泛得多。有一个简单的规则oneCreateObject()(或CreateTexture,或GetRenderTarget,或GetBackBuffer,等等......)必须面对one@ 987654325@, one AddRef() 必须面对 one Release()

    在 COM 中 IUnknown::Release() 返回对对象的引用数。它可能会欺骗您,您可以认为: “嗯......我只是打电话给Release(),直到它返回0,我不会有任何泄漏。????利润!!!!!!111”AddRef 可能由 Direct3D 本身调用,也可能由您将此对象传递给的 3rd_party 库或您的应用程序之外的其他东西调用。 一个 Release一个 AddRef。当您不再需要对象时,您应该调用Release,不要浪费系统资源。 你说:

    在后台缓冲区上多次调用 Release 似乎不会造成任何损害

    这没有任何意义。可能是宇宙太喜欢你了,或者你太幸运了,没有从 D3D 中得到例外。

    如果您愿意使用智能指针(例如CComPtr),它们可以让您的生活更轻松。在这种情况下,您不需要显式调用Release,如果将其分配给某个对象,则会在CComPtr dtor 中调用它。

    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
    

    您也可以#define D3D_DEBUG_INFObefore 包括直接 3d 标头并切换到调试 d3d 运行时。它有助于发现 d3d 应用程序中的漏洞。

    CComPtr原力与你同在。

    【讨论】:

    • 非常感谢您的回答。我并不是说我认为调用Release 一百次是个好主意,或者我曾经想过这样做,我只是好奇为什么它没有破坏它。 :)
    【解决方案3】:

    D3D 对象是 COM 对象,它们使用基本的引用计数系统来管理对象的生命周期。 (有关 Component Object Model 的更多信息,请参阅维基百科,或 MSDN 文章 Managing Object Lifetimes

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

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

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

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

    【讨论】:

      【解决方案4】:

      是的,你是对的。这称为引用计数,它确保对象在使用期间一直处于活动状态,而不再使用。您可以使用各种智能指针来强制执行此规则 - shared_ptr 和 (C++11) unique_ptr 都允许自定义删除器调用 Release()。这使得控制 Direct3D 对象的生命周期变得容易,就像控制应用程序中的任何其他对象一样。您无需开始包含 ATL 库和 CComPtr 即可将智能指针与 COM 接口一起使用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-07
        • 1970-01-01
        • 2019-01-05
        • 1970-01-01
        • 2015-10-13
        • 2011-11-24
        相关资源
        最近更新 更多