DLL中的CImage析构函数阻塞整个MFC程序

时间:2015-04-15 08:54:42

标签: c++ dll mfc gdi+

我正在使用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将显示以下消息:

  

该过程似乎已死锁(或未运行任何用户模式   码)。所有线程都已停止

调试时,我发现它来自ReleaseGDIPlus文件中的atlimage.h函数,在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功能会阻塞,我该如何解决?

4 个答案:

答案 0 :(得分:2)

原因如下:

  1. 您必须在DLL之外初始化/取消初始化GDI +并且只执行一次
  2. GDI +不是线程安全的。所以你应该只在主app线程中使用它。

答案 1 :(得分:2)

如果您在MFC应用程序中使用DLL,您应该:

在InitInstance

中初始化它

在ExitInstance中取消初始化。

无论如何,它应该只在主线程中完成一次。

答案 2 :(得分:1)

当卸载完全不相关的常规MFC DLL时,我们现在也遇到了问题。不知何故,这混淆了关机代码。我认为MS在这里通过在全局对象(CInitGDIPlus)中放置GDI + init和关闭来犯错,从而增加了GDI +在DllMain中关闭的风险。这完全违背了他们自己的一套规则。

有关于此的kb文章(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已关闭连接)。

答案 3 :(得分:1)

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