I have a CLI wrapper to a C++ class. The C++ class has a callback that passes
a char* array, and the length of that array.
void (CPP_CALLBACK*)(char* data, unsigned int dataLength)
In C# land, I have this callback
private delegate void AppPacketReceivedDelegate(byte[] data);
My CLI wrapper receives the CPP_CALLBACK callback, and must then somehow call the C# delegate.
Any ideas on how to do this ? I tried
System::Action<cli::array<char>^>
but I am not sure how to match the delegate to this Action.
Update:
I convert the delegate into a function pointer, and then I can call the function pointer from CLI using this syntax:
typedef void(__stdcall * WRAPPER_APP_PACKET_CALLBACK) (cli::array<unsigned char>^);
But, when I call this, the array is always of size 1 !
Here a simple example, not exactly matching your situation, but it could be quickly adapted.
CppClass.h
namespace MyCppNamespace
{
typedef void (__stdcall *callback_function)(int, const char*);
class CppClass
{
private:
callback_function _callbackFunc;
public:
void DoSomething(callback_function callbackFunc);
}
}
DotNetClass.cs
namespace MyManagedNamespace
{
public delegate void ManagedCallback(int size, string message);
}
CliWrapperClass.h
#include "CppClass.h"
using namespace MyManagedNamespace;
namespace MyCliNamespace
{
public ref class CliWrapperClass
{
private:
CppClass *_cppClass;
public:
void DoSomething(ManagedCallback ^ callback);
}
}
CliWrapperClass.cpp
#include "CliWrapperClass.h"
namespace MyCliNamespace
{
void CliWrapperClass::DoSomething(ManagedCallback ^ callback)
{
System::IntPtr callbackPtr = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(callback);
_cppClass->DoSomething(static_cast<callback_function>(callbackPtr.ToPointer()));
}
}
In the .Net class, you can create an instance of CliWrapperClass and call its DoSomething(...) function in this way:
namespace MyManagedNamespace
{
public class ManagedClass
{
private ManagedCallback _callback = MyCallback;
private void MyCallback(int size, string message)
{
// Do what you want ...
}
public void MyFunction()
{
CliWrapperClass wrapper = new CliWrapperClass();
wrapper.DoSomething(_callback);
}
}
}
In the end, I marked the C# class and the corresponding delegate as unsafe, and
then I was able to call the delegate from C++ and pass over the C-style array. On the C# side, I created a managed array and copied the bytes over using Marshal.Copy
Related
I'm using a native DLL that is called out of my C# application. One of the DLL-functions is defined like this:
int set_line(const int width,fct_line_callback callback)
fct_line_callback itself is defined as
typedef int (*fct_line_callback)(double power,void *userData);
So how can I use this function out of my C#-application? Is there a way to define a C# method to be used as callback-function for this DLL-call?
Thanks!
You have to declare a delegate type that matches the native function pointer. It probably should look like this:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate int fct_line_callback(double power, IntPtr userdata);
Now you can write the pinvoke declaration:
[DllImport("foo.dll", CallingConvention = CallingConvention.Cdecl)]
extern static int set_line(int width, fct_line_callback callback);
If the callback can only be made while set_line() is executing then calling the native function is simple:
public void SetLine(int width) {
set_line(width, MyCallback);
}
private void MyCallback(double power, IntPtr userdata) {
// etc...
}
However, if the callback can be made after set_line() is executed, in other words when the native code stores the function pointer, then you have to make sure that the garbage collector cannot collect the delegate object. Simplest way to do so is by storing the object in a static variable:
static class Wrapper {
private static fct_line_callback callback = MyCallback;
public static void SetLine(int width) {
set_line(width, callback);
}
private static int MyCallback(double power, IntPtr userdata) {
// etc...
}
}
I have a C++ project containing a nonmanaged class method used to display string in a user interface :
void MyProject::displayIHM(std::string mystring);
This project is compiled with /clr because it calls another one made with C#/.NET 4.0. The goal of the .NET project is to make heavy computation tasks. During computation, we want to get back from it some information to the user interface.
My idea was to create two new methods in the C++-cli project :
void MyProject::displayFromDotNet(String^ mystring)
{
displayIHM(ToStdString(mystring));
}
string ToStdString ( String ^ s)
{
const char* chars = (const char*)(Marshal::StringToHGlobalAnsi(s)).ToPointer();
string os = chars;
Marshal::FreeHGlobal(IntPtr((void*)chars));
return os;
}
Until now, everything is ok but now the difficult part : how to provide displayFromDotNet to the .NET project. My idea was to provide a function pointer in the constructor of the .NET class and then to launch the process :
void (MyProject::*pointeurFunc)(String^) = &MyProject::displayFromDotNet;
ComputationProject^ kernel = gcnew ComputationProject((this->*pointeurFunc));
kernel->Compute();
The second line does not work. The constructor of ComputationProject has a IntPtr parameter but I do not know if I can convert a function pointer to an IntPtr^ in C++. I also made some attempts using Marshal::GetDelegateForFunctionPointer but it could not compile.
I do not know what to do, any help would be appreciated!
EDIT : yes ComputationProject is my C#/.NET project. The error with line 2 is "cannot convert parameter 1 from 'overloaded function type' to 'System::IntPtr'".
I finally find a (ugly) way.
My main problem was I could not pass a method pointer to C# because it is not a real function pointer (so I cannot cast it to IntPtr).
I decided to create a second class containing a static MyProject object and a static method calling displayIHM on the static object :
class StaticMyProject
{
public :
static MyProject staticObject;
static void DisplayInIHM(char *);
};
In cpp :
MyProject StaticMyProject::objetStatique;
void StaticMyProject::DisplayInIHM(char *message)
{
std::string message2(message);
staticObject.displayIHM(message2);
}
Now for calling Compute method of ComputationProject, I modified the code like this :
StaticMyProject::objetStatique = *this;
void (*funcPointer)(char*) = StaticMyProject::DisplayInIHM;
ComputationProject^ kernel = gcnew ComputationProject((IntPtr)funcPointer);
kernel->Compute();
And in my ComputationProject.cs :
public class ComputationProject
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void FunctionPointer([MarshalAs(UnmanagedType.LPStr)]string message);
public readonly FunctionPointer DisplayMethod;
public ComputationProject(IntPtr ptr)
{
this.DisplayMethod = (FunctionPointer)Marshal.GetDelegateForFunctionPointer(ptr, typeof(FunctionPointer));
}
public int Compute()
{
this.DisplayMethod("Beginning computation...");
...
}
}
C# function calls the exposed function in c++ which in-turn calls class abc's some_fun(int), how should i get the object of abc in C# some_fun enter code here
C++:
class abc
{
public: abc some_fun(int x);
}
this function calls class abc's some_fun(int),
how should i get the object of abc in C# some_fun (marshalled object)
extern "C" SAMPLEDLL_API void __cdecl some_fun(int x);
from c# wrapper: how to i marshal it
public static extern IntPtr some_fun(int abc);
You'll have to "Pinvoke" your c++ function from you C# program.
When you do this, you'll have to declare your function signature in C# terms.
In that declaration, you'll have to give a C# struct represnting the C++ struct you trying to pass.
for example, if you have the C++ struct:
struct FOO
{
public long Dummy;
};
In your C# program, you'll have to define a C# struct
public struct CSFOO
{
public int Dummy;
}
which you'll pass to your PINVOKEd function.
This link can help you:
http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx
I have this code in C++
class MyClass { ... };
typedef MyClass (*Callback)();
Callback theCB;
static void RegisterCallback( Callback cb ) { theCB = cb; };
static void CallCallback() {
MyClass obj = theCB();
}
I am using swig but for simplicity (if you don't know swig) I have this wrapper in C#
public class MyClassWrapper
{
public IntPtr ptrToNativeObj; // pointer to native MyClass object
public MyClassWrapper()
{
ptrToNativeObj = call to native code that creates
and returns a new instance of MyClass in C++
}
};
Now I want to support the callback mechanism in C# so I set it up like this:
public MyClassWrapper MyFunction()
{
return new MyClassWrapper();
}
delegate MyClassWrapper CallbackDotNet();
static void main()
{
var fct = new CallbackDotNet( MyFunction );
P/Invoke call to native function RegisterCallback( fct );
then finally:
P/Invoke call to native function CallCallback();
}
I have all this code setup to work properly.
The native code in CallCallback will call MyFunction properly.
But now I need to handle the returned object properly...
MyFunction returns a C# reference while the callback in C++ is returning by value so this would not work for free:
static void CallCallback() {
MyClass obj = theCB();
}
How can I marshall the "reference" to a MyClassWrapper object, returned from MyFunction, so that C++ receives "by-value" a MyClass object ?
Should I go ahead and write a custom marshaller ?
http://msdn.microsoft.com/en-us/library/zk0a8dea(v=vs.90).aspx
Then use it like here
[return: MarshalAs(UnmanagedType.CustomMarshaler,
MarshalType = "MyCustomMarshaler")]
delegate MyClassWrapper CallbackDotNet();
I looked at the documentation for custom marshallers and it's quite complex.
Looks like the interesting method to implement is the following:
IntPtr MarshalManagedToNative( Object ManagedObj );
And the code will be something like
IntPtr MarshalManagedToNative( Object ManagedObj )
{
MyClassWrapper val = ManagedObj as MyClassWrapper;
return val.ptrToNativeObj;
}
But this will return a MyClass* back to the native code, not a MyClass value that this C++ code expects !
static void CallCallback() {
MyClass obj = theCB();
}
Will the marshaller be smart enough to dereference the pointer ?
Thank you all for your comments
Looks like the custom marshaler is the way to go !
I did a simple test case and everything works fine. It works as expected.
Here is the marshaler in case you are interested:
public class MyCustomMarshaler : ICustomMarshaler
{
public static ICustomMarshaler GetInstance(String cookie)
{
return new MyCustomMarshaler();
}
public IntPtr MarshalManagedToNative(Object ManagedObj)
{
MyClassWrapper val = ManagedObj as MyClassWrapper;
return val.ptrToNativeObj;
}
...
}
[return: MarshalAs(UnmanagedType.CustomMarshaler,MarshalType = "MyCustomMarshaler")]
public delegate MyClassWrapper CallbackDotNet();
With this marshaler when C++ calls the C# callback the function MarshalManagedToNative is called on return from that C# callback and this enables to convert the C# reference (to MyClassWrapper) to a pointer to the C++ class MyClass.
This seems sufficient enough and P/Invoke will then take care of dereferencing this MyClass* to a MyClass value.
It wasn't as hard as I thought...
I'm trying to execute some methods (in this particular case, rdOnAllDone) from a third party DLL, written in C, and looking trough the headers files, I found this:
#ifndef TDECLSDONE
#ifdef STDCALL
#define CCON __stdcall
#else
#define CCON __cdecl
#endif
#define TDECLSDONE
#endif
#define DLLIMP __declspec (dllimport)
DLLIMP int CCON rdOnAllDone (void(CCON *)(int));
After goggling for a way to call this method, I made this:
[DllImport("sb6lib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int rdOnAllDone(Delegate d);
public delegate void rdOnAllDoneCallbackDelegate();
private static void rdOnAllDoneCallback()
{
Console.WriteLine("rdOnAllDoneCallback invoked");
}
The method was called correctly except that I couldn't get the int parameter. So I tried adding the input parameter int like this
[DllImport("sb6lib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int rdOnAllDone(Delegate d);
public delegate void rdOnAllDoneCallbackDelegate(int number);
private static void rdOnAllDoneCallback(int number)
{
Console.WriteLine("rdOnAllDoneCallback invoked " + number);
}
But now delegate is called twice and and it crashes the program with the following error " vshosts32.exe has stopped working"
What's the correct way to call this DLL method?
EDIT: Forgot to add the Main method:
public static void Main()
{
rdOnAllDoneCallbackDelegate del3 = new rdOnAllDoneCallbackDelegate(rdOnAllDoneCallback);
rdOnAllDone(del3);
while (true)
{
Thread.Sleep(1000);
}
}
Three things you need to do to make this work right:
you need to tell the pinvoke marshaller about the actual delegate type, using Delegate isn't good enough. That will create the wrong thunk that won't properly marshal the argument. Which is what you saw happening.
you need to tell the marshaller about the calling convention if it isn't __stdcall with the [UnmanagedFunctionPointer] attribute. Getting this wrong imbalances the stack with good odds for a hard crash.
you need to store a reference to the delegate object so that the garbage collector won't collect it. It cannot see references held by native code. Getting this wrong makes the native code fail with a hard crash after the next garbage collection.
So this ought to work better, tweak as necessary:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void rdOnAllDoneCallbackDelegate(int parameter);
[DllImport("sb6lib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int rdOnAllDone(rdOnAllDoneCallbackDelegate d);
class Foo {
private static rdOnAllDoneCallbackDelegate callback; // Keeps it referenced
public static void SetupCallback() {
callback = new rdOnAllDoneCallbackDelegate(rdOnAllDoneCallback);
rdOnAllDone(callback);
}
private static void rdOnAllDoneCallback(int parameter) {
Console.WriteLine("rdOnAllDoneCallback invoked, parameter={0}", parameter);
}
}
Your delegates signature has to match that of the native callback, also it has to have the UnmanagedFunctionPointerAttribute set appropriately.
In your case like so:
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
public delegate void rdOnAllDoneCallbackDelegate(int parameter);
[DllImport("sb6lib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int rdOnAllDone(rdOnAllDoneCallbackDelegate callback);
Usage:
{
rdOnAllDone(rdOnAllDoneCallback);
}
private static void rdOnAllDoneCallback(int parameter)
{
Console.WriteLine("rdOnAllDoneCallback invoked, parameter={0}", parameter);
}