C++/CLI代码中的内存泄漏..我做错了什么?

Ben*_*Ben 7 c# c++ memory-leaks c++-cli wrapper

用C++编写没有memleak的代码对我来说不是问题,我只是坚持RAII习语.

在C#中编写无memleak的代码也不是很难,垃圾收集器会处理它.

不幸的是,编写C++/CLI代码对我来说一个问题.我以为我已经理解它是如何工作的,但我仍然有很大的问题,我希望你能给我一些提示.

这就是我所拥有的:

用C#编写的Windows服务,内部使用C++库(例如OpenCV).使用C++/CLI包装类访问C++类.例如,我有一个图像对象的MatW C++/CLI包装类cv::Mat,它作为构造函数参数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;
};
Run Code Online (Sandbox Code Playgroud)

例如,另一个C++类可能是

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

它的包装类:

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;
    }
Run Code Online (Sandbox Code Playgroud)

这是我的Windows Service C#类中对方法的调用:

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

现在,这是我的问题:

  • 我的代码有什么问题吗?
  • 我必须using在我的C#代码中使用?当使用多个包装器对象时,它会使代码变得丑陋,因为using语句必须嵌套
  • Dispose()在使用这些物品后我可以使用吗?
  • 我可以不打扰并把它留给垃圾收集器吗?(不using,不Dispose())
  • 上面的代码是将对象List<string^>^从C++/CLI返回到C#的正确方法吗?
  • 使用gcnew并不意味着垃圾收集器将处理对象,我不必关心如何以及何时释放它们?

我知道这是很多问题,但我想要的只是摆脱我的内存泄漏,所以我列出了我认为可能出错的一切......

Dan*_*ker 5

我的代码有什么问题吗?

不是您发布的内容 - 您正在using正确应用语句.因此,您的代码示例不是导致内存泄漏的原因.

我必须在我的C#代码中使用吗?当使用多个包装器对象时,它会使代码变得丑陋,因为using语句必须嵌套

你不必,但你不必在语法上嵌套它们.这相当于:

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

我可以在使用对象后使用Dispose()吗?

你可以,但是你需要一个try/ finally来确保Dispose总是被调用,即使抛出异常.该using陈述封装了整个模式.

我可以不打扰并把它留给垃圾收集器吗?(没有使用,没有Dispose())

C++ RAII通常应用于所有类型的临时状态清理,包括递减在构造函数中递增的计数器等等.而GC在后台线程中运行.它不适合RAII可以处理的所有确定性清理方案.与RAII相当的CLR是IDisposable,它的C#语言接口是using.在C++中,它的语言接口是(自然地)析构函数(成为Dispose方法)和delete运算符(成为调用Dispose).在"堆栈"上声明的Ref类对象实际上只相当于C#使用模式.

上面的代码是将List ^从C++/CLI返回到C#等对象的正确方法吗?

差不多!

使用gcnew并不意味着垃圾收集器会处理对象,我不必关心如何以及何时释放它们?

你不必释放内存.但是如果类实现了IDisposable,那么这与内存分配完全不同.您必须Dispose在放弃对象之前手动调用.

警惕终结器 - 这是一种挂钩到GC的方式,以便在GC收集对象时运行自己的清理代码.但它们并不适合应用程序代码中的一般用途.它们从您无法控制的线程运行,在您无法控制的时间运行,并且按照您无法控制的顺序运行.因此,如果一个对象的终结器尝试使用终结器访问另一个对象,则第二个对象可能已经完成.无法控制这些事件的顺序.SafeHandle现在涵盖了终结器的大部分原始用途.