本人最近做项目,发现在MFC Debug环境,使用OpenCV动态库会误报内存泄露。使用windbg.exe工具仍然无法排查问题所在。于是在opencv开源站点提交了issues。链接如下:
https://github.com/opencv/opencv/issues/16591
另外,Opencv论坛也有人提这个问题,但是似乎没有太好的办法:
http://www.opencv.org.cn/forum/forum.php?mod=viewthread&tid=265559 中文论坛
https://answers.opencv.org/questions/scope:all/sort:activity-desc/page:1/query:mfc%20memory%20leak/ 英文论坛
https://groups.google.com/forum/#!topic/microsoft.public.vc.mfc/5tMGENar9R4/discussion
1、问题描述
新建一个MFC应用程序,导入OpenCV动态库,程序使用了opencv变量和函数。当程序在运行时,一切正常,但是点击应用程序关闭按钮时,那么问题就来了,输出窗口会出现一大堆内存泄漏问题。如下所示:
程序“[13260] mfctest.exe”已退出,返回值为 2 (0x2)。
注:实践证明,这是误报的内存泄漏。这个现象仅限于MFC Debug的情况,MFC Release不会。此外,Windows控制台应用程序也无此误报现象。
2、原因分析
之前在网上看过一篇文章,说是因为opencv有关的dll文件比mfc dll先加载导致的。在VS输出窗口可以看到,opencv_xxx.dll在mfc的相关dll之前加载。
“mfctest.exe”(Win32): 已加载“F:\tmp-source\cv420test\Debug\mfctest.exe”。已加载符号。
“mfctest.exe”(Win32): 已加载“C:\Windows\SysWOW64\ntdll.dll”。已加载符号。
“mfctest.exe”(Win32): 已加载“C:\Windows\SysWOW64\kernel32.dll”。已加载符号。
“mfctest.exe”(Win32): 已加载“C:\Windows\SysWOW64\KernelBase.dll”。已加载符号。
“mfctest.exe”(Win32): 已加载“F:\tmp-source\cv420test\Debug\opencv_core420d.dll”。已加载符号。
“mfctest.exe”(Win32): 已加载“F:\tmp-source\cv420test\Debug\tbb_debug.dll”。已加载符号。
“mfctest.exe”(Win32): 已加载“C:\Windows\SysWOW64\msvcp140d.dll”。已加载符号。
“mfctest.exe”(Win32): 已加载“C:\Windows\SysWOW64\vcruntime140d.dll”。已加载符号。
“mfctest.exe”(Win32): 已加载“C:\Windows\SysWOW64\ucrtbased.dll”。已加载符号。
“mfctest.exe”(Win32): 已加载“F:\tmp-source\cv420test\Debug\opencv_imgcodecs420d.dll”。已加载符号。
“mfctest.exe”(Win32): 已加载“F:\tmp-source\cv420test\Debug\opencv_imgproc420d.dll”。已加载符号。
“mfctest.exe”(Win32): 已加载“C:\Windows\SysWOW64\mfc140ud.dll”。已加载符号。
“mfctest.exe”(Win32): 已加载“C:\Windows\SysWOW64\advapi32.dll”。已加载符号。
“mfctest.exe”(Win32): 已加载“C:\Windows\SysWOW64\msvcrt.dll”。已加载符号。
但是这个办法真的能有效解决吗?未必!
3、网上分享的解决办法
在网上可以搜索到的解决办法,有以下四种,本人每个都动手实践了一下,
(1)把OpenCV编译成静态库,并且也在静态库中使用MFC
编译时有两种方案供选择:
A方案使用默认值,VC++ OpenCV源码工程->配置属性->C++->代码生成->运行库->多线程调试 (/MTd)和多线程 (/MT)
B方案是把运行库修改为多线程调试 DLL (/MDd)和多线程 DLL (/MD)
我们自己工程中使用OpenCV的静态库。然后:
如果是第A方案
VC++自己的工程->配置属性>常规>MFC的使用“ ,由“在共享 DLL 中使用 MFC”修改为“在静态库中使用 MFC”。
VC++自己的工程->配置属性->C++->代码生成->运行库->多线程调试 (/MTd)和多线程 (/MT)
VC++自己的工程的Debug和Release分别要使用OpenCV的Debug和Release静态库。不可以只使用Release的库。
(这种情况是很多人采取的做法,也是OpenCV官方推荐的解决办法之一。)
如果是B方案
VC++自己的工程->配置属性>常规>MFC的使用“ ,维持默认值不变,“在共享 DLL 中使用 MFC”。
VC++自己的工程->配置属性->C++->代码生成->运行库->多线程调试 DLL (/MDd)和多线程 DLL (/MD)
VC++自己的工程的Debug和Release分别要使用OpenCV的Debug和Release静态库。不可以只使用Release的库。
(firecat实践结果是不可行,编译通不过,error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MTd_StaticDebug”不匹配值“MDd_DynamicDebug”)
(2)把OpenCV编译成动态库,但是在静态库中使用MFC
VC++自己的工程->配置属性>常规>MFC的使用“ ,由“在共享 DLL 中使用 MFC”修改为“在静态库中使用 MFC”。
(这种情况是很多人采取的做法,也是OpenCV官方推荐的解决办法之一。)
(3)将opencv的dll二次封装到新的dll之中。
在mfc应用程序中不要出现opencv的函数和变量。自己写个库程序,封装成dll,然后延迟加载这个dll。
(firecat实践结果是不可行,仍然有内存泄露!)
(4)采取dll延迟加载技术
(不是太推荐!这个方法不能确保总是成功,firecat实践结果是有时成功,有时不成功)
仅针对debug模式,点击VC++项目属性,进入属性页,依次找到通用属性、链接器、输入、延迟加载的DLL选项,然后把OpenCV的bin文件下的所有dll的名称添加到延迟加载的DLL中。添加完之后,点击确定,然后再重新编译一下程序,就没有内存泄漏出现了。因为mfc下内存泄露主要是由于opencv dll先加载导致,因此只需要延迟加载即可。
设置完毕,编译,会提示警告,没关系,不变理会:
1>LINK : warning LNK4199: 已忽略 /DELAYLOAD:opencv_aruco420d.dll;未找到来自 opencv_aruco420d.dll 的导入
1>LINK : warning LNK4199: 已忽略 /DELAYLOAD:opencv_bgsegm420d.dll;未找到来自 opencv_bgsegm420d.dll 的导入
......
1>LINK : warning LNK4199: 已忽略 /DELAYLOAD:opencv_ximgproc420d.dll;未找到来自 opencv_ximgproc420d.dll 的导入
1>LINK : warning LNK4199: 已忽略 /DELAYLOAD:opencv_xobjdetect420d.dll;未找到来自 opencv_xobjdetect420d.dll 的导入
1>LINK : warning LNK4199: 已忽略 /DELAYLOAD:opencv_xphoto420d.dll;未找到来自 opencv_xphoto420d.dll 的导入
1>mfctest.vcxproj -> F:\tmp-source\cv420test\Debug\mfctest.exe
1>已完成生成项目“mfctest.vcxproj”的操作。
4、最后附上我的opencv debug dll动态库文件清单:
opencv_aruco420d.dll opencv_bgsegm420d.dll opencv_bioinspired420d.dll opencv_calib3d420d.dll opencv_ccalib420d.dll opencv_core420d.dll opencv_datasets420d.dll opencv_dnn_objdetect420d.dll opencv_dnn_superres420d.dll opencv_dnn420d.dll opencv_dpm420d.dll opencv_face420d.dll opencv_features2d420d.dll opencv_flann420d.dll opencv_fuzzy420d.dll opencv_gapi420d.dll opencv_hfs420d.dll opencv_highgui420d.dll opencv_img_hash420d.dll opencv_imgcodecs420d.dll opencv_imgproc420d.dll opencv_line_descriptor420d.dll opencv_ml420d.dll opencv_objdetect420d.dll opencv_optflow420d.dll opencv_phase_unwrapping420d.dll opencv_photo420d.dll opencv_plot420d.dll opencv_python3.dll opencv_quality420d.dll opencv_reg420d.dll opencv_rgbd420d.dll opencv_saliency420d.dll opencv_shape420d.dll opencv_stereo420d.dll opencv_stitching420d.dll opencv_structured_light420d.dll opencv_superres420d.dll opencv_surface_matching420d.dll opencv_text420d.dll opencv_tracking420d.dll opencv_ts420d.dll opencv_video420d.dll opencv_videoio420d.dll opencv_videostab420d.dll opencv_xfeatures2d420d.dll opencv_ximgproc420d.dll opencv_xobjdetect420d.dll opencv_xphoto420d.dll
OpenCV Release staticLib静态库文件清单,少部分lib需要从\opencv-4.2.0\build_x32_static\3rdparty拷贝
ippiw.lib ippicvmt.lib ittnotify.lib tbb.lib zlib.lib libjpeg-turbo.lib libwebp.lib libpng.lib libtiff.lib libjasper.lib IlmImf.lib opencv_aruco420.lib opencv_bgsegm420.lib opencv_bioinspired420.lib opencv_calib3d420.lib opencv_ccalib420.lib opencv_core420.lib opencv_datasets420.lib opencv_dnn_objdetect420.lib opencv_dnn_superres420.lib opencv_dnn420.lib opencv_dpm420.lib opencv_face420.lib opencv_features2d420.lib opencv_flann420.lib opencv_fuzzy420.lib opencv_gapi420.lib opencv_hfs420.lib opencv_highgui420.lib opencv_img_hash420.lib opencv_imgcodecs420.lib opencv_imgproc420.lib opencv_line_descriptor420.lib opencv_ml420.lib opencv_objdetect420.lib opencv_optflow420.lib opencv_phase_unwrapping420.lib opencv_photo420.lib opencv_plot420.lib opencv_quality420.lib opencv_reg420.lib opencv_rgbd420.lib opencv_saliency420.lib opencv_shape420.lib opencv_stereo420.lib opencv_stitching420.lib opencv_structured_light420.lib opencv_superres420.lib opencv_surface_matching420.lib opencv_text420.lib opencv_tracking420.lib opencv_video420.lib opencv_videoio420.lib opencv_videostab420.lib opencv_xfeatures2d420.lib opencv_ximgproc420.lib opencv_xobjdetect420.lib opencv_xphoto420.lib
---
参考文献