多次调用后LoadBitmap失败

时间:2016-09-20 14:10:54

标签: c++ mfc hbitmap cbitmap

在这个函数中,在大约90次调用之后(它在一个循环中被调用,并且想法是每次加载一个单独的图像,但为了简单起见我将它保存到一个图像)。全局变量现在变为本地变量

   void CDLP_Printer_ControlDlg::DisplayBMPfromSVG(CString& strDsiplayFile)
{
    HBITMAP hbmp_temp = (HBITMAP)::LoadImage(0, strDsiplayFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
    if (!hbmp_temp)
    {
        //hbmp_temp = ::LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP1));
        ActionList.AddString(L"Bitmap Load Failure: GetBMPromSVG");
        ActionList.UpdateWindow();
        if (!hbmp_temp)
            return;
    }

    CBitmap bmp_temp;
    bmp_temp.Attach(hbmp_temp);
    mProjectorWindow.m_picControl.ModifyStyle(0xF, SS_BITMAP, SWP_NOSIZE);
    mProjectorWindow.m_picControl.SetBitmap(bmp_temp);

    return;
}

我希望有人能想出一个错误的想法。 GetLastError返回“8”,这对我来说没有任何意义。

2 个答案:

答案 0 :(得分:3)

Detach会破坏之前的句柄。

请注意,如果在调用Detach后调用SetBitmap,图片控件的位图将被销毁。结果是图片控件被绘制一次,但不会重新绘制。例如,如果调整对话框,则图片控件将变为空白。

修改

要销毁旧位图,请调用Detach,然后调用DestroyObject。实施例

HGDIOBJ hbitmap_detach = m_bitmap.Detach();
if (hbitmap_detach)
    DeleteObject(hbitmap_detach); 
m_bitmap.Attach(hbitmap);

如果是临时CBitmap,则不需要DeleteObject,因为DeleteObject超出范围时会自动调用CBitmap

请注意,如果在调用SetBitmap后销毁位图,则会破坏图片控件的位图。结果是图片控件被绘制一次,但不会重新绘制。例如,如果调整对话框,则图片控件将变为空白。

如果在堆栈上声明临时CBitmap并附加位图句柄,则会出现同样的问题。该位图句柄将被销毁,图片控制无法重新绘制。

此外,Windows XP有时会制作需要销毁的重复位图。 SetBitmap返回前一个位图的句柄。在Vista +中,返回的位图与m_bitmap中保存的位图相同,我们已经使用Detach销毁了位图。但是在XP中我们需要销毁这个副本,如果它是一个不同的句柄。

void CMyDialog::foo()
{
    HBITMAP save = m_bitmap;
    HBITMAP hbitmap = (HBITMAP)::LoadImage(0, filename,
        IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
    if (hbitmap)
    {
        HGDIOBJ hbitmap_detach = m_bitmap.Detach();
        //Edit ****************************************
        //Delete old handle, otherwise program crashes after 10,000 calls
        if (hbitmap_detach)
            DeleteObject(hbitmap_detach); 
        //*********************************************
        m_bitmap.Attach(hbitmap);

        HBITMAP oldbmp = m_picControl.SetBitmap(m_bitmap);

        //for Windows XP special case where there might be 2 copies:
        if (oldbmp && (oldbmp != save))
            DeleteObject(oldbmp);
    }
}

此外,SetBitmap需要HBITMAP参数并返回HBITMAP,因此您可以完全避免使用CBitmap。以下示例适用于Vista +

void foo()
{
    HBITMAP temp = (HBITMAP)::LoadImage(0,filename,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
    if (temp)
    {
        HBITMAP oldbmp = m_picControl.SetBitmap(temp);
        if (oldbmp)
            DeleteObject(oldbmp);
    }
}

答案 1 :(得分:1)

您的代码有几个问题,一些是次要的,另一些是致命的(实现实际上似乎只能起作用,因为操作系统已准备好处理这些常见的错误)。以下是原始代码的注释列表:

void CDLP_Printer_ControlDlg::DisplayBMPfromSVG(CString& strDsiplayFile) {
//                                              ^ should be const CString&
    HBITMAP hbmp_temp = (HBITMAP)::LoadImage(0, strDsiplayFile, IMAGE_BITMAP, 0, 0,
                                             LR_LOADFROMFILE);
    if (!hbmp_temp) {
        //hbmp_temp = ::LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP1));
        ActionList.AddString(L"Bitmap Load Failure: GetBMPromSVG");
        ActionList.UpdateWindow();
        if (!hbmp_temp)
//      You already know, that the condition is true (unless your commented out code
//      is supposed to run).
            return;
    }

    CBitmap bmp_temp;
    bmp_temp.Attach(hbmp_temp);
//  ^ This should immediately follow the LoadImage call, to benefit from automatic
//    resource management. (What's the point of using MFC when you decide to implement
//    manual resource management on top of it?)
    mProjectorWindow.m_picControl.ModifyStyle(0xF, SS_BITMAP, SWP_NOSIZE);
//                                            ^ Use named constants. No one is going to
//                                              look up the documentation just to find
//                                              out, what you are trying to do.
    mProjectorWindow.m_picControl.SetBitmap(bmp_temp);
//  The GDI object (hbmp_temp) now has two owners, the CBitmap instance bmp_temp, and
//  the picture control. At the same time, you are throwing away the handle previously
//  owned by the control. This is your GDI resource leak.

    return;
//  ^ Superfluous. This is merely confusing readers. Remove it.
}
// This is where things go fatal: The bmp_temp d'tor runs, destroying the GDI resource
// hbmp_temp, that's also owned by the control. This should really blow up in your face
// but the OS knows that developers cannot be trusted anymore, and covers your ass.

两个主要问题是:

  • 未能删除代码所拥有的GDI资源(SetBitmap的返回值)。这最终导致任何尝试创建其他GDI资源失败。
  • 通过将两个所有者分配到同一资源(HBITMAP)而导致的悬空指针(hbmp_temp)。
  • 这里还有另一个问题。由于您为位图资源分配了两个所有者,因此这将导致双重删除。它没有,因为你选择了资源清理。 (你不知道自己在做什么这一事实让你在这里得救了。下次celebrate your "can do" attitude时请记住这一点。)

以下是具有固定资源管理的版本。这对你没有任何意义,因为你不能很好地了解MFC(或C ++),以及如何帮助理解自动资源管理。总之:

void CDLP_Printer_ControlDlg::DisplayBMPfromSVG(CString& strDsiplayFile) {
    HBITMAP hbmp_temp = (HBITMAP)::LoadImage(0, strDsiplayFile, IMAGE_BITMAP, 0, 0,
                                             LR_LOADFROMFILE);
    // Immediately attach a C++ object, so that resources will get cleaned up
    // regardless how the function is exited.
    CBitmap bmp_temp;
    if (!bmp_temp.Attach(hbmp_temp)) {
        // Log error/load placeholder image
        return;
    }

    mProjectorWindow.m_picControl.ModifyStyle(0xF, SS_BITMAP, SWP_NOSIZE);
    // Swap the owned resource of bmp_temp with that of the control:
    bmp_temp.Attach(mProjectorWindow.m_picControl.SetBitmap(bmp_temp.Detach()));
}

最后一行是关键部分。它实现了将原始Windows API资源与资源管理包装器交换的规范方法。这是一系列操作:

  1. bmp_temp.Detach()释放GDI资源的所有权。
  2. SetBitmap()将GDI资源的所有权传递给控件,​​并返回先前的GDI对象(如果有)。
  3. bmp_temp.Attach()获取返回的GDI资源的所有权。这可以确保在bmp_temp超出范围时(在函数末尾)清理以前的资源。