C# delegate for C++ callback - c#

I think I have basically understood how to write c# delegates for callbacks, but this one is confusing me.
The c++ definition is as follows:
typedef int (__stdcall* Callback)(
long lCode,
long lParamSize,
void* pParam
);
and my c# approach would be:
unsafe delegate int CallbackDelegate (int lCode, int lParamSize, IntPtr pParam);
Although this seems to be incorrect, because I get a PInvokeStackInbalance error, which means my definition of the delegate is wrong.
The rest of the parameters of the function are strings or ints, which means they cannot cause the error, and if I just pass a IntPtr.Zero instead of the delegate (which would mean I'm pointing to a non-existent callback function) I get an AccessViolation error, which makes sense aswell.
What am I doing wrong?
EDIT:
The full c++ function is:
int
__stdcall
_Initialize (
const char* FileName,
Callback cbFunction,
int Code,
const char* Name,
unsigned int Option,
unsigned int Option2
);
My c# version is:
[DllImport("MyDll.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int _Initialize (string FileName, CallbackDelegate cbFunction, int Code, string Name, uint Options, uint Options2);
The function is (for testing) just called inside of the main routine of a console application:
static void Main(string[] args)
{
CallbackDelegate del = new CallbackDelegate(onCallback);
Console.Write(_Initialize("SomeFile.dat", del, 1000, "", 0, 4));
Console.Read();
}
where onCallback is this:
static int onCallback(int lCode, int lParamSize, IntPtr pParam)
{
return 0;
}
I get the PInvokeStackInbalance error on the line where I call _Initialize, if I pass a IntPtr.Zero instead of the delegate, and change the definition of the function to IntPtr instead of CallbackDelegate then I get a AccessViolationException.

I added your code to my current project where I'm doing a lot of C#/C++ interop in VS2012. And while I hate to use the "it worked on my machine", it worked fine for me. The code as I ran it is listed out below just to illustrate that I didn't make and fundamental changes.
My suggestion is that you build a new native dll with a stub function for _Initialize such as below and see if it works when you can control both sides of the interface. If that works but the real dll doesn't, it comes down to either compiler settings on the native side or their is a bug on the native side and it is stomping on the stack.
extern "C" {
typedef int (__stdcall* Callback)(long lCode,long lParamSize,void* pParam );
TRADITIONALDLL_API int __stdcall _Initialize (const char* FileName,Callback cbFunction, int Code,
const char* Name,unsigned int Option,unsigned int Option2)
{
cbFunction(0, 0, nullptr);
return 0;
}
}
On the C# side, I added the declarations to my interface class:
public delegate int CallbackDelegate(int lCode, int lParamSize, IntPtr pParam);
[DllImport("XXX.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int _Initialize(string FileName, CallbackDelegate cbFunction, int Code, string Name, uint Options, uint Options2);
And then in my main:
private int onCallback(int lCode, int lParamSize, IntPtr pParam)
{
return 0;
}
XXXInterface.CallbackDelegate del = new XXXInterface.CallbackDelegate(onCallback);
Console.Write(XXXInterface._Initialize("SomeFile.dat", del, 1000, "", 0, 4));

A .NET long is 64bits. A C++ long may be just 32bits. Check with the C++ compiler which compiled that definition as to what size it's longs are.

[UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)]
unsafe delegate int CallbackDelegate (int lCode, int lParamSize, IntPtr pParam);
If .NET assumes cdecl instead of stdcall, your stack will most assuredly be hosed.

Related

Passing a struct pointer in C# interop results in NULL

I am building a managed DLL for use in unmanaged environment (C/C++ app - FreeRDP). Interop works fine in most cases, but in one particular I am not able to pass a pointer to struct.
In the API I have a struct:
typedef struct _IWTSListenerCallback IWTSListenerCallback;
struct _IWTSListenerCallback
{
UINT(*OnNewChannelConnection)(IWTSListenerCallback* pListenerCallback,
IWTSVirtualChannel* pChannel,
BYTE* Data,
BOOL* pbAccept,
IWTSVirtualChannelCallback** ppCallback);
};
As well as a function I am calling:
UINT(*CreateListener)(IWTSVirtualChannelManager* pChannelMgr,
const char* pszChannelName,
ULONG ulFlags,
IWTSListenerCallback* pListenerCallback);
Both translated to C#:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate uint ListenerCallbackNewConnectionDelegate(IntPtr listenerCallback, IntPtr channel, [MarshalAs(UnmanagedType.LPArray)] byte[] data, IntPtr accept, ref IntPtr channelCallback);
[StructLayout(LayoutKind.Sequential)]
public struct IWTSListenerCallback
{
[MarshalAs(UnmanagedType.FunctionPtr)]
public ListenerCallbackNewConnectionDelegate OnNewChannelConnection;
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate uint ChannelManagerCreateListenerDelegate(IntPtr channelManager, [MarshalAs(UnmanagedType.LPStr)] string channelName, ulong flags, IntPtr listenerCallback);
[MarshalAs(UnmanagedType.FunctionPtr)]
public ChannelManagerCreateListenerDelegate CreateListener;
And execution code:
var callback = new IWTSListenerCallback();
callback.OnNewChannelConnection = NewChannelConnection;
var pCallback = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IWTSListenerCallback)));
Marshal.StructureToPtr(callback, pCallback, false);
var ret = channelManager.CreateListener(pChannelManager, "TestChannel", 0, pCallback);
And while pChannelManager (which is a pointer I obtain from unmanaged code calling my DLL) and the string are sent through without any problems, the pointer I create here (pCallback) is assigned successfuly in C#, but it results in a NULL in unmanaged code.
I assume the problem is either with how I defined the struct, or how I defined the function (although the function is being called successfuly in unmanaged code). I use the method to create the pointer in exact same way as in another part of the DLL, and it works perfectly fine there when passed to unmanaged function.
EDIT:
By #jdweng suggestion:
[StructLayout(LayoutKind.Sequential)]
public struct TestCall
{
public IntPtr channelManager;
[MarshalAs(UnmanagedType.LPStr)]
public string channelName;
public ulong flags;
public IntPtr listenerCallback;
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate uint ChannelManagerCreateListenerDelegate(IntPtr testStructure);
var test = new TestCall();
test.channelManager = pChannelManager;
test.channelName = "TestChannel";
test.flags = 0;
test.listenerCallback = pCallback;
var pTest = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FreeRDPTypes.TestCall)));
Marshal.StructureToPtr(test, pTest, false);
var ret = channelManager.CreateListener(pTest);
Didn't work.
EDIT2: Workaround! Only if you have access to original unmanaged code. I rearranged the function arguments so the structure pointers are first, like this:
UINT(*CreateListener)(IWTSVirtualChannelManager* pChannelMgr,
IWTSListenerCallback* pListenerCallback,
const char* pszChannelName,
ULONG ulFlags);
And it works! Probably a problem with offset.
It was a matter of offset. C/C++ ULONG was typedef unsigned long which I wrongly assumed corresponded to C# ulong, but in fact the first one is 4 bytes in Visual, while the other is 8 bytes, which resulted in 4 bytes offset. Fixed by changing ulong to uint and adding [MarshalAs(UnmanagedType.U4)] for good measure. Final look of the function I was calling inside C#:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate uint ChannelManagerCreateListenerDelegate(IntPtr channelManager, [MarshalAs(UnmanagedType.LPStr)] string channelName, [MarshalAs(UnmanagedType.U4)] uint flags, IntPtr listenerCallback);

