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

时间:2011-01-21 22:50:03

标签: c# c++ 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 =“对象引用未设置为对象的实例。”

奇怪的是,如果我从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调用。

5 个答案:

答案 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")