使用 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
进行快速修复。
不要将 dc
、memdc
等声明为 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);
}
我无法管理我在屏幕截图中创建的位图和 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
进行快速修复。
不要将 dc
、memdc
等声明为 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);
}