Calling Unmanaged Code from Managed Code

Anyone know how i fix this error?
ERROR:
A call to PInvoke function has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.
I am using P/Invoke to call a native code from a managed code. Native code is being imported via dynamic link libraries.
Steps are
Locating .dll containing these function.
Loading .dll into memory
Locating the address of function in memory
Transforming this memory representation of object to a data format (marshalling)
DLL has a function with declaration
long __stdcall Connect (long,long,long,long,long,long,long,long);`
In my application i created a delegate
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate long delConnect(long a, long b, long c, long d, long e, long f, long g, long h);`
Then i created a class for loading dll and locating address of function in memory
`static class NativeMethods
{
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr hModule);
}`
Now when i am trying to use the function then i am getting error
IntPtr pDll = NativeMethods.LoadLibrary("serial.dll");
IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "_Connect#32");
delConnect connect = (delConnect)Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall,typeof(delConnect));
long check = connect(1, 0, 12, 9600, 0, 0, 8, 0);
bool result = NativeMethods.FreeLibrary(pDll);`
I have used "_Connect#32" instead of "Connect" in function name parameters of GetProcAddress method, because of name mangling.
When i am debugging i get this error at line containing statement
long check = connect(1, 0, 12, 9600, 0, 0, 8, 0);
The C/C++ long is 32 bit in Windows, so an int in .net. Try changing it (on Unix it's more like an IntPtr)
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int delConnect(int a, int b, int c, int d, int e, int f, int g, int h);
And perhaps instead of StdCall try Cdecl:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
For the DllImport of Windows API you should use
[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
So that if possible .NET uses the Unicode variant, and not the Ansi variant.
Is there a reason you're using LoadLibrary() rather than just PInvoking directly?
What happens if you try this instead:
[DllImport("serial.dll", EntryPoint = "_Connect#32", CallingConvention = CallingConvention.StdCall)]
static extern int DelConnect(int a, int b, int c, int d, int e, int f, int g, int h);
(Note C# int == C++ long, normally)

C# interop services - using C dll - void*

I'm having trouble interoperating with a DLL written in C. I'm not sure what type of param to put in place of void*
This is how the API of given DLL looks like:
POSNET_API POSNET_STATUS __stdcall POS_SetDeviceParam ( POSNET_HANDLE hDevice,
unsigned long paramCode,
void * paramValue
)
this is how I was trying to import it in C#:
[DllImport(EXT_DLL)]
private static extern int POS_SetDeviceParam(IntPtr hDevice, int POSNET_DEV_PARAM_IP, *type* paramValue);
in place of type I was putting:
[MarshalAs(UnmanagedType.LPStr)] string and other L*Str
[MarshalAs(UnmanagedType.LPArray)] char[] and other type of arrays including of type byte
IntPtr which where AllocHGlobal, GCHandle.Alloc allocated before
even preceeding the method with unsafe and type = void*, combining above alloc aso.
raw type: string, char[]...
I ran out of possibilities by myself.
paramValue should have a value of an IP in following format: "192.168.1.1" - this is how it looks like in a C demo:
string ip="10.33.44.6";
POS_SetDeviceParam(hDevice,POSNET_DEV_PARAM_IP,(void*)ip.c_str());
In C# code, the hDevice is not being initialized - POS_SetDeviceParam should initialize it with additional params.
Any suggestions are very welcome!
You will need to use this P/Invoke signature:
[DllImport(EXT_DLL)]
private static extern int POS_SetDeviceParam(
IntPtr hDevice,
int paramCode,
IntPtr paramValue
);
But you'll have to do some work to that string in order to pass it through the IntPtr paramValue argument.
Perhaps you can try using Marshal.StringToHGlobalAnsi() as that will give you an IntPtr you can use. If using this method though be sure to free the memory once you've finished with it.
Thank you guys for all the suggestions, the code below solved my problem!
[DllImport(EXT_DLL)]
private static extern int POS_SetDeviceParam(IntPtr hDevice, UInt32 POSNET_DEV_PARAM_IP, IntPtr paramValue);
void Test() {
POS_SetDeviceParam(new IntPtr(), 0x00020005, Marshal.StringToHGlobalAnsi("192.168.1.1"));
}

Argument passed incorrectly from C# code to C++ DLL

I use the following DllImport:
[DllImport(#"someDLL.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern UINT64 someFunc(int arga, int argb, int argc);
I'm calling the function as follows:
someFunc(0,0,1);
In h file i declare the function:
extern "C" __declspec(dllexport) UINT64 someFunc(int arga, int argb, int argc);
cpp:
UINT64 someFunc(int arga, int argb, int argc)
{
...
}
In the C++ code I receive weird values (such as 1218628, 20140292, 1219020).
Any idea why?
You didn't show the C++ code so I don't see the problem in your code. So, I tried recreating it myself. I created a C# WPF project which calls into a DLL.
C#:
[DllImport(#"c:\users\owner\documents\visual studio 2010\Projects\MyDll\Release\MyDll.dll",
CallingConvention = CallingConvention.Cdecl)]
private static extern UInt64 someFunc(int arga, int argb, int argc);
private void DoIt_Click(object sender, RoutedEventArgs e)
{
UInt64 val = someFunc(0, 0, 1);
ResultLabel.Content = val.ToString();
}
C++ DLL:
extern "C" __declspec(dllexport) unsigned __int64 someFunc(int arga, int argb, int argc)
{
CString s;
s.Format(L"%d\t%d\t%d", arga, argb, argc);
AfxMessageBox(s);
return arga + argb + argc;
}
The message box from C++ shows 0 0 1 as expected and the C# code gets 1 returned as expected.
Use __stdcall instead of __cdecl if you can, in __stdcall is the C DLL that performs stack cleanup and is the standard way windows C dlls are used.
It is also a good behaviour to specify the calling convention in your C code, both for readability and because C++ project settings can specify what calling convention use by default.
Try to set it __cdecl explicitly, maybe c++ compiler is compiling it using __stdcall.
extern "C" __declspec(dllexport) UINT64 __cdecl someFunc(int arga, int argb, int argc);

P/Invoke C# struct with strings to C void*

I'm having a problem creating a C# P/invoke wrapper around a third party C library. In particular, the library has a method with the signature
int command(SomeHandle *handle, int commandNum, void *data, int datasize);
It is a wildcard method that does different things depending on commandNum. data can be a pointer to anything, like a single integer, or a char[], or a struct of some kind (my problem).
I have declared the wrapper as follows:
[DllImport("LIBRARY.DLL", EntryPoint = "command")]
public static extern int Command(IntPtr Handle, int CommandNum, [In, Out] IntPtr Data, int DataSize);
Now, when i call it with an opcode to fill a byte[] it works:
//WORKS, Buffer contains "library 1.0" after the call
const int BUFFER_SIZE = 128;
byte[] Buffer = new byte[BUFFER_SIZE];
int BytesWritten = 0;
GCHandle BufferHandle = GCHandle.Alloc(Buffer, GCHandleType.Pinned);
try
{
BytesWritten = Command(MyHandle, GET_VERSION, BufferHandle.AddrOfPinnedObject(), BUFFER_SIZE);
}
finally
{
BufferHandle.Free();
}
However, when I try the same with a simple struct, I cannot make it work, no matter what I try. the struct looks like this:
public struct FormatInfoType
{
public int Format;
public IntPtr Name; //const char*
public IntPtr Extension; //const char*
}
Here I am supposed to fill "Format" with an int (say, 1) and then the call to "command(...)" is meant to give me back the name and extension fields
If I pass this struct, the code compiles and runs correctly, but the values in the struct are never modified. If I change the IntPtr's to Strings or StringBuilders (and I've tried a myriad of MarshalAs attributes), then I cannot get the IntPtr to the struct because the it becomes non-blittable and the GCHandle line throws an exception.
Any help on this would be greatly appreciated.
EDIT:
I've tried many ways to call command() with the structure, but currently it looks like this:
FormatInfoType f = new FormatInfoType();
f.Format = 1;
f.Name = IntPtr.Zero;
f.Extension = IntPtr.Zero;
GCHandle fHandle = GCHandle.Alloc(f, GCHandleType.Pinned);
try
{
Command(MyHandle, GET_FORMAT_INFO, fHandle.AddrOfPinnedObject(), Marshal.SizeOf(f));
}
finally
{
fHandle.Free();
}
You can overload p/invoke signatures, try:
[DllImport("LIBRARY.DLL", EntryPoint = "command")]
public static extern int Command(IntPtr Handle, int CommandNum, ref FormatInfoType Data, int DataSize);

Categories