堆中的 DLL 内存泄漏

Posted

技术标签:

【中文标题】堆中的 DLL 内存泄漏【英文标题】:DLL Memory leaks in the heap 【发布时间】:2014-09-09 15:02:22 【问题描述】:

我正在调试某人的代码并收到错误消息:这可能是由于堆损坏,这表明 .exe 或它已加载的任何 DLL 中存在错误。我研究并了解到这是由于一些内存泄漏,对象没有从堆中正确删除。我查看并查看了代码(逐行调试),它在返回语句处崩溃。这是代码:问题可能出在哪里?

首先是调用DLL的c#代码

    public class Analysis
    
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1), Serializable]
        public struct Pothole
        
            public int Id;
            public int FrameNumber;
            public int LeftX;
            public int TopY;
            public int Width;
            public int Height;
            public int Certainty; // note that this is a percentage value, hence integer
            public double Latitude;
            public double Longitude;
            public double Speed;
            public double Area;
            public double Volume;
            [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 20)]
            public string TimeCreated;
            [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 200)]
            public string CroppedImage;
            [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 200)]
            public string LeftImage;
            [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 200)]
            public string RightImage;
        ;

        private int progress;
        public int Progress
        
            get  return progress; 
            set  progress = value; 
        

        public List<FileInfo> AllVideoFiles;
        public List<FileInfo> FramesInDir;
        public List<GpsPoint> GpsList;
        public VideoMetaData MetaData;
        public List<TrackPothole> PotholeList;
        public VideoHandling VideoHandler;

        public int verbose; //if verbose, then you get a lot of wordy output on what the program is doing

        private VspDatabaseEntities1 _vde;        

        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        public delegate void ProgressCallback(int value);
        public event ProgressCallback OnProgressUpdated;

        //initializes video files and calibraiton file names
        [DllImport(@"Analyser.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
        public static extern int Initialize(string SettingsDir, int SettingDirLength, string LeftVideoFile, int LeftFileLength, 
            string RightVideoFile, int RightFileLength, string GpsCoordsFile, int GpsFileLength, string OutputDirectory, int OutputDirLength);

        //analyse road files (do everything)
        [DllImport(@"Analyser.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern int Start([MarshalAs(UnmanagedType.FunctionPtr)] ProgressCallback callbackPointer, int verbose);

        //Does the analysis of videos, uses the c++ analysis Start() function
        public void Go(ProgressBar bar)
        
            //flag -> should actually be set outside of function, can't find the outside
            verbose = 1;            

            //obtain parameters from settings.settings
            string inputDir = Properties.Settings.Default["InputDir"].ToString();
            string outputDir = Properties.Settings.Default["OutputDir"].ToString();
            string settingsDir = Properties.Settings.Default["SettingsDir"].ToString();

            //setup directories and filenames            
            //string camfile = settingsDir + "\\" + "Calib.yaml"; => now we send the directory, not the specific file
            string rightvid = "";
            string leftvid = "";
            string gpsfile = "";

            //Associate multiple files according to creation times and loop through matching pairs 
            //(this works because of the copy operation? -> but won't everything have the same time then?)
            AllVideoFiles = GetFiles(inputDir, "*.avi");            
            List<FileInfo> rightvids = new List<FileInfo>(AllVideoFiles.Where(v => v.FullName.Contains("3D_R")).OrderBy(v => v.CreationTime));
            List<FileInfo> leftvids = new List<FileInfo>(AllVideoFiles.Where(v => v.FullName.Contains("3D_L")).OrderBy(v => v.CreationTime));
            List<FileInfo> gpsfiles = new List<FileInfo>(GetFiles(inputDir, "*.txt").OrderBy(g => g.CreationTime));
            Console.WriteLine("Got this far1");
            //Check that the number of right and left video files and gps files match
            int lowestCount = 0;
            if (rightvids.Count != leftvids.Count)
            
                MessageBox.Show("Error: The number of right camera videos does not match the number of left camera videos!");
                if (rightvids.Count < leftvids.Count)
                    lowestCount = rightvids.Count;
                else
                    lowestCount = leftvids.Count;
            
            else
                lowestCount = rightvids.Count;

            if (gpsfiles.Count != lowestCount)
            
                MessageBox.Show("Error: The number of gps data files does not match the number of right camera video files!");
                if (gpsfiles.Count < lowestCount)
                    lowestCount = gpsfiles.Count;
            

            //@todo 
            //Currently, looping through the files and hoping there is only one in the file.
            Console.WriteLine("Got this far2");
            for (int i = 0; i < lowestCount; i++)
            
                rightvid = rightvids[i].FullName;
                leftvid = leftvids[i].FullName;
                gpsfile = gpsfiles[i].FullName;

                if (verbose==1)
                    Console.WriteLine("C# FileNames: " + settingsDir + " " + outputDir + " " + leftvid + " " + gpsfile);

                //Pass the filenames to the c++ functions
                int InitReturn = Initialize(settingsDir, settingsDir.Length, leftvid, leftvid.Length,
                    rightvid, rightvid.Length, gpsfile, gpsfile.Length, outputDir, outputDir.Length);

                if (verbose == 1)
                
                    Console.WriteLine("Initilize return value : " + InitReturn);
                    Report();
                

                //Setup the progress bar
                ProgressCallback callback = (value) => bar.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                    new Action(delegate() bar.Value = value; ));

                //***RUN THE ANALYSIS
                Console.WriteLine("Got this far4");
                int start_ret_val = Start(callback, verbose);
                if (verbose==1) Console.WriteLine("Start return value: " + start_ret_val);

                //Create the pothole array
                Pothole[] potArr = new Pothole[10000];
                Thread.Sleep(1000); //@todo Why sleep?

                if (start_ret_val > 0)
                
                    //fill up the array 
                    ///@todo How does this impact the memory wastage?
                    potArr = GetPotholes(start_ret_val);

                    MessageBox.Show("Analysis completed");

                    //bar.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                      //                  new Action( delegate()  bar.Value = 0;  ));
                
                else
                
                    MessageBox.Show("Analysis returned with error code : " + start_ret_val); //@todo Add some interpretation here
                

                Console.WriteLine("Got this far5");
                            
        

C++ 函数从这里开始:

cv::Mat RunClassifier(cv::Mat& frame, cv::Mat& mask, std::vector<CvANN_MLP*>& anns, std::vector<int>& criteria,std::vector<float>& thresholds, Mat& final_certainty)

    //instantiate certainty matrix
    std::vector<Mat> indiv_certs;
    for (unsigned int i = 0; i < anns.size(); i++)
        indiv_certs.push_back(Mat::zeros(frame.size(), CV_32FC1) );
    Mat certainty = Mat::zeros(frame.size(), CV_32FC1);
    final_certainty = Mat::zeros(frame.size(), CV_32FC1);

    Mat final_image = frame.clone();
    Mat* individual_masks = new Mat[anns.size()]();
    for (unsigned int iter = 0; iter < anns.size();iter++)
    
        individual_masks[iter] = Mat::zeros(frame.rows,frame.cols,CV_32F);
    
    Mat final_mask = Mat::zeros(frame.rows,frame.cols,CV_32F);

    //Convert the image to HSV colorspace (You could probably save a bit more if you converted and normalised the frame to 32F beforehand
    // and not individually for each channel.
    Mat hsv;
    std::vector<Mat> hsv_channels;
    cvtColor(frame, hsv, CV_BGR2HSV);
    split(hsv, hsv_channels);

    Mat hue, value, saturation;
    hsv_channels.at(HUE).convertTo(hue, CV_32F);
    hue /= HUE_MAX;
    hue = 1- hue;   
    hsv_channels.at(SATURATION).convertTo(saturation, CV_32F);
    Mat sat_pure = saturation.clone();
    saturation /= SATURATION_MAX;
    hsv_channels.at(VALUE).convertTo(value, CV_32F);
    value /= VALUE_MAX;

    //loop through the anns 
    //Declare multithreading variables
    PCERT_DATA pCert_Array[3];
    DWORD dwThreadIdArray[3];
    HANDLE hThreadArray[3];

    for (unsigned int ann_iter = 0; ann_iter < anns.size(); ann_iter++)
    
        //allocate memory for thread data
        pCert_Array[ann_iter] = (PCERT_DATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(CERT_DATA) );

        bool fft_flag = FALSE;
        //Convert the image block according to the ann
        switch (criteria.at(ann_iter))
        
            case HUE:
            
                pCert_Array[ann_iter]->converted_frame = &hue;
                break;
            
            case SATURATION:
            
                pCert_Array[ann_iter]->converted_frame = &saturation;
                break;
            
            case VALUE:
            
                pCert_Array[ann_iter]->converted_frame = &value;;
                break;
            
            case FREQUENCY:
            
                pCert_Array[ann_iter]->converted_frame = &sat_pure;
                fft_flag = TRUE;
                break;
            
            default:
            
                return Mat();
            
        


        //assign data to memory
        pCert_Array[ann_iter]->ann = anns.at(ann_iter);
        pCert_Array[ann_iter]->certainty = &(indiv_certs[ann_iter]);

        pCert_Array[ann_iter]->mask = &mask;
        pCert_Array[ann_iter]->fft_flag = fft_flag;
        pCert_Array[ann_iter]->individual_mask =  &(individual_masks[ann_iter]);
        pCert_Array[ann_iter]->threshold = thresholds.at(ann_iter);

        //call CalculateCertainty
        hThreadArray[ann_iter] = CreateThread( 
            NULL,                   // default security attributes
            0,                      // use default stack size  
            ComputeCertainty,       // thread function name
            pCert_Array[ann_iter],          // argument to thread function 
            0,                      // use default creation flags 
            &dwThreadIdArray[ann_iter]);   // returns the thread identifier         
    

    //wait for all the threads to stop computing
    WaitForMultipleObjects(anns.size(), hThreadArray, TRUE, INFINITE);

    //summing operation of certanties
    for (unsigned int i = 0; i < anns.size(); i++)
        certainty += indiv_certs[i];

    //AND all the various ANN outputs
    Mat temp = Mat::zeros(individual_masks[0].size(), individual_masks[0].type() );
    for (unsigned int i = 0; i < anns.size(); i++)
        temp += individual_masks[i];

    Mat temp_binary_mask = (temp == anns.size());
    temp = Mat::ones(individual_masks[0].size(), individual_masks[0].type() );
    temp.copyTo(final_mask, temp_binary_mask);

    cv::multiply(final_mask, certainty, final_certainty, 1.0/anns.size() );

    delete individual_masks;

    //deallocate threading memory used
    for(unsigned int i=0; i<anns.size(); i++)
    
        CloseHandle(hThreadArray[i]);
        if(pCert_Array[i] != NULL)
        
            HeapFree(GetProcessHeap(), 0, pCert_Array[i]);
            pCert_Array[i] = NULL;    // Ensure address is not reused.
        
    

    return final_mask;

【问题讨论】:

如果您混合使用 Debug 和 Release DLL 或 EXE,您会得到这个“错误”(在这种情况下,这不是错误,而是由不同的内存分配器引起的行为差异)。跨度> @crashmstr 我将编辑帖子并添加 c#。我检查了,我没有混合 DLL 或 EXE 文件 请在 c++ 代码中为 individual_masks 使用向量。避免指向 cv::Mat 【参考方案1】:

您是否尝试过使用内存泄漏检测工具,例如Visual Leak Detector?

在下面的代码中,

Mat* individual_masks = new Mat[anns.size()]();

应该使用operator delete[]删除,所以delete[] individual_masks;

此外,无论内存泄漏如何,都应该进行数组边界检查。

for (unsigned int ann_iter = 0; ann_iter < anns.size(); ann_iter++) 
 
    //allocate memory for thread data 
    pCert_Array[ann_iter] = (PCERT_DATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(CERT_DATA) );

anns是输入参数,所以它的大小不会是固定值,而是在PCERT_DATA pCert_Array[3];中声明了pCert_Array

【讨论】:

感谢您的指点。我们在三个特征上训练神经网络,所以在我们的例子中,anns 总是三个,但它不是很好的编程实践。所以即使你说我应该删除 Mat 指针对象,我仍然遇到同样的问题,我试图让 VLD 仍然工作

以上是关于堆中的 DLL 内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

JS档案揭秘第一集 内存泄漏与垃圾回收

带有 .NET 的 C++ dll:内存泄漏是确定的吗?

内存机制及内存泄漏相关总结

.NET WCF w3wp 本机内存泄漏和加载程序堆中 0 大小的 18k 动态程序集

C ++调用COM DLL中的函数可能发生内存泄漏

C++/C# 应用程序中的内存泄漏