【问题标题】:Memory Loss from WM_Paint's BeginPaint() functionWM_Paint 的 BeginPaint() 函数导致内存丢失
【发布时间】:2015-03-06 21:38:16
【问题描述】:

我查看了该网站和其他网站对类似问题的多个回复,虽然我觉得我已经接近了,但我还是不能完全正确。不过,这可能是一个非常愚蠢的问题。


所以我过去只每隔几分钟调用一次 WndProc 案例“WM_Paint”(通过 InvalidateRect),所以我并没有真正注意到泄漏。现在我添加了一些每秒调用它大约 5 次的东西。在那一秒钟内,我的内存使用量跃升了大约 3800k。是的,这引起了人们的注意... 代码如下:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        // Other cases omitted since we skip them due to "case WM_Paint:".
        case WM_PAINT:
            wndProc_Paint(hwnd);
        break;
        // Other cases omitted since we skip them due to "break;".
    }
    return 0;
}

void wndProc_Paint(HWND hwnd)
{
    g_hbmBoard = ConvertIplImageToHBITMAP(targetBoardImg); //OpenCV command
    BITMAP bm;
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps); // <- Breakpoint here while monitoring mem usage shows this is what is adding ~772k per call which never gets released.
    HDC hdcMem = CreateCompatibleDC(hdc);

    HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmBoard);
    GetObject(g_hbmBoard, sizeof(bm), &bm);
    BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmGreenLight);
    GetObject(g_hbmGreenLight, sizeof(bm), &bm);
    BitBlt(hdc, screenResW - 59, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmWorkingLight);
    GetObject(g_hbmWorkingLight, sizeof(bm), &bm);
    BitBlt(hdc, screenResW - 94, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmWorkingIndicator);
    GetObject(g_hbmWorkingIndicator, sizeof(bm), &bm);
    BitBlt(hdc, screenResW - 129, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    //DeleteObject(hbmOld);
    SelectObject(hdcMem, hbmOld);
    DeleteDC(hdcMem);
    //SelectObject(hdc, hbmOld);
    //ReleaseDC(hwnd, hdc);
    //DeleteDC(hdc);
    //DeleteObject(hdc);

    EndPaint(hwnd, &ps);
}

除了 g_hbmWorkingIndicator 段和注释掉的部分之外,这就是我的 WM_Paint 在它被调用 5 次/秒之前的样子(甚至在我将它放入它自己的函数之前 - 这是一个单独的问题)。

行“HDC hdc = BeginPaint(hwnd, &ps);”是内存被添加的地方,它永远不会被释放。每次我们通过函数时都会发生这种情况。所以现在我尝试解决这个问题。

阅读了类似的问题,我认为我需要释放或删除 hdc 的 DC。我不是(仍然不是 - 是的,我已经阅读了 MSDN 页面)确定 SelectObject 如何与这些一起工作,所以我已经尝试过使用和不使用 ReleaseDC 和 DeleteDC(&both),用于 hdc 和 hdcMem。我还为 hdc 和 hdcMem 尝试了 DeleteObject。这些都没有任何影响。

DeleteObject(hbmOld) 有效果。它解决了问题。除了它是错误的解决方案之外,因为虽然我的记忆没有失控,但我确实有一些视觉效果被错误的视觉效果所取代。 g_hbmGreenLight 通常获取 g_hbmBoard 的图形,而 g_hbmWorkingLight 变为绿色(g_hbmGreenLight 的图形)。移动“DeleteObject(hbmOld);”在 EndPaint(~) 之后或尝试使用“SelectObject”只会更改哪些对象被哪个错误的图形替换 - 同样;返回内存泄漏。


编辑: 为了完整起见,我在此处包含了 ConvertIplImageToHBITMAP(IplImage* image) 的代码。这完全有可能是罪魁祸首。

