mercredi 24 juillet 2019

C++-CLI: safely passing a callback from managed code to c++ code

I have a lot of c++ classes that accept some form of callbacks, usually a boost::signals2::slot object.

But for simplicity, lets assume the class:

class Test
{
    // set a callback that will be invoked at an unspecified time
    // will be removed when Test class dies
    void SetCallback(std::function<void(bool)> callback);
}

Now I have a managed class that wraps this c++ class, I would like to pass a callback method to the c++ class.

public ref class TestWrapper
{
public:
    TestWrapper()
        : _native(new Test())
    {

    }

    ~TestWrapper()
    {
        delete _native;
    }
private:
    void CallbackMethod(bool value);
    Test* _native;
};

now usually what I would do is the following:

  1. Declare a method in the managed wrapper that is the callback I want.
  2. Create a managed delegate object to this method.
  3. Use GetFunctionPointerForDelegate to obtain a pointer to a function
  4. Cast the pointer to the correct signature
  5. Pass the pointer to the native class as callback.
  6. I also keep the delegate alive since I fear it will be garbage collected and I will have a dangling function pointer (is this assumption correct?)

this looks kind of like this:

_managedDelegateMember = gcnew ManagedEventHandler(this, &TestWrapper::Callback);
System::IntPtr stubPointer = Marshal::GetFunctionPointerForDelegate(_managedDelegateMember);
UnmanagedEventHandlerFunctionPointer  functionPointer = static_cast<UnmanagedEventHandlerFunctionPointer >(stubPointer.ToPointer());   
_native->SetCallback(functionPointer);

I Would like to reduce the amount of code and not have to perform any casts nor declare any delegate types. I want to use a lambda expression with no delegate.

This is my new approach:

static void SetCallbackInternal(TestWrapper^ self)
{
    gcroot<TestWrapper^> instance(self);
    self->_native->SetCallback([instance](bool value)
    {
        // access managed class from within native code
        instance->Value = value;
    }
    );
}

  • Declare a static method that accepts this in order to be able to use C++11 lambda.
  • Use gcroot to capture the managed class in the lambda and extend its lifetime for as long as the lambda is alive.
  • No casts, no additional delegate type nor members, minimal extra allocation.

Question: Is this approach safe? I'm fearing I'm missing something and that this can cause a memory leak / undefined behavior in some unanticipated scenario.

Aucun commentaire:

Enregistrer un commentaire