我在非托管可视化C ++中编写了一个DLL,我在使用C#和C ++应用程序时遇到了一些麻烦。以下是C ++ DLL中的原型:
extern "C" __declspec(dllexport) int WINAPI ZBNConnect( UCHAR dev, LPARAM hWnd, ZBCallbackFn rfn, ZBCallbackFn nfn, int DevType, byte * DevAddr, ZBCallbackFn dfn );
我的C#应用程序可以链接到该函数,没问题,但是当它试图调用该函数时会抛出异常:
catch (Exception e) { /* ... */ }
e.Message =“对象引用未设置为对象的实例。”
奇怪的是,如果我从DLL中的原型中取出WINAPI
并重新编译,C#应用程序会毫无问题地调用该函数。不幸的是,WINAPI
必须保留,因为这就是在C ++应用程序中定义函数的方式。
该函数目前在C#应用程序中进行了原型设计,如下所示:
public delegate int ZBNConnectDelegate(uint dev, IntPtr hWnd, USBCallbackDelegate rfn, NotifyCallbackDelegate nfn, uint DevType, byte[] DevAddr, ZBdebugCallbackDelegate dfn);
public ZBNConnectDelegate ZBNConnect;
procName = "ZBNConnect";
fUintPtr = Kernel32.GetProcAddress(dllHandle, procName);
if (fUintPtr == UIntPtr.Zero)
{
throw new ArgumentException(procName);
}
fIntPtr = unchecked((IntPtr)(long)(ulong)fUintPtr);
ZBNConnect = (ZBNConnectDelegate)Marshal.GetDelegateForFunctionPointer(fIntPtr, typeof(ZBNConnectDelegate));
如何修改C#应用程序以使其正常工作?感谢。
编辑:其他信息
静态链接([DllImport...]
)不是一个选项,因为根据连接到系统的硬件,在运行时加载支持附加硬件的不同DLL。这两个DLL都具有相同的API调用。
答案 0 :(得分:3)
基本上是错的。您声明了一个委托,就好像该函数是一个回调。看起来根本不像回调,它看起来像你应该用[DllImport]声明的东西。为了使它像你一样工作,你必须pinvoke LoadLibrary和GetProcAddress()。什么[DllImport]在幕后做什么。我没有看到你使用它。
答案 1 :(得分:0)
WINAPI
宏更改了调用约定。
尝试
[UnmanagedFunctionPointer(CallingConvention = CallingConvention.StdCall]
public delegate Int32 ZBNConnectDelegate(Byte dev, IntPtr hWnd, USBCallbackDelegate rfn, NotifyCallbackDelegate nfn, Int32 DevType, Byte[] DevAddr, ZBdebugCallbackDelegate dfn);
答案 2 :(得分:0)
extern "C" __declspec(dllexport) int WINAPI ZBNConnect( UCHAR dev, LPARAM hWnd, ZBCallbackFn rfn, ZBCallbackFn nfn, int DevType, byte * DevAddr, ZBCallbackFn dfn );
public delegate int ZBNConnectDelegate(uint dev, IntPtr hWnd, USBCallbackDelegate rfn, NotifyCallbackDelegate nfn, uint DevType, byte[] DevAddr, ZBdebugCallbackDelegate dfn);
C ++ UCHAR(dev)是1字节值,C#uint是4。当你进行本机调用时,你正在废弃堆栈,由于某种原因,WINAPI让你逃脱它。此外,使用像这样的委托而不是正常的P / Invoke DllImport很可能会导致你的问题,因为你不能自定义封送的方式。
答案 3 :(得分:0)
如果你可以在C#端运行时检测到正确的DLL,我会使用两个DllImport声明:
[DllImport("Dll1.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "ZBNConnect")]
extern static int ZBNConnect1(...)
[DllImport("Dll2.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "ZBNConnect")]
extern static int ZBNConnect2(...)
public static int ZBNConnect(...)
{
if (UseDll1)
return ZBNConnect1(...);
return ZBNConnect2(...);
}
注意强>
P / Invoke由动态加载交付。如果您从未调用ZBNConnect1,则永远不会加载Dll1.dll,反之亦然。
<强>更新强>
将EntryPoint =添加到DllImport。
答案 4 :(得分:0)
事实证明,在函数声明和定义中添加WINAPI
会导致DLL中的函数名被破坏。遗憾的是,WINAPI
需要与已部署的应用程序保持兼容。解决方法是向链接器添加额外的导出:
#pragma comment(linker, "/EXPORT:ZBNConnect=_ZBNConnect@28")