【问题标题】:C++ Memory management failing when deleting Bitmap and CLSID objects using GDI+使用 GDI+ 删除位图和 CLSID 对象时 C++ 内存管理失败
【发布时间】:2019-06-22 01:03:44
【问题描述】:

我无法管理在屏幕截图对象类中创建的位图和 CLSID 对象的内存。这两个都来自 GDI+ 库。标头列出了 Screenshot.h 中的以下私有变量

#include <gdiplus.h>
#include <iostream>
#include <fstream>
#include <string>
#include "windows.h"
#pragma once
#pragma comment(lib, "gdiplus.lib")


using namespace std;
using namespace Gdiplus;


class Screenshot
{
private:
    HDC dc, memdc, fontdc;
    HBITMAP membit;
    Bitmap* bmpPtr;
    CLSID clsid;
ULONG_PTR gdiplusToken;
    int GetEncoderClsid(const WCHAR* format, CLSID* pClsid);

public:
    Screenshot();
    ~Screenshot();
    void TakeScreenshot(string userAction, string winName, long xMousePos, long yMousePos, long long tStamp);
    void SaveScreenshot(string filename);
    void memoryManagement();
};

然后当我的主程序截屏时,值是用 TakeScreenshot() 填充的,但还没有保存到磁盘

void Screenshot::TakeScreenshot(//redacted for readibility) {
GdiplusStartupInput gdiplusStartupInput;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    HWND hwnd = GetDesktopWindow();
    dc = ::GetDC(0);
    int scaleHeight, scaleWidth = 0;        
    int Height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
    int Width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
    scaleHeight = Height + (0.1 * Height);
    memdc = CreateCompatibleDC(dc);
    membit = CreateCompatibleBitmap(dc, Width, scaleHeight);
    HBITMAP bmpContainer = (HBITMAP)SelectObject(memdc, membit);
    BitBlt(memdc, 0, 0, Width, Height, dc, 0, 0, SRCCOPY);

    //Other code that adds fonts, etc. Does not invoke bmpPtr

    bmpPtr = new Bitmap(membit, NULL);
    GetEncoderClsid(L"image/jpeg", &clsid);

如果屏幕截图被保存,另一个函数 SaveScreenshot() 使用 bmpPtr->Save() 并在其中调用 Gdiplus 关闭。但是,某些屏幕截图会从队列(STL 队列)中弹出并超出内存而不是保存,如下所示:

void ManageQueue(Screenshot& ssObj)
{
    //If queue contains 30 screenshots, pop off first element and push new object
    //Else just push new object
    if (screenshotQueue.size() == MAX_SCREENSHOTS)
    {
        screenshotQueue.front().memoryManagement();
        screenshotQueue.pop();
        screenshotQueue.push(ssObj);
    }
    else
    {
        screenshotQueue.push(ssObj);
    }
}

我编写了一个 MemoryManagement() 函数来在屏幕截图弹出之前执行必要的释放和删除。如果屏幕截图已保存,则不会调用此函数:

void Screenshot::memoryManagement()
{
    delete bmpPtr;
    delete &clsid;
    ReleaseDC(NULL, memdc);
    DeleteObject(fontdc);  
    DeleteObject(memdc);
    DeleteObject(membit);
}

当调用 bmpPtr 或 clsid 上的 delete 时,无论是来自该函数还是在解构器中,程序都会崩溃。我现在在程序中遇到了严重的内存泄漏,并且没有运行相当于 Valgrind 的 Windows,我假设它来自这里。如何成功删除这些对象?作为贡献的程序员,我会将源代码中的任何答案归功于我。如果需要,请留下任何改进我的问题的建议。

【问题讨论】:

  • delete 中使用地址运算符&amp; 是一个很大的危险信号。只有delete 你实际上是new(和delete[] 你是new[])。
  • 我也许可以不删除 clsid 就逃脱,但位图肯定需要删除。完全删除 delete &clsid 仍然会导致 delete bmpPtr 崩溃。
  • 也许你应该显示Bitmap的构造函数和析构函数。将日志记录添加到您的程序或在调试器中逐步执行它,以确保被删除的bmpPtr 的指针值与您分配的指针值相同。问题也可能在其他地方,这取决于这些是否是 Screenshot 类中的成员变量(您对此不清楚),以及 Screenshot 类是如何构造、破坏、复制或通常以其他方式使用的。
  • 好吧,bmpPtr = new Bitmap(membit, NULL); 你当然应该这样做delete bmpPtr。如果崩溃,那么问题很可能出现在 Bitmap 类中(您没有向我们展示),但也可能是因为您对 bmpPtr 进行了一些重新分配而您没有向我们展示,或者因为您 delete bmpPtr多次?
  • 澄清:位图、HDC等是Gdi+库的一部分,不是我写的类或Screenshot的一部分。根据 MSDN,使用Bitmap 构造函数分配的新位图会保留在内存中,“直到 Bitmap::Bitmap 对象被删除或超出范围”。 bmpPtr 仅在我上面显示的代码中使用。 Screenshot 目前只使用默认构造函数和析构函数。

标签: c++ memory-leaks bitmap gdi+


【解决方案1】:
scaleHeight = Height + (0.1 * Height);

这似乎是为了解决 DPI 缩放问题。如果 DPI 设置为 10%,它将起作用,但通常情况并非如此。你必须通过清单文件让你的程序知道 DPI。使用SetProcessDPIAware 进行快速修复。

不要将dcmemdc 等声明为类成员。这些是 GDI 句柄(不是 GDI+),您可以短时间持有它们,通常是在函数执行期间。你必须尽快释放它们。

clsid 这样的其他变量也不需要声明为类成员。如果您愿意,您可以将它们声明为类成员,但没有任何好处。

如果您有多显示器设置,您还需要SM_XVIRTUALSCREEN/Y 才能获得显示器设置的左上角。

//call this once on start up
SetProcessDPIAware();

HDC dc = ::GetDC(0);
int x = GetSystemMetrics(SM_XVIRTUALSCREEN);
int y = GetSystemMetrics(SM_YVIRTUALSCREEN);
int Height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int Width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
HDC memdc = CreateCompatibleDC(dc);
HBITMAP membit = CreateCompatibleBitmap(dc, Width, Height);
HBITMAP bmpContainer = (HBITMAP)SelectObject(memdc, membit);
BitBlt(memdc, 0, 0, Width, Height, dc, x, y, SRCCOPY);

Bitmap* bmpPtr = new Bitmap(membit, NULL);
// or just Bitmap bmp(membit, NULL);

CLSID clsid;
GetEncoderClsid(L"image/jpeg", &clsid);
bmpPtr->Save(L"output.jpg", &clsid);

//cleanup:
delete bmpPtr;
SelectObject(memdc, bmpContainer);
DeleteObject(membit);
DeleteDC(memdc);
ReleaseDC(0, dc);

【讨论】:

  • 感谢您的回答。所以rescale不是为了DPI,而是在截图底部的空白区域添加文字。 GDI 句柄和 CLSID 作为类成员,因为我无法立即保存屏幕截图。相信我,我希望能够执行您的代码 sn-p,但屏幕截图必须在内存中保存一段未知的时间,并且不能立即保存。未保存的Screenshot 对象由 STL 队列管理,如果有更多屏幕截图进入,则会弹出。我在主要问题中添加了示例代码。
  • 屏幕截图可以独立于 GDI 句柄和其他变量进行存储。 delete &amp;clsid; 是错误的。我建议阅读 C++ 基础知识并从更简单的示例开始。
【解决方案2】:

此问题的解决方案是使用命名空间删除而不是常规删除。切换到此可防止在调试期间触发断点并密封内存泄漏。

    void Screenshot::memoryManagement()
{
    ::delete bmpPtr;
    ReleaseDC(NULL, memdc);
    DeleteObject(fontdc);
    DeleteObject(memdc);
    DeleteObject(membit);
    GdiplusShutdown(gdiplusToken);
}

【讨论】:

  • 应该是ReleaseDC(NULL, dc)以避免资源泄漏。 bmpPtr 是独立于其他句柄存储截图位图,您不需要保留其他句柄。你可以持有memdcmembit...并重复使用它们,但dc应该尽快释放。
猜你喜欢
  • 1970-01-01
  • 2023-03-16
  • 2011-08-10
  • 1970-01-01
  • 2015-11-01
  • 1970-01-01
  • 2014-09-02
  • 2011-10-07
  • 1970-01-01
相关资源
最近更新 更多