2010-11-04 43 views

回答

6

委托具有两个相关的属性,方法和目标。如果委托是为实例方法创建的,则目标将为非null。并且只要垃圾收集器可以看到委托实例,该对象就会保持活动状态。

原生代码与回调问题有关。将委托实例传递给pinvoked本地函数时,P/Invoke编组器将使用Marshal.GetFunctionPointerForDelegate()创建一个存根,以在本机代码进行回调时生成所需的Target引用。然而,垃圾收集器不能看到这个存根,因此不会找到对委托对象的引用。并收集它。本地代码的下一次回调会导致崩溃。

为了避免这种情况,您必须自己存储委托对象,以便只要本机代码可以进行回调,它就会保持引用状态。将它存储在一个静态变量中是一个明显的解决方案。

+0

有点晚了评论,但确实存储在一个'static'变量也阻止重排在其他答案中提到? – rmobis 2014-12-02 14:36:42

+0

钉住一个物体是完全不同的东西,而不是这里的主题。回拨代表不需要固定。 – 2014-12-02 14:49:30

+1

其他答案(以及我检查过的其他一些资源)确实提到过,如果回调委托被移动到不同的地址,那么当尝试调用它时,本地层会崩溃,因为它拥有的地址不再有效。还是我错了? – rmobis 2014-12-02 14:52:22

1

我想在你存储委托对象的时候会发生错误(但通常不会)。我们都知道,托管内存将由Garbage Collect进行安排。 (这意味着被管理对象的物理内存地址将被改变。)

成像有一个长时间的代理被本地代码调用,我们将代理设置为静态成员或类成员。但有时(我们不知道什么时候,我们只知道它会发生),GC安排了内存,并且委托的物理内存可能从0x000000A到0x0000010。但是本地代码对此一无所知,对于本地代码,它只知道永远调用0x000000A。 所以我们不仅要存储委托对象,而且要使用GCHandle.Alloc来告诉GC不要移动委托对象的物理内存。然后,本地代码在回调时会表现良好。

那么,因为GC没有频繁地安排管理内存,所以对于短期生命代表,即使你不叫GCHandle.Alloc,你的代码总是“做得好”,但有时它会发疯。 现在,你知道原因。

参考: http://dotnet.dzone.com/news/net-memory-control-use-gchandl

+0

当您使用GCHandle.Alloc时,您不需要固定委托 – 2013-10-12 10:41:08

1

我错了。 您不需要固定委托(另一方面,您不能固定委托,将会抛出异常(System.ArgumentException:Object包含非原始数据或非可读数据。)

参考:关于克里斯Brumme的博客http://blogs.msdn.com/cbrumme/archive/2003/05/06/51385.aspx

克里斯Brumme写道 http://social.msdn.microsoft.com/Forums/vstudio/en-US/bd662199-d150-4fbf-a5ee-7a06af0493bb/interop-pinning-and-delegates?forum=

详情: 按照同样的思路,管理代表可以被编排到非托管代码,在那里它们被暴露非托管函数指针。呼吁那些指针将执行非托管到受管理的转换;呼叫约定的改变;进入正确的AppDomain;和任何必要的参数编组。很明显,非托管函数指针必须引用一个固定地址。如果GC重新定位那将是一场灾难!这导致许多应用程序为委托创建一个固定句柄。这完全没有必要。非托管函数指针实际上是指我们动态生成的本地代码存根,以执行编组转换。该存根存在于GC堆外的固定存储器中。

但是,应用程序负责以某种方式延长委托的生命周期,直到不再有来自非托管代码的调用发生。本机代码存根的生存期与代理的生存期直接相关。一旦收集到委托,通过非托管函数指针进行的后续调用将崩溃或以其他方式破坏进程。在我们最近发布的版本中,我们添加了一个Customer Debug Probe,它允许您干净地检测代码中的这一点 - 这些都很常见 - 。如果您在开发过程中尚未开始使用客户调试探针,请查看!

相关问题