memory leaks in C++/CLI code.. what did I do wrong?
Asked Answered
F

2

7

Writing memleak-free code in C++ isn't a problem for me, I just keep to the RAII idiom.

Writing memleak-free code in C# isn't very hard either, the garbage collector will handle it.

Unfortunately, writing C++/CLI code is a problem for me. I thought I had understood how it works, but I still have big problems and I hope you can give me some hints.

This is what I have:

A Windows service written in C# that uses C++ libraries (for example OpenCV) internally. The C++ classes are accessed with C++/CLI wrapper classes. For example I have a MatW C++/CLI wrapper class for a cv::Mat image object, which takes as constructor argument a System::Drawing::Bitmap:

public ref class MatW
{
public:
    MatW(System::Drawing::Bitmap ^bmpimg)
    {
        cv::Size imgsize(bmpimg->Width, bmpimg->Height);
        nativeMat = new Mat(imgsize, CV_8UC3);

        // code to copy data from Bitmap to Mat
        // ...
    }

    ~MatW()
    {
        delete nativeMat;
    }

    cv::Mat* ptr() { return nativeMat; }

private:
    cv::Mat *nativeMat;
};

Another C++ class might be for example

class PeopleDetector
{
public:
    void detect(const cv::Mat &img, std::vector<std::string> &people);
}

And its wrapper class:

public ref class PeopleDetectorW
{
public:
    PeopleDetectorW() { nativePeopleDetector = new PeopleDetector(); }
    ~PeopleDetectorW() { delete nativePeopleDetector; }

    System::Collections::Generic::List<System::String^>^ detect(MatW^ img)
    {
        std::vector<std::string> people;
        nativePeopleDetector->detect(*img->ptr(), people);

        System::Collections::Generic::List<System::String^>^ peopleList = gcnew System::Collections::Generic::List<System::String^>();

        for (std::vector<std::string>::iterator it = people.begin(); it != people.end(); ++it)
        {
            System::String^ p = gcnew System::String(it->c_str());
            peopleList->Add(p);
        }

        return peopleList;
    }

And here is the call to the method in my Windows Service C# class:

Bitmap bmpimg = ...
using (MatW img = new MatW(bmpimg))
{
    using (PeopleDetectorW peopleDetector = new PeopleDetector())
    {
        List<string> people = peopleDetector.detect(img);
    }
}

Now, here's my questions:

  • is there anything wrong with my code?
  • do I have to use using in my C# code? It makes the code ugly, when there are multiple wrapper objects in use, because the using statements have to be nested
  • could I use Dispose() instead after having used the objects?
  • Could I just not bother and leave it to the garbage collector? (no using, no Dispose())
  • is the above code the right way to return objects like List<string^>^ from C++/CLI to C#?
  • does using gcnew not mean that the garbage collector will take care of the objects and I don't have to care how and when to free them?

I know that's a lot of questions, but all I want is to get rid of my memory leak, so I listed everything that I think could possibly go wrong...

Fissionable answered 3/10, 2012 at 2:33 Comment(0)
M
6

is there anything wrong with my code?

Not in what you've posted - you are applying using statements correctly. So your code sample is not the cause of your memory leaks.

do I have to use using in my C# code? It makes the code ugly, when there are multiple wrapper objects in use, because the using statements have to be nested

You don't have to, but you don't have to nest them syntactically. This is equivalent:

Bitmap bmpimg = ...
using (MatW img = new MatW(bmpimg))
using (PeopleDetectorW peopleDetector = new PeopleDetector())
{
    List<string> people = peopleDetector.detect(img);
}

could I use Dispose() instead after having used the objects?

You could, but then you'll need a try/finally to ensure Dispose is always called, even when an exception is thrown. The using statement encapsulates that whole pattern.

Could I just not bother and leave it to the garbage collector? (no using, no Dispose())

C++ RAII is commonly applied to all kinds of temporary state clean-up, including things like decrementing a counter that was incremented in the constructor, etc. Whereas the GC runs in a background thread. It is not suitable for all the deterministic clean-up scenarios that RAII can take care of. The CLR equivalent of RAII is IDisposable, and the C# language interface to it is using. In C++, the language interface to it is (naturally) destructors (which become Dispose methods) and the delete operator (which becomes a call to Dispose). Ref class objects declared "on the stack" are really just equivalent to the C# using pattern.

is the above code the right way to return objects like List^ from C++/CLI to C#?

Pretty much!

does using gcnew not mean that the garbage collector will take care of the objects and I don't have to care how and when to free them?

You don't have to free the memory. But if the class implements IDisposable, then that is a totally separate issue from memory allocation. You have to call Dispose manually before you abandon the object.

Be wary of finalizers - these are a way of hooking into the GC to get your own clean-up code to run when the GC collects your objects. But they are not really fit for general use in application code. They run from a thread that you don't control, at a time you don't control, and in an order that you don't control. So if one object's finalizer tries to access another object with a finalizer, the second object may already have been finalized. There is no way to control the ordering of these events. Most of the original uses of finalizers are nowadays covered by SafeHandle.

Monkey answered 3/10, 2012 at 8:37 Comment(0)
E
4

You don't have a finalizer in the ref class.

In C++/CLI the destructors are called either when you create an instance of the class on the stack (C++ style) and then it goes out of scope, or you use the delete operator. The finalizers are called by the GC when it's time to finalize the object.

In C# the GC handles the destruction of all the objects (there's no delete operator) so there's no distinction between the destructor and finalizer.

So the "destructors" with the ~ behave like c++ destructors, not at all like C# destructors. The "destructors" in C++/CLI ref class are compiled into a .Net Dispose() method.
The equivilant to a C# destructor/finalizer is the finalizer method witch is defined with a ! (exclamation mark).

So, to avoid memory leaks, you need to define a finalizer:

 !MatW()
    {
        delete nativeMat;
    }

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

See the MSDN article Destructors and Finalizers in visual C++ Use the

Expunge answered 3/10, 2012 at 7:42 Comment(4)
Adding a finalizer is hardly ever the right thing to do. In this case it would mean that destructor code in C++ would execute on the finalizer thread(s) at an uncontrollable time.Monkey
No, that's for ref classes, not native classes. ref classes are supposed to get destructed on the GC thread, just like every other C# class.Expunge
Perhaps you misunderstood my comment. Your code snippet is a modification to a ref class that is a wrapper around a native class. From within a finalizer, it triggers the deletion of a native class. Hence it will cause native C++ destructor code to execute on the CLR's finalizer thread. Introducing additional threads into a native C++ program by accident is a bad idea.Monkey
@DanielEarwicker : You're right. The finalizer is called on the GC thread. And that fact should be taken into consideration for thread safety in the C++ logic.Expunge

© 2022 - 2024 — McMap. All rights reserved.