从C DLL调用C#回调函数时崩溃

4
我正在Windows CE 6上编写一个C#应用程序来监视3G调制解调器。该应用程序将调用C DLL中的函数来访问调制解调器。
在启动时,C#应用程序将调用此函数以创建新连接:
[DllImport("swmodem.dll", CallingConvention = CallingConvention.Winapi)]
      public static extern int CreateDataConnection(EVENT_CALLBACK callback);

EVENT_CALLBACK 的定义如下:
public delegate void EVENT_CALLBACK(int e, IntPtr data);

一个数据结构也被定义了:
[StructLayout(LayoutKind.Sequential)]      
public struct ECIO_INFO
{
        public UInt32 ecio1;    /*!< Primary scramble code */
        public UInt32 ecio2;    /*!< Received signal code power */
        public UInt32 ecio3;    /*!< Energy per chip per power density */
}

在 C DLL 中,CreateDataConnection() 函数中传递了一个函数指针,用于更新调制解调器状态。
int CreateDataConnection(EVENT_CALLBACK ecb)
{
    .
    .               
    fEventCallback = ecb;

    // Create a connection
    .
    .
}

创建连接后,DLL将调用回调函数来更新调制解调器的状态,例如EC/IO(接收到的导频能量比)。
基本上,当ECIO更改时,将调用回调函数将ECIO数据传递给C#应用程序:
在C DLL中:
void ProcessNotification(EVENT_CALLBACK fEventCallback)
{
    ECIO_INFO ecio_info;

        ecio_info.ecio1 = ecio_info.ecio2 = ecio_info.ecio3 = 0;
        if(data.nNumOfCells>0)
            ecio_info.ecio1 = data.arCellInfo[0].nEcIo;
        if(data.nNumOfCells>1)
            ecio_info.ecio2 = data.arCellInfo[1].nEcIo;
        if(data.nNumOfCells>2)
            ecio_info.ecio3 = data.arCellInfo[2].nEcIo;

        if(data.nNumOfCells>0)
            fEventCallback(ME_RSCP_ECIO, &ecio_info);
}

在 C# 应用程序中,回调函数的定义如下:
private void ModemEventCallback(int e, IntPtr data)
{
    .
    .

    Modem.ECIO_INFO new_reinfo = new Modem.ECIO_INFO();
    new_reinfo = (Modem.ECIO_INFO)Marshal.PtrToStructure(
       data, typeof(Modem.ECIO_INFO));
    .
    .
}

现在问题来了。程序启动时,一切正常,连接成功创建,EC/IO也在更新。但是运行几个小时后,EC/IO的更新停止了。经过测试,我发现当回调函数被调用时,更新就会停止。
fEventCallback(ME_RSCP_ECIO, &ecio_info);

我不知道这里出了什么问题。可能在C# DLL调用中传递函数指针的方式不正确,或者代码中隐藏着一些错误?

1
它被埋在我们看不到的代码中。委托对象可能正在被垃圾回收。GC无法从C代码中看到对它的引用。将对象存储在类的字段或静态变量中,以便GC可以看到它。 - Hans Passant
同意@Hans的说法,如果您不将委托存储在某个地方,它们将被收集,GC无法跟踪传递给extern C的引用。 - Felice Pollano
3个回答

3

试试这个

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void EVENT_CALLBACK(int e, IntPtr data);

这解决了我的问题。


3

由于回调函数只是C/C++中的指针,因此回调参数必须声明为IntPtr类型。创建EVENT_CALLBACK实例并确保它在程序运行期间一直存在。使用Marshal.GetFunctionPointerForDelegate方法将委托实例转换为IntPtr,并将得到的IntPtr传递给CreateDataConnection函数。

[DllImport("swmodem.dll", CallingConvention = CallingConvention.Winapi)]
      public static extern int CreateDataConnection(IntPtr callback);
... EVENT_CALLBACK c; c = new EVENT_CALLBACK( ... ); // 请确保该实例一直存在! ... CreateDataConnection(Marshal.GetFunctionPointerForDelegate(c));

1
我认为你必须使用GCHandl.AllocGCHandleType.Pinned,这样你就会告诉垃圾回收器即使应用程序中没有引用此对象的“根”,该对象也必须保留在内存中,且不能被压缩。

1
不需要固定。CLR为本地代码创建了一个特殊的thunk,Marshal.GetFunctionPointerForDelegate()也是如此。 固定将是积极有害的,因为它会保持固定状态很长时间。 必要的只是对委托对象的可见引用。 - Hans Passant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接