Pass callback argument from C# to unmanaged C++ - c#

I have some of unmanaged C++ dynamic library and C# GUI application, using it. I want to pass callback via parameters in some library provided methods. Is it possible to pass a callback to unmanaged C++ method from C#.
// unmanaged C++
typedef uint8_t (__stdcall *SomeCallback)();
MYDLL_API uint8_t someMethod(SomeCallback cb);
I'm trying to use library in this way:
// C# part
public delegate Byte SomeDelegate();
[DllImport("mylibraryname.dll")]
public static extern Byte someMethod(ref SomeDelegate pDelegate);
// actuak callback
Byte myCallback() {
// some code
}
...
// call unmanaged passing callback
static void Main(string[] args) {
someMethod(myCallback);
}
I receive error on compilation:
cannot convert from 'method group' to 'ref SomeDelegate
Am I totally wrong with my approach?

It is because you MUST put the ref modifier before the argument and that forces the it to be a variable. so:
change you extern to:
public static extern Byte someMethod([MarshalAs(UnmanagedType.FunctionPtr)]
ref SomeDelegate pDelegate);
and your call to:
SomeDelegate action = new SomeDelegate(myCallback);
someMethod(ref action);
UPDATE: And if you want to pass an argument to the callback (say an int):
public delegate Byte SomeDelegate([MarshalAs(UnmanagedType.I4)] int value);
[DllImport("mylibraryname.dll")]
public static extern Byte someMethod([MarshalAs(UnmanagedType.FunctionPtr)]
ref SomeDelegate pDelegate);
Byte MyMethod([MarshalAs(UnmanagedType.I4)] int value)
{
return (byte) (value & 0xFF);
}
and calling:
SomeDelegate action = new SomeDelegate(MyMethod);
someMethod(ref action);

