我的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,没有效果。
答案 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, ".");