使用 GDI+ 删除位图和 CLSID objects 时,C++ 内存管理失败

C++ Memory management failing when deleting Bitmap and CLSID objects using GDI+

我无法管理我在屏幕截图中创建的位图和 CLSID object 的内存 object class。这两个都来自 GDI+ 库。 header 列出了 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 shutdown。但是,一些屏幕截图从 queue (STL queue) 中弹出并且内存不足而不是保存,如下所示:

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 我假设它来自这里。如何才能成功删除这些object?我会将源代码中的任何答案归功于贡献程序员。如果需要,请留下任何改进我的问题的建议。

scaleHeight = Height + (0.1 * Height);

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

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

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

如果您有多显示器设置,您还需要 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);

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

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