c # — утечки памяти в коде C ++ / CLI .. что я сделал не так?

Написание кода без C ++ на C ++ не проблема для меня, я просто придерживаюсь RAII идиома.

Написание кода без утечек в C # тоже не сложно, сборщик мусора справится с этим.

К сожалению, написание кода C ++ / CLI является проблема для меня. Я думал, что понял, как это работает, но у меня все еще есть большие проблемы, и я надеюсь, что вы можете дать мне несколько советов.

Вот что у меня есть:

Служба Windows, написанная на C #, которая использует библиотеки C ++ (например, OpenCV) для внутреннего использования. Доступ к классам C ++ осуществляется с помощью классов-оболочек C ++ / CLI.
Например, у меня есть MatW Класс C ++ / CLI для cv::Mat объект изображения, который принимает в качестве аргумента конструктора 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;
};

Другой класс C ++ может быть, например,

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

И его класс оболочки:

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;
}

И вот вызов метода в моем классе Windows Service C #:

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

Теперь вот мои вопросы:

  • что-то не так с моим кодом?
  • я должен использовать using в моем коде C #? Это делает код некрасивым, когда используется несколько объектов-оболочек, потому что using заявления должны быть вложенными
  • Могу ли я использовать Dispose() вместо того, чтобы после использования объектов?
  • Могу я просто не беспокоиться и оставить это сборщику мусора?
    (нет usingнет Dispose())
  • является ли приведенный выше код правильным способом возврата таких объектов, как List<string^>^ из C ++ / CLI в C #?
  • использует gcnew не означает, что сборщик мусора позаботится об объектах, и мне не нужно заботиться о том, как и когда их освободить?

Я знаю, что это много вопросов, но все, что я хочу, это избавиться от утечки памяти, поэтому я перечислил все, что, по моему мнению, могло пойти не так …

7

Решение

что-то не так с моим кодом?

Не в том, что вы опубликовали — вы подаете заявку using заявления правильно. Таким образом, ваш пример кода не является причиной утечек памяти.

я должен использовать использование в моем коде C #? Это делает код некрасивым, когда используется несколько объектов-оболочек, потому что операторы using должны быть вложенными

Вы не должны, но вам не нужно вкладывать их синтаксически. Это эквивалентно:

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

Могу ли я использовать Dispose () вместо того, чтобы использовать объекты?

Вы могли бы, но тогда вам понадобится try/finally для обеспечения Dispose всегда вызывается, даже когда выдается исключение. using заявление инкапсулирует весь этот шаблон.

Могу я просто не беспокоиться и оставить это сборщику мусора? (без использования, без утилизации ())

C ++ RAII обычно применяется для всех видов временной очистки состояния, включая такие вещи, как уменьшение счетчика, который был увеличен в конструкторе, и т. Д. В то время как сборщик мусора работает в фоновом потоке. Это не подходит для всех детерминированных сценариев очистки, о которых может позаботиться RAII. CLR-эквивалент RAII IDisposableи интерфейс языка C # к нему using, В C ++ языковой интерфейс к нему является (естественно) деструкторами (которые становятся Dispose методы) и delete оператор (который становится вызовом Dispose). Объекты класса Ref, объявленные «в стеке», на самом деле просто эквивалентны C# используя шаблон.

является ли приведенный выше код правильным способом возврата объектов типа List ^ из C ++ / CLI в C #?

Довольно много!

Разве использование gcnew не означает, что сборщик мусора позаботится об объектах, и мне не нужно заботиться о том, как и когда их освобождать?

Вам не нужно освобождать память. Но если класс реализует IDisposableтогда это совершенно отдельная проблема с распределением памяти. Вы должны позвонить Dispose вручную, прежде чем покинуть объект.

Будьте осторожны с финализаторами — это способ подключиться к GC, чтобы ваш собственный код очистки запускался, когда GC собирает ваши объекты. Но они не очень подходят для общего использования в коде приложения. Они запускаются из потока, который вы не контролируете, в то время, когда вы не контролируете, и в порядке, который вы не контролируете. Таким образом, если финализатор одного объекта пытается получить доступ к другому объекту с помощью финализатора, второй объект, возможно, уже был завершен. Нет способа контролировать порядок этих событий. Большинство оригинальных применений финализаторов в настоящее время покрыты SafeHandle.

5

Другие решения

У вас нет финализатора в классе ref.

В C ++ / CLI деструкторы вызываются либо когда вы создаете экземпляр класса в стеке (стиль C ++), а затем он выходит из области видимости, либо вы используете delete оператор.
Финализаторы называются GC когда пришло время завершать объект.

В C # GC обрабатывает уничтожение всех объектов (нет оператора удаления), поэтому нет разницы между деструктором и финализатором.

Итак «деструкторы«с ~ ведут себя как деструкторы c ++, совсем не как деструкторы C #.
«деструкторы«в C ++ / CLI класс ссылки скомпилированы в .Net Dispose() метод.
Эквивалент для деструктора / финализатора C # — это метод финализатора, который определяется с помощью ! (восклицательный знак).

Итак, чтобы избежать утечек памяти, вам нужно определить финализатор:

 !MatW()
{
delete nativeMat;
}

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

Смотрите статью MSDN Деструкторы и финализаторы в Visual C ++
Использовать

2