DLL 中的 CImage 析构函数会阻塞整个 MFC 程序
CImage destructor in DLL blocks the whole MFC program
我正在使用 MFC 对话框 DLL,按下按钮时在我的主项目中调用。在这个 DLL 中,我使用 GDI+ 来显示和调整图像大小,在以下函数中:
#include "atlimage.h"
void CPhysicsDialogDlg::displayImage()
{
CImage img1;
img1.Load(m_pathText);
m_imgSize.x = img1.GetWidth();
m_imgSize.y = img1.GetHeight();
CDC *screenDC = GetDC();
CDC mDC;
mDC.CreateCompatibleDC(screenDC);
CBitmap b;
b.CreateCompatibleBitmap(screenDC, IMAGE_DISPLAY_WIDTH, IMAGE_DISPLAY_HEIGHT);
CBitmap *pob = mDC.SelectObject(&b);
mDC.SetStretchBltMode(HALFTONE);
img1.StretchBlt(mDC.m_hDC, 0, 0, IMAGE_DISPLAY_WIDTH, IMAGE_DISPLAY_HEIGHT, 0, 0,
img1.GetWidth(), img1.GetHeight(), SRCCOPY);
mDC.SelectObject(pob);
m_picture.SetBitmap((HBITMAP)b.Detach());
ReleaseDC(screenDC);
}
但是调用这个函数的时候,整个程序就完全卡住了。在调试模式下,当我尝试暂停并 运行 它时,Visual Studio 显示以下消息:
The process appears to be deadlocked (or is not running any user-mode
code). All threads have been stopped
调试的时候发现是来自atlimage.h
文件中的ReleaseGDIPlus
函数,在CImage
析构函数中调用:
inline void CImage::CInitGDIPlus::ReleaseGDIPlus() throw()
{
EnterCriticalSection(&m_sect);
if( m_dwToken != 0 )
{
Gdiplus::GdiplusShutdown( m_dwToken ); // this line freezes everything
}
m_dwToken = 0;
LeaveCriticalSection(&m_sect);
}
有趣的是,当我运行将这个单一对话框作为 EXE 而不是作为 DLL 调用时,一切正常。
我试图在函数中明确地执行 GDI+ 启动和关闭,同样的问题:
void CPhysicsDialogDlg::displayImage()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
//function code
GdiplusShutdown(gdiplusToken);
}
我正在使用 Visual Studio 2005 和 Windows 7.
所以问题是:为什么 ReleaseGDIPlus
函数会阻塞,我该如何解决这个问题?
原因如下:
- 你必须 initialize/uninitialize 在 DLL 之外的 GDI+ 并且只做一次
- GDI+ 不是线程安全的。所以你应该只在主应用程序线程中使用它。
如果您在 MFC 应用程序中使用 DLL,您应该:
在 InitInstance 中初始化它
在 ExitInstance 中取消初始化。
无论如何,它应该只在主线程内完成一次。
当一个完全不相关的常规 MFC DLL 被卸载时,我们现在也遇到了这个问题。这不知何故混淆了关机代码。我认为 MS 在这里犯了一个错误,将 GDI+ init 和 shutdown 放在一个全局对象 (CInitGDIPlus) 中,从而增加了 GDI+ 在 DllMain 中被关闭的风险。这完全违反了他们自己的一套规则。
关于这个有一篇知识库文章(Q322909)。问题在 VS2013 中仍然存在。
进一步研究发现,常规 DLL(具有自己的 CWinApp 实例)与 MFC 结合作为共享 DLL 会扰乱 MFC 的全局管理。 AfxGlobalsAddRef / AfxGlobalsRelease 使用全局引用计数。当引用计数变为零时,MFC 释放其资源。在这些资源中有 GDI+ 资源(例如使用 CImage 的 CPngImage)。在此过程中可能会调用 GDI+ 关闭代码。由于引用计数现在仅在卸载常规 MFC DLL 时才降为零,因此在 DllMain 期间会有效调用 GDI+ 关闭代码,从而导致挂起。好像是MFC的一个bug。
一个解决方案可能是过早地释放 DLL。如果它们是 COM 组件,可以使用 'CoFreeUnusedLibrariesEx'。如果您隐式链接,我想没有解决方法。
如果您将 MFC 用作共享 DLL,请确保在覆盖 CWinApp::InitInstance
时调用 __super::InitInstance
(您应该有一个派生自 CWinApp
并覆盖 InitInstance
和 ExitInstance
)。这将告诉 MFC 在调用 __super::ExitInstance
之前您需要全局变量,确保它们没有被其他 DLL 释放!
我正在使用 MFC 对话框 DLL,按下按钮时在我的主项目中调用。在这个 DLL 中,我使用 GDI+ 来显示和调整图像大小,在以下函数中:
#include "atlimage.h"
void CPhysicsDialogDlg::displayImage()
{
CImage img1;
img1.Load(m_pathText);
m_imgSize.x = img1.GetWidth();
m_imgSize.y = img1.GetHeight();
CDC *screenDC = GetDC();
CDC mDC;
mDC.CreateCompatibleDC(screenDC);
CBitmap b;
b.CreateCompatibleBitmap(screenDC, IMAGE_DISPLAY_WIDTH, IMAGE_DISPLAY_HEIGHT);
CBitmap *pob = mDC.SelectObject(&b);
mDC.SetStretchBltMode(HALFTONE);
img1.StretchBlt(mDC.m_hDC, 0, 0, IMAGE_DISPLAY_WIDTH, IMAGE_DISPLAY_HEIGHT, 0, 0,
img1.GetWidth(), img1.GetHeight(), SRCCOPY);
mDC.SelectObject(pob);
m_picture.SetBitmap((HBITMAP)b.Detach());
ReleaseDC(screenDC);
}
但是调用这个函数的时候,整个程序就完全卡住了。在调试模式下,当我尝试暂停并 运行 它时,Visual Studio 显示以下消息:
The process appears to be deadlocked (or is not running any user-mode code). All threads have been stopped
调试的时候发现是来自atlimage.h
文件中的ReleaseGDIPlus
函数,在CImage
析构函数中调用:
inline void CImage::CInitGDIPlus::ReleaseGDIPlus() throw()
{
EnterCriticalSection(&m_sect);
if( m_dwToken != 0 )
{
Gdiplus::GdiplusShutdown( m_dwToken ); // this line freezes everything
}
m_dwToken = 0;
LeaveCriticalSection(&m_sect);
}
有趣的是,当我运行将这个单一对话框作为 EXE 而不是作为 DLL 调用时,一切正常。
我试图在函数中明确地执行 GDI+ 启动和关闭,同样的问题:
void CPhysicsDialogDlg::displayImage()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
//function code
GdiplusShutdown(gdiplusToken);
}
我正在使用 Visual Studio 2005 和 Windows 7.
所以问题是:为什么 ReleaseGDIPlus
函数会阻塞,我该如何解决这个问题?
原因如下:
- 你必须 initialize/uninitialize 在 DLL 之外的 GDI+ 并且只做一次
- GDI+ 不是线程安全的。所以你应该只在主应用程序线程中使用它。
如果您在 MFC 应用程序中使用 DLL,您应该:
在 InitInstance 中初始化它
在 ExitInstance 中取消初始化。
无论如何,它应该只在主线程内完成一次。
当一个完全不相关的常规 MFC DLL 被卸载时,我们现在也遇到了这个问题。这不知何故混淆了关机代码。我认为 MS 在这里犯了一个错误,将 GDI+ init 和 shutdown 放在一个全局对象 (CInitGDIPlus) 中,从而增加了 GDI+ 在 DllMain 中被关闭的风险。这完全违反了他们自己的一套规则。
关于这个有一篇知识库文章(Q322909)。问题在 VS2013 中仍然存在。
进一步研究发现,常规 DLL(具有自己的 CWinApp 实例)与 MFC 结合作为共享 DLL 会扰乱 MFC 的全局管理。 AfxGlobalsAddRef / AfxGlobalsRelease 使用全局引用计数。当引用计数变为零时,MFC 释放其资源。在这些资源中有 GDI+ 资源(例如使用 CImage 的 CPngImage)。在此过程中可能会调用 GDI+ 关闭代码。由于引用计数现在仅在卸载常规 MFC DLL 时才降为零,因此在 DllMain 期间会有效调用 GDI+ 关闭代码,从而导致挂起。好像是MFC的一个bug。
一个解决方案可能是过早地释放 DLL。如果它们是 COM 组件,可以使用 'CoFreeUnusedLibrariesEx'。如果您隐式链接,我想没有解决方法。
如果您将 MFC 用作共享 DLL,请确保在覆盖 CWinApp::InitInstance
时调用 __super::InitInstance
(您应该有一个派生自 CWinApp
并覆盖 InitInstance
和 ExitInstance
)。这将告诉 MFC 在调用 __super::ExitInstance
之前您需要全局变量,确保它们没有被其他 DLL 释放!