HBITMAP ConvertIplImageToHBITMAP(IplImage* pImage)
{
    IplImage* image = (IplImage*)pImage;
    bool imgConverted = false;

    if(pImage->nChannels != 3)
    {
        IplImage* imageCh3 = cvCreateImage(cvGetSize(pImage), 8, 3);
        if(pImage->nChannels==1){cvCvtColor(pImage, imageCh3, CV_GRAY2RGB);}
        image = imageCh3;
        imgConverted = true;
    }

    int bpp = image->nChannels * 8;
    assert(image->width >= 0 && image->height >= 0 && (bpp == 8 || bpp == 24 || bpp == 32));
    CvMat dst;
    void* dst_ptr = 0;
    HBITMAP hbmp = NULL;
    unsigned char buffer[sizeof(BITMAPINFO) + 255*sizeof(RGBQUAD)];
    BITMAPINFO* bmi = (BITMAPINFO*)buffer;
    BITMAPINFOHEADER* bmih = &(bmi->bmiHeader);

    ZeroMemory(bmih, sizeof(BITMAPINFOHEADER));
    bmih->biSize = sizeof(BITMAPINFOHEADER);
    bmih->biWidth = image->width;
    bmih->biHeight = image->origin ? abs(image->height) : -abs(image->height);
    bmih->biPlanes = 1;
    bmih->biBitCount = bpp;
    bmih->biCompression = BI_RGB;

    if (bpp == 8)
    {
        RGBQUAD* palette = bmi->bmiColors;
        int i;
        for (i = 0; i < 256; i++)
        {
            palette[i].rgbRed = palette[i].rgbGreen = palette[i].rgbBlue = (BYTE)i;
            palette[i].rgbReserved = 0;
        }
    }

    hbmp = CreateDIBSection(NULL, bmi, DIB_RGB_COLORS, &dst_ptr, 0, 0);
    cvInitMatHeader(&dst, image->height, image->width, CV_8UC3, dst_ptr, (image->width * image->nChannels + 3) & -4);
    cvConvertImage(image, &dst, image->origin ? CV_CVTIMG_FLIP : 0);

    if(imgConverted)
    {cvReleaseImage(&image);}

    return hbmp;
}

所以,这就是说:帮助我 StackExchange,你是我唯一的希望! ;_;

【问题讨论】:

  • 如果有的话,我会查看g_hbmBoard = ... 与每个WM_PAINT 消息的重复调用。你的绘画结构、内存中的 dc 和其他 GDI 东西的其余设置至少在最初看起来是合理的(尽管我承认我并没有真正仔细研究过粗略的检查)。您正确选择旧的 hbm,并“删除”您“创建”的内容,这是 dc 正确的。
  • 你为什么不把它降到最低限度,做一个我们可以编译和运行的完整程序?
  • 雷米的回答非常出色,值得最终选择。不相关,如果您觉得可以应对挑战,您可以考虑使用您初始化的 PAINTSTRUCT 中的无效区域坐标来优化您正在使用的区域,或者如果有问题的更新区域不在范围内,甚至完全避免它们用于任何特定给定操作的 blt 矩形。当然,这假设您仅通过此处未显示的代码首先正确地使 需要 更新的区域无效。否则就没有真正的胜利。祝你好运。
  • @WhozCraig 我 am 仅在 code-not-seen-here 中使需要更新的区域无效,因此我一定会对此进行研究。那就是说;尽管雷米的回答非常出色,但内存泄漏仍然失控。我想了解并修复错误或首先提出问题实际上并没有任何目的。

标签: c++ winapi


【解决方案1】:

您正在丢失由SelectObject(hdcMem, g_hbmBoard) 返回的原始HBITMAP,因此您在调用DeleteDC() 之前没有正确恢复它,因此它被泄露了。每次调用 SelectObject() 时都会覆盖 hbmOld 变量,但在再次覆盖 hbmOld 之前,您不会将当前的 hbmOld 值恢复为 dcMem。使用SelectObject() 时,您必须在完成对HDC 的更改后恢复原始对象。

试试这个:

