1

在这个函数中,经过大约 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”,这对我来说毫无意义。

4

2 回答 2

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);
        DeleteObject(temp);
    }
}
于 2016-09-21T05:33:31.147 回答
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)。
  • 这里真的还有另一个问题。由于您为位图资源分配了两个所有者,这将导致双重删除。它没有,因为您选择反对资源清理。(你不知道自己在做什么的事实拯救了你。下次庆祝你“能做”的态度时请记住这一点。)

以下是具有固定资源管理的版本。这对您没有任何意义,因为您对 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这样可以确保在超出范围时(在函数结束时)清理先前的资源。
于 2016-09-21T13:24:47.033 回答