使用带代理的C ++函数后,C#app在退出时崩溃

时间:2014-07-14 16:02:03

标签: c# c++ delegates dllimport

我的C#app需要与用C ++编写的DLL对话。我没有这个DLL的代码,但我有一个演示应用程序的代码(也在C ++中)使用DLL并且工作。

这是来自C ++演示应用程序的有趣代码。它有两个函数,其中一个函数接受一个由4个委托组成的回调集。

typedef BOOL (CBAPI* OPENSCANNERSDK)(HWND hwnd, TCallBackSet* callbackSet, wchar_t* configPath);
typedef void (CBAPI* CLOSESCANNERSDK)();

typedef struct              TCallBackSet
{
    TOnScannerStatusEvent   scannerStatusEvent;
    TOnScannerNotifyEvent   scannerNotifyEvent;
    TOnRFIDStatusEvent      rfidStatusEvent;
    TOnRFIDNotifyEvent      rfidNotifyEvent;
} TCallBackSet;

typedef void        (cdecl* TOnScannerStatusEvent   )   (int scannerStatus);
typedef void        (cdecl* TOnScannerNotifyEvent)      (int scannerNotify, int lParam);
typedef void        (cdecl* TOnRFIDStatusEvent      )   (int rfidStatus);
typedef void        (cdecl* TOnRFIDNotifyEvent      )   (int rfidnotify);

所以,我必须调用OpenScannerSDK,传递带有指向某些函数的回调集,做一些事情,最后调用CloseScannerSDK。

我已经在我的C#app中声明了这一点:

[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "_OpenScannerSDK")]
extern public static bool OpenScannerSDK(IntPtr hwnd, TCallBackSet callbackSet, 
  [MarshalAs(UnmanagedType.LPWStr)]string configPath);

[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "_CloseScannerSDK")]
extern public static void CloseScannerSDK();

[StructLayout(LayoutKind.Sequential)]
public class TCallBackSet
{
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void TOnScannerStatusEvent(int scannerStatus);

    [MarshalAs(UnmanagedType.FunctionPtr)]
    public TOnScannerStatusEvent ScannerStatusEvent;

    (I have removed the other 3 callbacks for brevity)
}

最后我像这样使用库:

var callback = new TCallBackSet() { ... set the delegates ... }
OpenScannerSDK(IntPtr.Zero, callback, ".");
... do other stuff...
CloseScannerSDK();

所有这一切似乎都有效 - OpenScannerSDK和CloseScannerSDK以及我在它们之间使用的所有其他工作正常。问题是,只要应用程序尝试退出,我就会在KERNELBASE.dll中获得一个APPCRASH。我没有在崩溃报告文件中看到任何相关信息。我注意到如果我不调用OpenScannerSDK,而只是调用与委托无关的DLL的其他一些函数,则APPCRASH不会发生。

我也为代表们尝试了GC.KeepAlive,没有效果。

3 个答案:

答案 0 :(得分:1)

几年前,我有一个类似的问题将其他C DLL移植到C#。

C#中的函数指针表示为委托。在内部,委托是类实例,它们由GC收集,与收集其他对象的方式相同。

可能在你的DLL中有一些"停止" API,使其停止调用C ++函数指针。您必须在关闭应用程序之前调用它。

您的应用程序可能会收集委托对象,当C ++ DLL尝试从非托管代码调用时,会找到无效的对象引用。

将这些委托的C#中的引用保留在私有字段中是有意义的,以避免在应用程序运行时收集它们。此问题的sympton是应用程序间歇性崩溃。

希望这有帮助。

答案 1 :(得分:0)

经过进一步测试,我发现实际上OpenScannerSDK总是返回True,这让我相信它工作正常,但情况可能并非如此。我做了几个测试,包括传递一个没有任何成员的TCallBackSet结构,OpenScannerSDK仍然返回True。

所以似乎问题出在TCallBackSet和这些委托上。那里出了点问题。我尝试传递CallBackSet w和w / o ref,没有区​​别。 C ++中注意到的int与C#中的int不同,因此更改函数签名以使用short而不是int。这些都没有任何区别。

答案 2 :(得分:0)

对任何有兴趣的人来说,这是最终的解决方案。

首先,CallBackSet的定义如下:

[StructLayout(LayoutKind.Sequential)]
public class CallBackSet
{
    public IntPtr ScannerStatusEvent;
    public IntPtr ScannerNotifyEvent;
    public IntPtr RfidStatusEvent;
    public IntPtr RfidNotifyEvent;
}

然后:

OnScannerStatusEvent onScannerStatus = (ScannerStatus status) => {...};
OnScannerNotifyEvent onScannerNotify = (ScannerNotify notify, short lparam) => {...};
OnRfidStatusEvent onRfidStatus = (RfidStatus status) => {...};
OnRfidNotifyEvent onRfidNotify = (RfidNotify notify) => {...};

GCHandle.Alloc(onScannerStatus);
GCHandle.Alloc(onScannerNotify);
GCHandle.Alloc(onRfidStatus);
GCHandle.Alloc(onRfidNotify);

callbackSet = new CallBackSet()
{
    RfidNotifyEvent = Marshal.GetFunctionPointerForDelegate(onRfidNotify),
    RfidStatusEvent = Marshal.GetFunctionPointerForDelegate(onRfidStatus),
    ScannerNotifyEvent = Marshal.GetFunctionPointerForDelegate(onScannerNotify),
    ScannerStatusEvent = Marshal.GetFunctionPointerForDelegate(onScannerStatus)
};

callbackPtr = Marshal.AllocHGlobal(Marshal.SizeOf(callbackSet));
Marshal.StructureToPtr(callbackSet, callbackPtr, false);

OpenScannerSDK(IntPtr.Zero, callbackPtr, ".");