void wndProc_Paint(HWND hwnd)
{
    g_hbmBoard = ConvertIplImageToHBITMAP(targetBoardImg); //OpenCV command

    BITMAP bm;
    PAINTSTRUCT ps;

    HDC hdc = BeginPaint(hwnd, &ps);
    HDC hdcMem = CreateCompatibleDC(hdc);

    HBITMAP hbmOld = (HBITMAP) SelectObject(hdcMem, g_hbmBoard); // save the original HBITMAP
    GetObject(g_hbmBoard, sizeof(bm), &bm);
    BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    SelectObject(hdcMem, g_hbmGreenLight); // returns g_hbmBoard, no need to save it to hbmpOld
    GetObject(g_hbmGreenLight, sizeof(bm), &bm);
    BitBlt(hdc, screenResW - 59, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    SelectObject(hdcMem, g_hbmWorkingLight); // returns g_hbmGreenLight, no need to save it to hbmpOld
    GetObject(g_hbmWorkingLight, sizeof(bm), &bm);
    BitBlt(hdc, screenResW - 94, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    SelectObject(hdcMem, g_hbmWorkingIndicator); // returns g_hbmWorkingLight, no need to save it to hbmpOld
    GetObject(g_hbmWorkingIndicator, sizeof(bm), &bm);
    BitBlt(hdc, screenResW - 129, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    SelectObject(hdcMem, hbmOld); // restore the original HBITMAP

    DeleteDC(hdcMem);
    EndPaint(hwnd, &ps);

    // who owns g_hbmBoard? ConvertIplImageToHBITMAP() creates
    // a new HBITMAP, so you need to free it with DeleteObject()
    // before calling ConvertIplImageToHBITMAP() again.  It would
    // be better to create g_hbmBoard one time outside of WM_PAINT,
    // recreate g_hbmBoard only when the source image actually changes,
    // and then re-use g_hbmBoard as-is inside of WM_PAINT.
    // ConvertIplImageToHBITMAP() really does not belong in WM_PAINT...
    //
    //DeleteObject(g_hbmBoard);
}

或者,改用SaveDC()/RestoreDC()

void wndProc_Paint(HWND hwnd)
{
    g_hbmBoard = ConvertIplImageToHBITMAP(targetBoardImg); //OpenCV command

    BITMAP bm;
    PAINTSTRUCT ps;

    HDC hdc = BeginPaint(hwnd, &ps);
    HDC hdcMem = CreateCompatibleDC(hdc);

    int iOldState = SaveDC(hdcMem); // save everything the HDC currently has selected

    SelectObject(hdcMem, g_hbmBoard);
    GetObject(g_hbmBoard, sizeof(bm), &bm);
    BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    SelectObject(hdcMem, g_hbmGreenLight);
    GetObject(g_hbmGreenLight, sizeof(bm), &bm);
    BitBlt(hdc, screenResW - 59, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    SelectObject(hdcMem, g_hbmWorkingLight);
    GetObject(g_hbmWorkingLight, sizeof(bm), &bm);
    BitBlt(hdc, screenResW - 94, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    SelectObject(hdcMem, g_hbmWorkingIndicator);
    GetObject(g_hbmWorkingIndicator, sizeof(bm), &bm);
    BitBlt(hdc, screenResW - 129, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);

    RestoreDC(hdcMem, iOldState); // restore everything the HDC originally had selected

    DeleteDC(hdcMem);
    EndPaint(hwnd, &ps);

    // who owns g_hbmBoard? ConvertIplImageToHBITMAP() creates
    // a new HBITMAP, so you need to free it with DeleteObject()
    // before calling ConvertIplImageToHBITMAP() again.  It would
    // be better to create g_hbmBoard one time outside of WM_PAINT,
    // recreate g_hbmBoard only when the source image actually changes,
    // and then re-use g_hbmBoard as-is inside of WM_PAINT...
    // ConvertIplImageToHBITMAP() really does not belong in WM_PAINT...
    //
    //DeleteObject(g_hbmBoard);
}

【讨论】:

  • 上升。不错的答案。在您回答之前,我没有注意到 hbmOld 的重复覆盖。很棒的收获。
  • 嗯...不幸的是,这些都不起作用。在第一个中,您是否删除了所有“hbmOld = (HBITMAP)...”行?至于 g_hbmBoard - 这是一个全球性的。 ConvertIplImageToHBITMAP() 不会创建任何新的 HDC - 它主要使用 OpenCV 库命令并且不应该在屏幕上绘制任何内容。实际上;看来我根本不需要 hbmOld ,就像您在第二个中一样。这就是我复制/修改示例所得到的 ;_;
  • @Alexander ConvertIplImageToHBITMAP 每次调用都会创建一个新的位图,还是只是返回一个一次性初始化的位图句柄?如果您使用第一个列表,则一旦从 mem-dc 中选择第一个,删除那些 hbmOld 覆盖是正确的。第二个显然是首选,因为它简单(恕我直言)。我认为 Remy 的意思是“如果 ConvertIplImageToHBITMAP() 创建一个新的 HBITMAP...”
  • @WhozCraig & Remy - 奇怪的是,我忘记了 ConvertIplImageToHBITMAP() 实际上是我代码中的一个函数,而不是来自 OpenCV 库的函数。我已经包含了上面的代码(在原始问题中),所以不仅仅是我给你道听途说的答案!
  • @Alexander 在创建新的之前删除旧的g_hbmBoard
猜你喜欢
  • 2019-01-11
  • 1970-01-01
  • 2013-08-17
  • 2014-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-30
  • 2021-11-27
相关资源
最近更新 更多