Unmanaged to managed callback through wrapper: this works but is it good?

 

I implemented callback in mixed mode the following way and it works fine but I wonder if this is the right way to do it. In particular, is this 100% safe I mean could there be some obscure GC/CLR "event" that would make it crash at some point, for instance should not I be using pin pointers

It works this way: a delegate is declared in C# and used as argument to the setCallBack() function of a C++ .net wrapper. This function casts this pointer and use it to initialize the callBack pointer value in an unmanaged C++ class (it also keeps its value so the Wrapper can also call the callback). The C++ .net wrapper is shown below:

public ref class WrapperClass

{

public:

delegate int CallbackType();

WrapperClass()

{

UnManagedPt = new UnManaged();

}

void WrapperClass::setCallBack(CallbackType ^csFuncPt)

{

IntPtr ip = Marshal::GetFunctionPointerForDelegate(csFuncPt);

ptToCsCallBack = static_cast<funcType>(ip.ToPointer());

UnManagedPt->funcPtr=ptToCsCallBack;

}

private:

UnManaged* UnManagedPt;

static funcType ptToCsCallBack;

}



Answer this question

Unmanaged to managed callback through wrapper: this works but is it good?

  • winstonSmith

    Thanks for the prompt answer, then you think something like this would be Ok

    public class myGui: System.Windows.Forms.Form

    {

    WrapperClass.CallbackType myDelegate;

    WrapperClass test;

    private void Gui_Load(object sender, System.EventArgs e)

    {

    test = new WrapperClass();

    myDelegate = new WrapperClass.CallbackType(increaseProgressBar);

    test.setCallBack(myDelegate);

    }

    private int increaseProgressBar()

    {

    progressBar1.PerformStep();

    return(set_if_user_stopped_operations);

    }

    }

    PS: I did not even know the existence of the Rotor code


  • tornin2

    Since you keep a reference to the delegate: yes, that will keep it alive.


  • vtortola

    It is important that the delegate instance that the user passes to setCallBack doesn't get garbage collected. I assume that's what you tried to do with ptToCsCallBack. It shouldn't be static, you'll lose the reference that keeps the first delegate instance alive if you have more than one instance of WrapperClass.

    Storing the pointer you get from GetFunctionPointerForDelegate is not enough to keep the original delegate instance alive. Just store the csFuncPt reference in a class member.

    The docs are silent about whether or not you need to pin the delegate instance. Judging from the Rotor source code, I'd say no; it looks like it keeps a weak tracking handle.


  • AydinCinaRli

    Thank you very much for the very useful answer. You are totally right about the static.

    Storing the csFuncPt reference in the Wrapper class is indeed a smart idea to avoid GC issues. This means that I will have to do a GetFunctionPointerForDelegate each time I need to execute the callback. This approach leads me to 2 questions:

    1. I still need to find out how to execute the GetFunctionPointerForDelegate call from the unmanaged code. I guess this call would go though an intermediate function stored in Wrapper class, but then this intermediate callback should also have a not-GCed adress (since Wrapper is in C++ .Net).

    2. Is not it time consuming to call GetFunctionPointerForDelegate, say each time you need a progressBar to step

    This leads me to a more general comment about your point that "It is important that the delegate instance [...] doesn't get garbage collected". Can this really happen For reference, here is the code I am using in C#:

    WrapperClass.CallbackType myDelegate;

    WrapperClass test;

    test = new WrapperClass();

    myDelegate = new WrapperClass.CallbackType(increaseProgressBar);

    test.setCallBack(myDelegate);

    private int increaseProgressBar()

    {

    progressBar1.PerformStep();

    return(set_if_user_stopped_operations);

    }

    Thanks for you kind help,

    Fred


  • Peter Nimmo

    No, once is enough. You already store the result of GetFunctionPointerForDelegate() into UnManagedPt->funcPtr so you're good to go.

    Yes, your delegate instance myDelegate would get garbage collected in your example code. There is no reference left to it after the function exits. The user would have to store it in a class field to prevent that from happening. Since she sees a managed interface, there would be very little reason to assume for her that is required.


  • Unmanaged to managed callback through wrapper: this works but is it good?