【问题标题】:Preventing garbage collection when bridging C++/CLI to C#将 C++/CLI 桥接到 C# 时防止垃圾收集
【发布时间】:2015-07-09 05:04:22
【问题描述】:

我目前正在使用 C++ 代码库并编写托管 C++/CLI 包装器以启用 C# 桥接。我的一个托管类包含malloced 内存的非托管块:

public ref class ManagedFoo
{
public:
   ManagedFoo()  { m_fooData = malloc(AmountNeeded); }
   ~ManagedFoo() { free(m_fooData); } // not sure if this should be a finalizer instead
   void *m_fooData;
};

相关类持有指向该分配块的指针,并将其发送到非托管区域,假定它存在,直到进行特定调用:

public ref class ManagedBar
{
public:
    static void BorrowFooData(ManagedFoo^ f) 
    { 
      TotallyUnmanagedCode(f->m_fooData);
      // The unmanaged code has held onto this pointer for future use
    }
    static void DoneWithFooDataNow() 
    {
      MoreTotallyUnmanagedCode();
      // The unmanaged code is finally done with f->m_fooData at this point
    }
}; 

这样使用:

ManagedBar^ b = gcnew ManagedBar;
b->BorrowFooData(myFooA);
b->BorrowFooData(myFooB);
b->BorrowFooData(myFooC);
// lots of other code here, where myFoos could potentially become unreferenced

// at some point in the future...
b->DoneWithFooDataNow();

我怎样才能最好地避免过早的垃圾收集?

我目前的解决方法感觉相当昂贵——我在ManagedBar 中添加了一个List<ManagedFoo^> keepAlive 对象,并且每次调用BorrowFooData 都只是Adds 将foo 添加到列表中。这样,我可以确定引用继续存在并且垃圾收集不会发生。 (DoneWithFooDataNow 清除 keepAlive 列表。)

我实际上还没有对这段代码进行足够的压力测试,无法看到垃圾收集器会导致问题,但我相信这只是时间问题。

【问题讨论】:

  • 您的代码示例太模糊,无法理解问题所在。你写“可能会变得未被引用”,但你的意思是什么?为了使对象保持活动状态,您确实需要维护对它的引用。但这些变量是什么?他们不会保留参考吗?一个可能的警告是框架可能会注意到变量不再被使用并在此基础上收集对象。您可以使用GC.KeepAlive() 来避免这种情况。但是这里没有足够的信息来知道在你的情况下什么答案是正确的。
  • 我的意思是唯一的引用可能在“完全非托管代码”部分内。此代码是广泛使用的框架的一部分,无法预测如何调用它。 BorrowFooData 可以在一个函数中调用,DoneWithFooDataNow 可以在完全不同的地方调用。 GC.KeepAlive 仅在单个函数的持续时间内有效,因此对我没有帮助;我无法预测客户端代码何时会调用DoneWithFooDataNow,因为它在整个地方都在使用。
  • “唯一的引用可能在“完全非托管代码”部分中”——我在您的代码示例中没有看到 托管 对象引用被传递给 unmanaged 代码。无论如何,底线是如果没有对对象的引用,它可能会被收集。您必须在某处保留参考。如果ManagedBar 的调用者拒绝这样做,那么我想该类中的列表与任何方法一样好。没有a good, minimal, complete code example,就不可能真正完全理解这个场景。
  • 那个地方是TotallyUnmanagedCode(f->m_fooData);。这会将foo 内部数据移交给垃圾收集器无法找到的非托管代码。如果你愿意,我可以很快写一个独立的例子,但它不会重现任何问题,除非你知道一种方法可以强制垃圾收集器积极地收割所有东西。
  • 内部m_fooData 对象无论如何都不是托管对象。 GC是否能找到它是无关紧要的。您可以调用GC.Collect()强制收集操作。

标签: c# memory-management garbage-collection c++-cli


【解决方案1】:

GCHandle 类专门用于在非托管代码中存储引用。它的文档包括示例。

PS:对于您的问题,是的,处置非托管资源应该在终结器中。如果我没记错的话,如果你包含一个析构函数,你应该从析构函数中显式调用终结器,以防你 Dispose 对象。

【讨论】:

  • 难道我还需要维护一个List<GCHandle> 以便我可以GCHandle.Free 他们吗?这会比我现有的keepAlive 列表更有效吗?
  • 另外,你能解释一下为什么终结器是正确的吗?我不明白其中的微妙之处。
  • GCHandle 的要点是您可以使用它的ToIntPtr() 方法将它们存储在非托管结构中。传递给非托管代码的非托管结构类型。至于终结器/析构函数,我记得是:“析构函数在 Dispose() 上调用,终结器在集合上调用。”我不能 100% 确定(但仍然认为它会发生)是析构函数在对象上调用 SuppressFinalize(),所以你必须从析构函数调用终结器,否则它永远不会被调用。
猜你喜欢
  • 2011-12-20
  • 1970-01-01
  • 2012-03-24
  • 1970-01-01
  • 2013-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多