“xxxxInvoke”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃、损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用。
“xxxx::Invoke”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃、损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用。
前言:最近在做c#的项目中,出现了下图异常,有异常信息看出,是callback回调函数处引发的异常
托管和非托管代码的区别
-
托管的代码就是把有关底层的操作(比如内存申请,内存释放,垃圾回收等)封装起来了,不能直接进行内存的读取释放之类的和硬件相关的操作。
优点:安全,不会出现内存泄漏之类问题。
缺点:不能直接读取内存,性能上有损失,使用不灵活。
-
非托管的代码刚好相反,可以直接直接进行内存的读取释放之类的和硬件相关的操作。
优点:性能高,对开发人员的要求也高。
缺点:容易出现内存管理的问题。
例子:最直观的就是c#不推荐使用指针,而c++就可以使用指针来直接读取内存; c#使用垃圾回收,c++要手动的释放对象……
异常分析
通过对托管和非托管代码的理解,想必对此异常有一点眉目了。
这种错误,一般出现在托管代码调用非托管代码的过程中,如C#程序要调用c++的某个函数,而c++函数正好有个callback函数。当C#调用完之后,GC有可能会把这个callback给回收掉,如果C++接下来的某个函数中也同样使用了这个callback,就会抛出上述异常。因为C++是非托管的,GC又不知道它接下来还要使用callback所以就把它回收掉了。
解决
微软建议我们使用GC.KeepAlive()方法解决,官网例子
KeepAlive 方法的目的是确保对对象的引用存在,该对象有被垃圾回收器过早回收的危险。这种现象可能发生的一种常见情形是,当在托管代码或数据中已没有对该对象的引用,但该对象仍然在非托管代码(如 Win32 API、非托管 DLL 或使用 COM 的方法)中使用。 另一种过早发生垃圾回收的情形是,在一个方法中创建并使用一个对象。此时,当对对象的某个成员的调用仍在执行时,可能会对该对象进行回收,如第一个代码示例所示。 此方法引用 obj,从而使该对象从例程开始到调用此方法的那个位置(按执行顺序)均不符合进行垃圾回收的条件。在 obj 必须可用的指令范围的结尾(而不是开头)编写此方法的代码。 KeepAlive 方法除了延长作为参数传递的对象的生存期之外,不会执行任何操作,也会不产生任何其他副作用。
Structs.IDataCallback callback; ...... public void StartHsFundFlow(int length) { ..... ..... ..... for (int i = 0; i < hsFundFlowsCodes.Length; i++) { HSFundFlowsSerialID[i] = EmQuantAPI.csq(hsFundFlowsCodes[i], HSFundFlowsConstant.CSQ_HSFUNDFLOWS, "Pushtype=" + (int)OptionEnums.Pushtype.ALL, callback, IntPtr.Zero); ... ... GC.KeepAlive(callback); } ....... ...... }
我们需要在可用的指令范围的结尾 ,可以说方法的末尾处,加上GC.KeepAlive(obj);
参考博文: