Reputation: 14256
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
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
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
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
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
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