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 函数会阻塞,我该如何解决这个问题?

原因如下:

  1. 你必须 initialize/uninitialize 在 DLL 之外的 GDI+ 并且只做一次
  2. 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'。如果您隐式链接,我想没有解决方法。

https://connect.microsoft.com/VisualStudio/feedback/details/1470256/mfc-hangs-when-unloading-a-regular-dll-when-using-gdi

参见https://developercommunity.visualstudio.com/content/problem/246188/msconnect-1470256mfc-hangs-when-unloading-a-regula.html(MS 已关闭连接)。

如果您将 MFC 用作共享 DLL,请确保在覆盖 CWinApp::InitInstance 时调用 __super::InitInstance(您应该有一个派生自 CWinApp 并覆盖 InitInstanceExitInstance)。这将告诉 MFC 在调用 __super::ExitInstance 之前您需要全局变量,确保它们没有被其他 DLL 释放!