我的示例是在托管代码中使用非托管代码。
如果我在方法中实例化一个类级字段,GC将在收集我的类的实例之前收集此字段,如下所示:
public class SpyAgent
{
public delegate int WindowProcedureDelegate(int Wnd, uint Msg, int WParam, int LParam);
private WindowProcedureDelegate _windowProcedure;
public void SpyAgent()
{
}
...
public void SomeRandomMethod()
{
...
//GC will collect this instance before SpyAgent dispose.
_windowProcedure = new WindowProcedureDelegate(WindowProcedure);
SetWindowLong(window.Handle, (IntPtr)_windowProcedureIndex, _windowProcedure);
}
[DllImport("user32.dll")]
private static extern IntPtr SetWindowLong(IntPtr hWnd, IntPtr nIndex, WindowProcedureDelegate newProc);
...
}
使用系统一分钟后,我收到以下错误:
托管调试助手'CallbackOnCollectedDelegate'已检测到 一个问题 'C:\ Acaz \ Supervise.Bootstrap \ Supervise.Bootstrap \ Supervise.Bootstrap \ Supervise.Bootstrap \ CONTOSO \ BIN \调试\ Contoso.vshost.exe'。
附加信息:对垃圾收集的类型委托进行了回调 'Supervise.Application!Supervise.Application.SpyAgent + WindowProcedureDelegate ::调用'。 这可能会导致应用程序崩溃,损坏和数据丢失。什么时候 将代理传递给非托管代码,他们必须保持活着 管理应用程序,直到它保证永远不会 调用。
但是当我在构造函数中实例化委托时,我没有更多的错误。
public class SpyAgent
{
public delegate int WindowProcedureDelegate(int Wnd, uint Msg, int WParam, int LParam);
private WindowProcedureDelegate _windowProcedure;
public void SpyAgent()
{
//This instance will be collected by GC just when SpyAgent start to being collected too.
_windowProcedure = new WindowProcedureDelegate(WindowProcedure);
}
...
public void SomeRandomMethod()
{
...
SetWindowLong(window.Handle, (IntPtr)_windowProcedureIndex, _windowProcedure);
}
[DllImport("user32.dll")]
private static extern IntPtr SetWindowLong(IntPtr hWnd, IntPtr nIndex, WindowProcedureDelegate newProc);
...
}
为什么会这样?为什么在收集类之前收集在方法中初始化类级别的字段。没有意义,因为该字段是在类级别声明的,可以用于整个类。我将引用存储在类级别的项目中。
为什么当我在构造函数中初始化字段时,在收到类之前才收集字段?
垃圾收集器与方法和构造函数中初始化的对象的行为有何不同?
答案 0 :(得分:4)
代码段似乎与您遇到问题的真实代码非常不匹配。但核心问题是本机代码依赖于委托对象。垃圾收集器可以不看到该依赖关系,它对GetWindowLong()一无所知。这使得很有可能过早收集委托对象,最常见的是在下一次收集期间。当Windows传递消息时,这将导致硬崩溃,当您从调试器运行时,Managed Debugger Assistant会在它产生一个响亮的不可诊断的爆炸之前告诉您它。
与.NET Framework一样,您不应该自己编写此代码。对窗口进行子类化是一个非常常见的需求,Winforms和WPF也需要这样做。而不是从scatch中转移自己的类,而是从NativeWindow class派生自己的类。覆盖WndProc()
方法以实现您自己的消息处理。当您准备子窗口时,调用其AssignHandle()
方法。当您看到WM_NCDESTROY消息时,在WndProc覆盖中调用ReleaseHandle()
,该消息是在窗口过期之前发送到窗口的最后一条消息。 NativeWindow负责委托管道。例如:
class SpyAgent : NativeWindow {
public SpyWindow(IntPtr hWnd) {
this.AssignHandle(hWnd);
}
protected override void WndProc(ref Message m) {
if (m.Msg == 130) this.ReleaseHandle();
// Your code here
//...
base.WndProc(ref m);
}
}
答案 1 :(得分:0)
无法保证调用终结器的顺序。这两个对象都是因为无根并且有资格同时进行垃圾回收。你应该阅读关于“复活”的GC概念,其中一个没有生根但仍然可以从终结器队列中到达的对象再次生根(通过提供对有根对象的引用)。
实际上,构造顺序和内存顺序可能会影响终结顺序,但这没有记录。不要依赖它,因为它可以在.NET版本之间进行更改。
答案 2 :(得分:0)
我发现了这个问题。
是因为我多次调用SomeRandom方法。每当我对该字段执行新的委托归属时,我都丢失了过去的引用,然后GC收集了这个过去的实例,因为没有引用它。 :)