Jim Fell
Jim Fell

Reputation: 14256

Error Calling Unmanaged DLL Function from Managed Code

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.

Upvotes: 2

Views: 975

Answers (5)

Jim Fell
Jim Fell

Reputation: 14256

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

Upvotes: 0

Tergiver
Tergiver

Reputation: 14517

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.

Upvotes: 0

Ana Betts
Ana Betts

Reputation: 74654

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.

Upvotes: 0

Ben Voigt
Ben Voigt

Reputation: 283634

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);

Upvotes: 0

Hans Passant
Hans Passant

Reputation: 941465

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.

Upvotes: 3

Related Questions