多次调用后 LoadBitmap 失败

LoadBitmap fails after multiple calls

在这个函数中,调用了大约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 returns “8” 对我来说毫无意义。

Detach会破坏之前的句柄。

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

编辑

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

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

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

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

如果您在堆栈上声明一个临时 CBitmap 并附加位图句柄,也会出现同样的问题。该位图句柄将被破坏,图片控件无法自行重绘。

此外,WindowsXP有时会产生重复的位图,同样需要销毁。 SetBitmap returns 上一个位图的句柄。在 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 参数和 returns 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);
        DeleteObject(temp);
    }
}

您的代码有几个问题,一些是小问题,另一些是致命的(并且实现实际上只是看起来有效,因为 OS 已准备好处理这些常见错误)。这是原始代码的注释清单:

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 的 return 值)。这最终会导致任何创建额外 GDI 资源的尝试都失败。
  • 由于将两个所有者分配给同一资源 (hbmp_temp) 而导致的悬挂指针 (HBITMAP)。
  • 这里确实还有一个问题。由于您为位图资源分配了两个所有者,这将导致双重删除。它不会,因为您选择反对资源清理。 (你不知道自己在做什么的事实让你在这里救了你。下次你 时请记住这一点。)

以下是固定资源管理的版本。这对您来说没有任何意义,因为您对 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 资源的所有权传递给控件,​​并且 returns 以前的 GDI 对象(如果有的话)。
  3. bmp_temp.Attach() 获得 returned GDI 资源的所有权。这可确保在 bmp_temp 超出范围(在函数末尾)时清除先前的资源。