从托管代码调用非托管 DLL 函数时出错
我已经用非托管视觉 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 = "Object reference not set to an instance of an目的。”
奇怪的是,如果我从 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 调用。
I've written a DLL in unmanaged visual C++, and I'm having a little trouble getting it to work with both C# and C++ applications. Here's what the prototype in the C++ DLL looks like:
extern "C" __declspec(dllexport) int WINAPI ZBNConnect( UCHAR dev, LPARAM hWnd, ZBCallbackFn rfn, ZBCallbackFn nfn, int DevType, byte * DevAddr, ZBCallbackFn dfn );
My C# application can link to the function, no problem, but when it tries to call the function an exception is thrown:
catch (Exception e) { /* ... */ }
e.Message = "Object reference not set to an instance of an object."
Oddly, if I take WINAPI
out of the prototype in the DLL, and recompile, the C# application calls the function without any problems. Unfortunately, the WINAPI
must remain because that is how the function is defined in the C++ application.
The function is currently prototyped in the C# application like this:
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));
How can I modify the C# application to get this working? Thanks.
EDIT: Additional Information
A static link ([DllImport...]
) is not an option because depending on which hardware is attached to the system a different DLL that supports the attached hardware is loaded at run-time. Both DLLs have the same API calls.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
基本上有什么问题。您声明了一个委托,就好像该函数是一个回调一样。看起来根本不像回调,它看起来像是您应该使用 [DllImport] 声明的东西。要使其像您一样工作,您必须调用 LoadLibrary 和 GetProcAddress()。 [DllImport] 在幕后做了什么。我没看到你用那个。
Something is basically wrong. You declared a delegate, as though the function is a callback. Doesn't look like a callback at all, it look like something you should declare with [DllImport]. To make it work like you did, you'd have to pinvoke LoadLibrary and GetProcAddress(). What [DllImport] does under the hood. I don't see you using that.
WINAPI
宏更改了调用约定。尝试
The
WINAPI
macro changes the calling convention.Try
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 很可能会给您带来问题,因为您无法自定义事物的编组方式。
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++ UCHARs (dev) are 1-byte values and C# uint's are 4. You're trashing the stack when you make the native call, and for some reason, WINAPI is letting you get away with it. Also, using the delegate like this instead of doing the normal P/Invoke DllImport is most likely going to cause you problems since you can't customize how things are marshaled.
如果您可以在运行时从 C# 端检测到正确的 DLL,我将使用两个 DllImport 声明:
注意
P/Invoke 是通过动态加载来处理的。如果您从不调用 ZBNConnect1,则永远不会加载 Dll1.dll,反之亦然。
更新
将 EntryPoint= 添加到 DllImport。
If you can detect the correct DLL at runtime from the C# side, I would use two DllImport declaractions:
Note
P/Invoke is handed by dynamic loading. If you never call ZBNConnect1, Dll1.dll is never loaded and visa versa.
Updated
Added EntryPoint= to DllImport.
事实证明,将
WINAPI
添加到函数声明和定义中会导致 DLL 中的函数名称被破坏。不幸的是,需要WINAPI
来保持与已部署的应用程序的兼容性。修复方法是向链接器添加额外的导出:It turned out that adding
WINAPI
to the function declaration and definition was causing the function name in the DLL to be mangled. Unfortunately,WINAPI
was required to maintain compatibility with applications already deployed. The fix was to add an extra export to the linker: