HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Interop between C++ and C# via C++/CLI with callbacks

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
interopcallbackswithbetweenviacliand

Problem

I want to use an existing C++ library in a C# app. The C++ library will have async callbacks for things like OnConnect and user updates so I want to reflect this in the eventual C# code. I would appreciate thoughts on the following implementation particularly in the area of memory leaks in the CLI part.

To begin, I have a simple C++ class:

#include 

class CppClass
{
public:

    typedef void(*Callback)(int);

    CppClass(){};
    ~CppClass(){};

    void Connect(std::string conn_info)
    {
        OnConnect();
    };

    void OnConnect()
    {
        m_callback(42);
    };

    void SetOnCallback(Callback callback)
    {
        m_callback = callback;
    };

private:
    Callback m_callback;
};


To communicate with this, I have a C++/CLI wrapper:

```
using namespace System;
using namespace System::Runtime::InteropServices;

namespace CliCpp {

public delegate void OnConnectDelegate(int);

public ref class WrapperClass
{
public:

// C# callback delegates
CliCpp::OnConnectDelegate^ OnConnectHandler;

// Ctor / dtor
WrapperClass()
{
m_native = new CppClass();

// Set OnConnect callback
CliCpp::OnConnectDelegate^ managed_on_connect = gcnew CliCpp::OnConnectDelegate(this, &WrapperClass::OnConnect);
IntPtr stub_ptr = Marshal::GetFunctionPointerForDelegate(managed_on_connect);
CppClass::Callback fptr = static_cast(stub_ptr.ToPointer());
m_native->SetOnCallback(fptr);
GC::KeepAlive(managed_on_connect);
};

~WrapperClass()
{
this->!WrapperClass();
};

!WrapperClass()
{
if (m_native != nullptr)
delete m_native;
};

// Methods
void Connect(String^ conn_info)
{
m_native->Connect(msclr::interop::marshal_as(conn_info));
};

// Event handler from C++ code
void OnConnect(

Solution

I'm not sure about the callbacks but you are using the destructor/finalizer in a way that might cause problems.

~WrapperClass()
    {
        this->!WrapperClass();
    };

    !WrapperClass()
    {
        if (m_native != nullptr)
            delete m_native;
    };


If you are doing this then the descructor should call GC.SuppressFinalize(this); because the finalizer has already been called.

Then in your finalizer you should set the m_native = nullptr; because if the GC calls it again later it will crash as the object has been deleted but it's still pointing to it.

!WrapperClass()
    {
        if (m_native != nullptr)
        {
            delete m_native;
            m_native = nullptr;
        }
    }


As to the m_native I'd call it _nativePtr.

Code Snippets

~WrapperClass()
    {
        this->!WrapperClass();
    };

    !WrapperClass()
    {
        if (m_native != nullptr)
            delete m_native;
    };
!WrapperClass()
    {
        if (m_native != nullptr)
        {
            delete m_native;
            m_native = nullptr;
        }
    }

Context

StackExchange Code Review Q#137897, answer score: 3

Revisions (0)

No revisions yet.