我正在从 C# 应用程序调用 CopyFileEx,并将匿名委托传递给 LPPROGRESS_ROUTINE 参数,以便获取有关文件复制进度的通知。
我的问题是,是否需要固定匿名代表以及为什么(或为什么不)。
此外,如果出现以下情况,答案是否会改变:
- CopyFileEx 没有阻塞。
- 如果我通过了一个非匿名的代表。
谢谢!
委托不需要固定。如果垃圾收集器无法移动托管对象,则将其固定。如果编组信息是正确的,那么编组层将确保传递一个指向固定对象的指针。
但是,上面您建议局部变量可能使委托保持活动状态的评论表明对变量生存期的误解。我请您参考规范,其中指出:
局部变量的实际生命周期取决于实现。例如,编译器可能静态确定块中的局部变量仅用于该块的一小部分。使用这种分析,编译器可以生成导致变量存储的生命周期比其包含块更短的代码。局部引用变量引用的存储空间独立于该局部引用变量的生命周期而被回收
换句话说,如果你说:
void M()
{
Foo foo = GetAFoo();
UnmanagedLibrary.DoSomethingToFoo(foo);
}
然后允许抖动说“你知道,我看到没有托管代码在调用非托管调用后的那一刻再次使用 foo ;因此我可以在那个时候积极地从另一个线程回收该对象的存储”。这意味着当突然在另一个线程上解除分配时,非托管调用可以在对象上工作。
如果 Foo 有析构函数,这尤其令人讨厌。当对象被非托管库使用时,终结代码可能会在另一个线程上运行,天知道会导致什么样的灾难。
在这种情况下,您需要使用 KeepAlive 来保持托管对象处于活动状态。不要依赖局部变量;局部变量被特别记录为不能保证使事物保持活力。
有关详细信息,请参阅http://msdn.microsoft.com/en-us/library/system.gc.keepalive.aspx 。
您不需要固定它,但只要复制正在进行,您就需要保持对它的引用。
非托管代码调用的 thunk 被固定,但您必须确保委托没有被垃圾收集 - 因此引用。
从下面的msdn看来,在这种情况下,pinning 和 GC.KeepAlive 都不需要,因为 CopyFileEx 是同步的。具体来说,它说:
“通常,您不必担心委托的生命周期。每当您将委托传递给非托管代码时,CLR 将确保委托在调用期间处于活动状态。但是,如果本机代码保留了超出调用范围的指针并打算稍后通过该指针回调,您可能需要使用 GCHandle 显式阻止垃圾收集器收集委托。”
由于 CopyFileEx 不会在调用范围之外保留指向函数的指针,因此我们不需要调用 KeepAlive。