从托管代码调用非托管 DLL 函数时出错

发布于 2024-10-13 04:16:32 字数 1347 浏览 2 评论 0原文

我已经用非托管视觉 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

北恋 2024-10-20 04:16:32

基本上有什么问题。您声明了一个委托,就好像该函数是一个回调一样。看起来根本不像回调,它看起来像是您应该使用 [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.

雨后彩虹 2024-10-20 04:16:32

WINAPI 宏更改了调用约定。

尝试

[UnmanagedFunctionPointer(CallingConvention = CallingConvention.StdCall]
public delegate Int32 ZBNConnectDelegate(Byte dev, IntPtr hWnd, USBCallbackDelegate rfn, NotifyCallbackDelegate nfn, Int32 DevType, Byte[] DevAddr, ZBdebugCallbackDelegate dfn);

The WINAPI macro changes the calling convention.

Try

[UnmanagedFunctionPointer(CallingConvention = CallingConvention.StdCall]
public delegate Int32 ZBNConnectDelegate(Byte dev, IntPtr hWnd, USBCallbackDelegate rfn, NotifyCallbackDelegate nfn, Int32 DevType, Byte[] DevAddr, ZBdebugCallbackDelegate dfn);
硪扪都還晓 2024-10-20 04:16:32

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.

浪荡不羁 2024-10-20 04:16:32

如果您可以在运行时从 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。

If you can detect the correct DLL at runtime from the C# side, I would use two DllImport declaractions:

[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(...);
}

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.

戏舞 2024-10-20 04:16:32

事实证明,将 WINAPI 添加到函数声明和定义中会导致 DLL 中的函数名称被破坏。不幸的是,需要 WINAPI 来保持与已部署的应用程序的兼容性。修复方法是向链接器添加额外的导出:

#pragma comment(linker, "/EXPORT:ZBNConnect=_ZBNConnect@28")

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:

#pragma comment(linker, "/EXPORT:ZBNConnect=_ZBNConnect@28")
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文