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并不意味着垃圾收集器将处理对象,我不必关心如何以及何时释放它们?我知道这是很多问题,但我想要的只是摆脱我的内存泄漏,所以我列出了我认为可能出错的一切......
我的代码有什么问题吗?
不是您发布的内容 - 您正在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现在涵盖了终结器的大部分原始用途.