C++/CLI Wrapper class track unmanaged object pointers

38 Views Asked by At

I have an unmanaged C++ class SRWindow that I want to wrap in C++/CLI. The wrapper class is called SEWindow and has a constructor that also creates an SRWindow object:

public ref class SEWindow
{
private:
    SRWindow* nativeInstance;

public:
    SEWindow(int widthPX, int heightPX, String^ name, bool fullscreen)
    {
        //name -> nativeName
        nativeInstance = new SRWindow(widthPX, heightPX, nativeName, fullscreen);
    }

    SEWindow(SRWindow* window) : nativeInstance(window)
    {
    }

    !SEWindow()
    {
        delete nativeInstance;
    }

    //Other wrapped methods

};

Objects of the SRWindow class are passed between other C++ library classes, for example to the SRButtonLayout class, which has an addBinding(std::function<void(int key, SRWindow* window)>) method. To use this method in C++/CLI, the passed SRWindow* window object must be wrapped in gcnew SEWindow(window).

public delegate void KeyEvent(Key key, SEWindow^ window);

void ManagedAddBinding(SRButtonLayout* nativeInstance, KeyEvent^ ievent)
{
    gcroot<KeyEvent^> wrapper(ievent);
    nativeInstance->addBinding(
        [wrapper](int key, SRWindow* window) {
            wrapper->Invoke(static_cast<Key>(key), gcnew SEWindow(window));
        }
    );
}

This causes several SEWindow wrappers to reference the same unmanaged object and if one of them is caught by the garbage collector, the unmanaged object will also be deleted, even though other objects are still referencing it.

It may be possible to fix this issue using a map that counts the number of occurrences of each pointer, or in general a map that links a wrapper object with an unmanaged object based on its pointer, but this would be inefficient and seems too complicated for something so basic. Would it be possible to keep better track of unmanaged objects, so that they could only be deleted when they are no longer referenced?

1

There are 1 best solutions below

6
Aconcagua On

Don't try to re-invent the wheel, std::shared_ptr already exists exactly for such scenarios! Using such one your code might look like this:

// some general conversion function, independent from SEWindow class:
std::string nativeString(String^ managed);

public ref class SEWindow
{
public:
    SEWindow(int widthPX, int heightPX, String^ name, bool fullscreen)
        : nativeInstance
          (
              std::make_shared<SRWindow>
              (
                  widthPX, heightPX, nativeString(name), fullscreen
              )
          )
    { }

    SEWindow(std::shared_ptr<SRWindow>& window)
    // reference:                     ^
    // avoids unnecessary copy and with it, reference counting
        : nativeInstance(window) // <- reference counting only here is fine
    { }

    !SEWindow()
    {
        // just reset the pointer; if this is the last one
        // holding the object it will delete it
        nativeInstance.reset();
    }

private:
    std::shared_ptr<SRWindow> nativeInstance;
};

(Entirely untested code; if you find a bug, please fix yourself…)

If you now need to pass around the raw pointers to any third party libraries: No problem, you can always get one from the shared pointer via std::shared_ptr::get function, e.g. like:

void library::f(SRWindow* w);

void SEWindow::f()
{
    library::f(nativeInstance.get());
}

Finally (general advice; as you seem to construct the SRWindow instances on your own anyway, it might not be relevant in the specific case) if you retrieve the objects to be wrapped from one of the libraries and are not expected to delete them, but instead call some dedicated library function (or simply C's free), which is the case with many C libraries, then you have two options: Either still wrap these objects into a std::shared_ptr, but provide a custom deleter (-> the second, defaulted template argument) – in which case I recommend using a type alias for, e.g.:

using SRWindowSharedPointer = std::shared_ptr<SRWindow, SRWindowDeleter>;

– or you write yet another custom native wrapper calling the function in its destructor. This latter one you'd use then in the shared pointers. Note, though, that this latter approach – which might be slightly simpler to implement – introduces yet another level of indirection, thus I'd rather go with the custom deleter. This question might be helpful on writing such one.