Update to C# code for .NET 4.8:
public delegate Byte SomeDelegate(int value);
[DllImport("mylibraryname.dll")]
public static extern Byte someMethod(IntPtr pDelegate);
Byte MyMethod([MarshalAs(int value)
{
return (byte) (value & 0xFF);
}
calling:
SomeDelegate myCallback = new SomeDelegate(MyMethod);
someMethod(Marshal.GetFunctionPointerForDelegate(myCallback));

Related

C# 7.3 ref return with pinvoke

I would like to know what the difference is in using a ref return versus a pointer when doing pinvokes. The native method signature that I am calling is:
typedef struct Buffer {
const void* Data;
size_t Length;
} Buffer;
__declspec(dllexport) extern Buffer* GetNewBuffer();
On the C# end I have the Buffer struct mapped one to one.
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Buffer
{
public IntPtr Data;
public UIntPtr Length;
}
I also have two static methods that call the native function implemented like so:
[DllImport("testlib", EntryPoint = "GetNewBuffer")]
public static extern unsafe Buffer* GetNewBufferPointer();
[DllImport("testlib", EntryPoint = "GetNewBuffer")]
public static extern ref Buffer GetNewBufferReference();
These two methods compile down to the following MSIL:
.method public hidebysig static pinvokeimpl("testlib" as "GetNewBuffer" winapi)
valuetype TestInterop.Buffer* GetBufferPointer () cil managed preservesig
{
} // end of method Native::GetBufferPointer
.method public hidebysig static pinvokeimpl("testlib" as "GetNewBuffer" winapi)
valuetype TestInterop.Buffer& GetBufferReference () cil managed preservesig
{
} // end of method Native::GetBufferReference
Using these methods and accessing the returned structs members would look something like this:
unsafe
{
Buffer* bufferPointer = Native.GetBufferPointer();
var dataFromPointer = bufferPointer->Data;
}
ref Buffer bufferReference = ref Native.GetBufferReference();
var dataFromReference = bufferReference.Data;
Is there any difference between these two methods besides the different syntax used to store the return value and access its members? When using ref return with pinvoke, does the GC do anything special that I should be aware of?

PInvoke calling of function with wchar16_t parameters

I have a .dll with C++ function which takes const wchar16_t* parameters.
I'm trying to import and use it in c# with string, char array, and char array with additional '\0' char but I got no result.
When I check it in original c++ program in debug mode it have additional '\0' char at the end. What exact type should I use?
P.s. I'm not 100% sure that problems arises because of these parameters.
I would very appreciate and give many points to rep if someone could kindly look into small projects I attach illustrating the problem. C++ program works fine (we getlogin response), but in c# project OnLoginResponseCallback is never fire.
What I do in C#
[DllImport("ActiveTickServerAPI.dll", CharSet = CharSet.Unicode, EntryPoint = "?ATCreateLoginRequest##YA_K_KPB_W1P6AX00PAU_ATLOGIN_RESPONSE###Z#Z", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
private static extern ulong ATCreateLoginRequest(ulong sessionId, string user, string pwd, ATLoginResponseCallback onLoginResponse);
public delegate void ATLoginResponseCallback(ulong hSession, ulong hRequest, ATLOGINRESPONSE response);
public delegate void ATRequestTimeoutCallback(ulong origRequest);
static void Main(string[] args)
{
lastRequest = ATCreateLoginRequest(hSession, userId, pw, OnLoginResponseCallback);
bool rc = ATSendRequest(hSession, lastRequest, 3000, OnRequestTimeoutCallback);
}
static void OnLoginResponseCallback(ulong hSession, ulong hRequest, ATLOGINRESPONSE response)
{
Console.WriteLine("!THIS SHOULD FIRE, but fire only timeout callback");
}
[StructLayout(LayoutKind.Sequential)]
public struct ATLOGINRESPONSE
{
public byte loginResponse;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
public byte[] permissions;
public ATTIME serverTime;
}
[StructLayout(LayoutKind.Sequential)]
public struct ATTIME
{
public ushort year;
public ushort month;
public ushort dayOfWeek;
public ushort day;
public ushort hour;
public ushort minute;
public ushort second;
public ushort milliseconds;
}
in c++ it's work (getting login response). Here is function description from api docs:
ACTIVETICKSERVERAPI_API uint64_t ATCreateLoginRequest ( uint64_t session,
const wchar16_t * userid,
const wchar16_t * password,
ATLoginResponseCallback pCallback
)
working struct from c++
typedef struct _ATLOGIN_RESPONSE
{
ATLoginResponseType loginResponse;
uint8_t permissions[255];
ATTIME serverTime;
} ATLOGIN_RESPONSE, *LPATLOGIN_RESPONSE;
typedef struct _ATTIME
{
uint16_t year;
uint16_t month;
uint16_t dayOfWeek;
uint16_t day;
uint16_t hour;
uint16_t minute;
uint16_t second;
uint16_t milliseconds;
} ATTIME, *LPATTIME;
I downloaded your test project and immediately fixed the delegate declarations because they were wrong:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ATSessionStatusChangeCallback(ulong hSession, byte statusTyp);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ATLoginResponseCallback(ulong hSession, ulong hRequest, ref ATLOGINRESPONSE response);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ATRequestTimeoutCallback(ulong origRequest);
First time I ran the program I got:
SessionStatus - Connected
request timeout
!THIS SHOULD FIRE
SessionStatus - Connected
!THIS SHOULD FIRE
Second and subsequent times I got:
SessionStatus - Connected
!THIS SHOULD FIRE
No timeout. Why it behaved the way it did the first time is very hard to guess. You probably need to wait with the ATSendRequest() until you get confirmation of a proper login, something like that.
But clearly fixing the delegates took care of the problem, you are now getting the proper hSession value in the callback. It is not yet perfect, you need to ensure that they cannot be garbage collected. Store them in a static variable:
static ATSessionStatusChangeCallback statusCallback;
....
if (res == true) {
statusCallback = new ATSessionStatusChangeCallback(OnSessionStatusChangeCallback);
res = ATInitSession(sessionNumber, "activetick1.activetick.com",
"activetick2.activetick.com", 443, statusCallback, true);
}
Do the same thing on the 2 other ones.
The declaration of ATLOGINRESPONSE is almost certainly wrong.
public class ATLOGINRESPONSE
{
public byte loginResponse;
public byte[] permissions;
public ATTIME serverTime;
}
By declaring it as a class, you ensure that it will be marshalled by reference. But you preclude it ever being marshalled by value. Which might be fine. Anyway, I think I'd prefer to declare as a struct.
Further, the first two parameters look wrong to me. The first is probably an enum and should be declared as such. The second is a byte array but you need to specify how to marshal it. Like this:
[StructLayout(LayoutKind.Sequential)]
public struct ATLOGINRESPONSE
{
public ATLoginResponseType loginResponse; // you need to define the ATLoginResponseType enum
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
public byte[] permissions;
public ATTIME serverTime;
}
We don't know whether or not ATTIME is declared correctly.
I demangled your function name here: https://demangler.com/
The C++ function has this signature
unsigned __int64 __cdecl ATCreateLoginRequest(
unsigned __int64,
wchar_t const *,
wchar_t const *,
void (__cdecl*)(unsigned __int64,unsigned __int64,struct _ATLOGIN_RESPONSE *)
)
Your delegate is therefore declared with the wrong calling convention. It should be:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ATLoginResponseCallback(
ulong hSession,
ulong hRequest,
ref ATLOGINRESPONSE response
);
Note that having changed ATLOGINRESPONSE to be a struct, we have to make the parameter a ref parameter.
The function ATLoginResponseCallback is declared correctly in your C# code.
Your other delegates will also need to be declared with [UnmanagedFunctionPointer(CallingConvention.Cdecl)].
Unless the callback is only called from ATCreateLoginRequest then the delegate that is passed is subject to being garbage collected. So if ATCreateLoginRequest takes a copy of that delegate, and calls later, then you will need to keep the delegate alive. Assign it to a variable of type ATLoginResponseCallback whose life extends beyond the final call to the callback.
It's entirely plausible that the problems lie elsewhere.

calling c++ function from c# with a reference type as parameter

int Set(CANMsg &CANObj)
I have to call the method above, from c#. Untill now i have defined a wrapper :
extern "C" __declspec(dllexport) int SetWrapper(CANMsg CANObj);
CANMsg CANObj --- is this parameter ok or should i use CANMsg *CANObj ?
and here i implement the wrapper:
extern "C" __declspec(dllexport) int SetWrapper(CANMsg CANObj)
{
return Set(CANObj);
}
I am creating this wrapper because this is an overloaded version of the function and i had to make a difference somehow.
Here is the CANMsg class:
class CANMsg
{
public:
CANMsg();
~CANMsg();
void AddRef() const;
void Release() const;
unsigned int MsgId;
unsigned int DLC;
unsigned int Handle;
unsigned int Interval;
unsigned int TimeStamp;
unsigned char Data0;
unsigned char Data1;
unsigned char Data2;
unsigned char Data3;
unsigned char Data4;
unsigned char Data5;
unsigned char Data6;
unsigned char Data7;
protected:
mutable int refCount;
};
Now, in C# i have the following :
[StructLayout(LayoutKind.Sequential)]
public class CANmsg
{
public int MsgId;
public int DLC;
public int Handle;
public int Interval;
public int TimeStamp;
public char Data0;
public char Data1;
public char Data2;
public char Data3;
public char Data4;
public char Data5;
public char Data6;
public char Data7;
}
and the import is like this :
[DllImport("engine.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I4)]
public static extern int SetWrapper(IntPtr canMSGObject);
I am a bit confused about that CANMsg object, am I declaring it ok as an IntPtr, is the marshal ok, or the types ? If i let it like so, with the IntPtr, what kind of instantiation should i perform there? If i send a CANMsg object, i get an error regarding some invalid arguments.
Let me know if you need some more details about this.
When I see your C++ class definition, I ask myself "what happens in the constructor and the destructor?" and "what do AddRef() and Release() do?" These are important questions because you can't simply project data from a C# object onto that IntPtr and hope for the best. Instead, you should think about making a helper dll that does this work for you. You might need methods something like this:
public ref class MyLibraryHelper {
public:
IntPtr MakeCANMsg() { return gcnew IntPtr(new CANMsg()); }
void DestroyCANMsg(IntPtr msgPtr) {
CANMsg *msg = reinterpret_cast<CANMsg *>(msgPtr.ToPointer());
if (msg) delete msg;
}
void ProjectTo(CSharpCANMsg ^csh, IntPtr msgPtr)
{
CANMsg *msg = reinterpret_cast<CANMsg *>(msgPtr.ToPointer());
if (!msg) return;
msg->MsgId = csh->get_MsgId();
// etc
}
void ProjectFrom(IntPtr msgPtr, CSharpCANMsg ^csh)
{
CANMsg *msg = reinterpret_cast<CANMsg *>(msgPtr.ToPointer());
if (!msg) return;
csh->set_MsgId(msg->MsgId);
// etc
}
}
My C++/CLI is rusty, so expect some issues. If this looks like hand-marshalling, well, it is because given the class that you've exposed, it seems like you need it.
Now honestly, you probably don't want this. Really, you want a C++/CLI class that constructs a CANMsg and keeps it as a private member and then maps .NET properties onto the lower level object. This type of class will have to be disposable and the !ClassName() destructor will be responsible for deleting the underlying object.
You can not pass a C# object to native C++ like this. Marshal.StructureToPtr is what you need, the details and examples are here

Interop question about StringBuilder

I am calling a C# method from C code.
The C# method:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void p_func(StringBuilder arg);
public static void callback(StringBuilder arg)
{
Console.WriteLine(arg.ToString());
}
The C method:
extern "C" void c_method(p_func f)
{
char msg[4];
::strcpy(msg,"123");
char* p="123";
f(msg); // this is ok
f(p); //Error: Attempted to read or write protected memory!
}
But if I use String instead of StringBuilder as below in my C# method declaration, f(p) and f(msg) both work. Why?
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void p_func(String arg);
public static void callback(String arg)
{
Console.WriteLine(arg.ToString());
}
Note
the calling logic is like this:
c_method()---->delegate p_func--->callback()
not the reverse.
I checked the arg in the callback(StringBuilder arg), the Length, MaxCapacity, Capacity are all the same for char *p or msg[]. Only that *p leads to the exception. Why?
When you use String as the parameter type, the CLR will not attempt to write any changes back to the native memory buffer. When you use a StringBuilder (which is the right choice for in/out string parameters), it will. But the memory p points to will be read-only because of the way you declared it, that's why yout get an error.

Call c++ function pointer from c#

Is it possible to call a c(++) static function pointer (not a delegate) like this
typedef int (*MyCppFunc)(void* SomeObject);
from c#?
void CallFromCSharp(MyCppFunc funcptr, IntPtr param)
{
funcptr(param);
}
I need to be able to callback from c# into some old c++ classes. C++ is managed, but the classes are not ref classes (yet).
So far I got no idea how to call a c++ function pointer from c#, is it possible?
dtb is right. Here a more detailed example for Marshal.GetDelegateForFunctionPointer. It should work for you.
In C++:
static int __stdcall SomeFunction(void* someObject, void* someParam)
{
CSomeClass* o = (CSomeClass*)someObject;
return o->MemberFunction(someParam);
}
int main()
{
CSomeClass o;
void* p = 0;
CSharp::Function(System::IntPtr(SomeFunction), System::IntPtr(&o), System::IntPtr(p));
}
In C#:
public class CSharp
{
delegate int CFuncDelegate(IntPtr Obj, IntPtr Arg);
public static void Function(IntPtr CFunc, IntPtr Obj, IntPtr Arg)
{
CFuncDelegate func = (CFuncDelegate)Marshal.GetDelegateForFunctionPointer(CFunc, typeof(CFuncDelegate));
int rc = func(Obj, Arg);
}
}
Have a look at the Marshal.GetDelegateForFunctionPointer method.
delegate void MyCppFunc(IntPtr someObject);
MyCppFunc csharpfuncptr =
(MyCppFunc)Marshal.GetDelegateForFunctionPointer(funcptr, typeof(MyCppFunc));
csharpfuncptr(param);
I don't know if this really works with your C++ method, though, as the MSDN documentation states:
You cannot use this method with function pointers obtained through C++

Categories