【发布时间】: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