最近在做项目的时候,出现了几次 对“xxxx::Invoke”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃、损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。
异常。
这种错误,一般出现在托管代码调用非托管代码的过程中,如C#程序要调用c++的某个函数,而这个函数正好有个callback函数。当C#调用完之后,GC有可能会把这个callback给回收掉,如果C++接下来的某个函数中也同样使用了这个callback,就会抛出上述异常。因为C++是非托管的,GC又不知道它接下来还要使用callback所以就把它回收掉了。
这个时候,微软就建议我们使用GC.KeepAlive()
方法解决,参考官网例子
KeepAlive 方法的目的是确保对对象的引用存在,该对象有被垃圾回收器过早回收的危险。
- 这种现象可能发生的一种常见情形是,当在托管代码或数据中已没有对该对象的引用,但该对象仍然在非托管代码(如 Win32 API、非托管 DLL 或使用 COM 的方法)中使用。
- 另一种过早发生垃圾回收的情形是,在一个方法中创建并使用一个对象。此时,当对对象的某个成员的调用仍在执行时,可能会对该对象进行回收,如第一个代码示例所示。此方法引用 obj,从而使该对象从例程开始到调用此方法的那个位置(按执行顺序)均不符合进行垃圾回收的条件。在 obj 必须可用的指令范围的结尾(而不是开头)编写此方法的代码。
KeepAlive 方法除了延长作为参数传递的对象的生存期之外,不会执行任何操作,也会不产生任何其他副作用。
MSDN上有官方例子可以参考下。
在这里贴出我的代码:
private InteropHelper.MKDriverAcceptGatewayConnectCallback connectCallback;
private InteropHelper.MKDriverAcceptGatewayErrorCallback errorCallback;
public void cbMKDriverAcceptGatewayConnect(IntPtr dref, IntPtr sref, IntPtr addr, int socklen)
{
//...
}
public void cbMKDriverAcceptGatewayErrorCallback(IntPtr dref, int error)
{
//...
}
private void XtraForm1_Load(object sender, EventArgs e)
{
connectCallback = new InteropHelper.MKDriverAcceptGatewayConnectCallback(cbMKDriverAcceptGatewayConnect);
errorCallback = new InteropHelper.MKDriverAcceptGatewayErrorCallback(cbMKDriverAcceptGatewayErrorCallback);
drvRef = InteropHelper.MKDriverCreate();
if (drvRef == IntPtr.Zero)
{
MessageBox.Show("加载接口失败");
}
else
{
InteropHelper.MKDriverSetCb(drvRef, connectCallback, errorCallback);
GC.KeepAlive(connectCallback);//关键的两句
GC.KeepAlive(errorCallback);//关键的两句
//如果没有上面的这两句KeepAlive,MKDriverSetCb调用完之后,两个callback就被GC.Collect掉了,以后如果还有调用的话就会抛异常。
timer1.Enabled = true;
}
}
private void timer1_Tick(object sender, EventArgs e)
{
//间隔10ms
if (drvRef != IntPtr.Zero)
{
if (InteropHelper.MKDriverDispatch(drvRef, 0, 0) != 0)
{
//就是因为上面Load方法内两句GC.KeepAlive没有加,所以在这里会不定时抛出异常。谁知道搞C++的那帮人在MKDriverDispatch里也调用了callback。
GMessageBox.Wrong("接口授权过期");
return;
}
}
}
欢迎大家,评论交流。