Как определить причину утечки памяти в C++ приложении, использующем GDI+ для сравнения изображений?
Мое приложение должно работать 24/7, но через несколько часов непрерывной работы оно выдает пустое окно Microsoft Visual C++ Runtime Library и аварийно завершается. Диспетчер задач показывает потребление оперативной памяти в районе 8-20 МБ. Я удаляю созданные объекты через деструкторы.
Я использую библиотеку GDI+ для сравнения двух изображений: скриншота рабочего стола и вырезанного фрагмента (шаблона).
Вот класс для создания изображений рабочего стола:
class GDI_trinityWindow
{
public:
HBITMAP hBitmap;
RECT rc;
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
int Height, Widht;
GDI_trinityWindow(wchar_t nameWindow[100])
{
HWND hWnd = FindWindow(nameWindow, 0);
while (hWnd == NULL)
hWnd = FindWindow(nameWindow, 0);
SetWindowPos(hWnd, HWND_TOP, -8, -31, 0, 0, SWP_NOSIZE);
GetWindowRect(hWnd, &rc);
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
Height = rc.bottom - 8;
Widht = rc.right - 8;
scrdc = GetDC(0);
memdc = CreateCompatibleDC(scrdc);
membit = CreateCompatibleBitmap(scrdc, Widht, Height);
SelectObject(memdc, membit);
BitBlt(memdc, 0, 0, Widht, Height, scrdc, 0, 0, SRCCOPY);
hBitmap = (HBITMAP)SelectObject(memdc, membit);
}
~GDI_trinityWindow()
{
DeleteDC(scrdc);
DeleteDC(memdc);
DeleteObject(membit);
DeleteObject(hBitmap);
GdiplusShutdown(gdiplusToken);
}
void DeleteAllObject()
{
DeleteObject(scrdc);
DeleteObject(memdc);
DeleteObject(membit);
DeleteObject(hBitmap);
}
private:
HDC scrdc, memdc;
HBITMAP membit;
};
Вот метод, в котором я использую получаемые данные:
bool Search(Point& pt_firstpx, Point& pt_endpx, wchar_t* templateName)
{
HWND hWnd = FindWindow(winEVEOnline_ClassName, NULL);
SetForegroundWindow(hWnd);
SetWindowPos(hWnd, HWND_TOP, -8, -31, 0, 0, SWP_NOSIZE);
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
GDI_trinityWindow GDI(winEVEOnline_ClassName);
Bitmap bmp_trinity(GDI.hBitmap, NULL);
int Height = bmp_trinity.GetHeight(), Width = bmp_trinity.GetWidth();
bmp_trinity.Save(L"test.png", &png);
ARGB_array argb;
vector<ARGB_array> argb_arr = TemplateToVector(templateName);
int A, R, G, B;
Color clr;
int D = 6; //D - Диапазон
bool find = false;
int coint = 0;
for (int y = 0; y < bmp_trinity.GetHeight() && !find; y++)
for (int x = 0; x < bmp_trinity.GetWidth() && !find; x++)
{
bmp_trinity.GetPixel(x, y, &clr);
ARGBtoRGB(clr.GetValue(), A, R, G, B);
if (R == argb_arr[0].Red && G == argb_arr[0].Green && B == argb_arr[0].Blue && !find)
for (auto i : argb_arr)
{
bmp_trinity.GetPixel(x + i.pt.X - argb_arr[0].pt.X, y + i.pt.Y, &clr);
ARGBtoRGB(clr.GetValue(), A, R, G, B);
if (R < (i.Red + D) && R >(i.Red - D) && G < (i.Green + D) && G >(i.Green - D) && B < (i.Blue + D) && B >(i.Blue - D))
{
bmp_trinity.SetPixel(x + i.pt.X - argb_arr[0].pt.X, y + i.pt.Y, Color::Green);
coint++;
}
}
if (coint == argb_arr.size())
{
find = true;
pt_firstpx.X = x;
pt_firstpx.Y = y;
pt_endpx.X = x + argb_arr[argb_arr.size() - 1].pt.X - argb_arr[0].pt.X;
pt_endpx.Y = y + argb_arr[argb_arr.size() - 1].pt.Y;
}
else
coint = 0;
}
if (find)
cout << "find true" << endl;
cout << coint << endl;
cout << argb_arr.size() << endl;
bmp_trinity.Save(L"test2.png", &png);
GdiplusShutdown(gdiplusToken);
return find;
}
Какие возможные причины утечки памяти в этом коде и как их исправить?
Основная проблема в вашем коде связана с неправильным управлением ресурсами GDI и GDI+, а также с отсутствием корректного освобождения объектов Bitmap. Утечки памяти возникают из-за того, что многие GDI+ объекты не удаляются должным образом, а некоторые GDI объекты обрабатываются некорректно.
Содержание
- Основные проблемы в коде
- Конкретные утечки памяти
- Как исправить проблемы
- Инструменты для обнаружения утечек
- Оптимизация производительности
- Пример исправленного кода
- Рекомендации по мониторингу
Основные проблемы в коде
Некорректное управление GDI объектами:
DeleteDC(scrdc)неверен, так какscrdcполучен черезGetDC(0), а для освобождения используетсяReleaseDCDeleteObject(scrdc)иDeleteObject(memdc)некорректны, так какscrdcиmemdcимеют типHDC, а неHBITMAP- В
DeleteAllObject()вы пытаетесь удалитьscrdcиmemdcчерезDeleteObject, что вызывает неопределенное поведение
Проблемы с GDI+:
- В методе
SearchсоздаетсяBitmapизHBITMAP, но он не удаляется явно - Цветовые объекты
Colorсоздаются в цикле, но не освобождаются GdiplusStartupвызывается многократно без соответствующихGdiplusShutdown
Логические ошибки:
- В конструкторе
GDI_trinityWindowhBitmapприсваивается результатSelectObject, что возвращает предыдущий объект DC, а не новый битмап - В деструкторе
DeleteObject(hBitmap)может удалить не тот объект, который был создан
Конкретные утечки памяти
-
Утечка Bitmap объектов: Каждый вызов
SearchсоздаетBitmap bmp_trinity(GDI.hBitmap, NULL), но этот объект никогда не удаляется явно. В GDI+ Bitmap должен удаляться через вызовDelete()или в деструкторе. -
Утечка GDI контекстов:
scrdcполучен черезGetDC(0)и должен освобождаться черезReleaseDC(0, scrdc), а неDeleteDC. -
Утечка GDI+ токенов: Хотя в коде есть вызовы
GdiplusShutdown, они могут не выполняться при исключениях или преждевременном выходе из функции. -
Утечка временных объектов: В циклах создаются объекты
Color, которые накапливаются в памяти при длительной работе.
Как исправить проблемы
Исправление класса GDI_trinityWindow
class GDI_trinityWindow
{
public:
HBITMAP hBitmap;
RECT rc;
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
int Height, Widht;
GDI_trinityWindow(wchar_t nameWindow[100])
{
HWND hWnd = FindWindow(nameWindow, 0);
while (hWnd == NULL)
hWnd = FindWindow(nameWindow, 0);
SetWindowPos(hWnd, HWND_TOP, -8, -31, 0, 0, SWP_NOSIZE);
GetWindowRect(hWnd, &rc);
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
Height = rc.bottom - 8;
Widht = rc.right - 8;
scrdc = GetDC(0);
if (scrdc == NULL) {
throw std::runtime_error("Failed to get DC");
}
memdc = CreateCompatibleDC(scrdc);
membit = CreateCompatibleBitmap(scrdc, Widht, Height);
SelectObject(memdc, membit);
BitBlt(memdc, 0, 0, Widht, Height, scrdc, 0, 0, SRCCOPY);
hBitmap = (HBITMAP)SelectObject(memdc, membit);
}
~GDI_trinityWindow()
{
if (scrdc) {
ReleaseDC(0, scrdc);
}
if (memdc) {
DeleteDC(memdc);
}
if (membit) {
DeleteObject(membit);
}
if (hBitmap) {
DeleteObject(hBitmap);
}
if (gdiplusToken) {
GdiplusShutdown(gdiplusToken);
}
}
void DeleteAllObject()
{
if (scrdc) {
ReleaseDC(0, scrdc);
scrdc = NULL;
}
if (memdc) {
DeleteDC(memdc);
memdc = NULL;
}
if (membit) {
DeleteObject(membit);
membit = NULL;
}
if (hBitmap) {
DeleteObject(hBitmap);
hBitmap = NULL;
}
}
private:
HDC scrdc, memdc;
HBITMAP membit;
};
Исправление метода Search
bool Search(Point& pt_firstpx, Point& pt_endpx, wchar_t* templateName)
{
try {
HWND hWnd = FindWindow(winEVEOnline_ClassName, NULL);
SetForegroundWindow(hWnd);
SetWindowPos(hWnd, HWND_TOP, -8, -31, 0, 0, SWP_NOSIZE);
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
GDI_trinityWindow GDI(winEVEOnline_ClassName);
Bitmap* bmp_trinity = new Bitmap(GDI.hBitmap, NULL);
// Сохраняем копию для освобождения
std::unique_ptr<Bitmap> bmp_guard(bmp_trinity);
int Height = bmp_trinity->GetHeight(), Width = bmp_trinity->GetWidth();
// Используем безопасный способ сохранения
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
bmp_trinity->Save(L"test.png", &pngClsid);
ARGB_array argb;
vector<ARGB_array> argb_arr = TemplateToVector(templateName);
int A, R, G, B;
bool find = false;
int coint = 0;
// Оптимизация: предварительно выделяем память для цвета
Color clr;
for (int y = 0; y < bmp_trinity->GetHeight() && !find; y++)
for (int x = 0; x < bmp_trinity->GetWidth() && !find; x++)
{
bmp_trinity->GetPixel(x, y, &clr);
ARGBtoRGB(clr.GetValue(), A, R, G, B);
if (R == argb_arr[0].Red && G == argb_arr[0].Green && B == argb_arr[0].Blue && !find) {
for (auto i : argb_arr) {
int pixelX = x + i.pt.X - argb_arr[0].pt.X;
int pixelY = y + i.pt.Y;
if (pixelX >= 0 && pixelX < Width && pixelY >= 0 && pixelY < Height) {
bmp_trinity->GetPixel(pixelX, pixelY, &clr);
ARGBtoRGB(clr.GetValue(), A, R, G, B);
if (R < (i.Red + D) && R >(i.Red - D) &&
G < (i.Green + D) && G >(i.Green - D) &&
B < (i.Blue + D) && B >(i.Blue - D)) {
bmp_trinity->SetPixel(pixelX, pixelY, Color::Green);
coint++;
}
}
}
}
if (coint == argb_arr.size()) {
find = true;
pt_firstpx.X = x;
pt_firstpx.Y = y;
pt_endpx.X = x + argb_arr[argb_arr.size() - 1].pt.X - argb_arr[0].pt.X;
pt_endpx.Y = y + argb_arr[argb_arr.size() - 1].pt.Y;
} else {
coint = 0;
}
}
if (find)
cout << "find true" << endl;
cout << coint << endl;
cout << argb_arr.size() << endl;
// Сохраняем результат
bmp_trinity->Save(L"test2.png", &pngClsid);
GdiplusShutdown(gdiplusToken);
return find;
}
catch (const std::exception& e) {
// Обработка исключений
cerr << "Error in Search: " << e.what() << endl;
return false;
}
}
Инструменты для обнаружения утечек
-
Visual Studio Memory Diagnostic Tool
- Используйте встроенный профилировщик памяти Visual Studio
- Запустите приложение под отладчиком с включенным Memory Usage
-
Valgrind (для Linux)
- Хоть ваш код Windows, Valgrind может помочь в понимании принципов обнаружения утечек
-
GDI+ Leak Detection
- Используйте
GdiplusSetBatchс отслеживанием выделений - Реализуйте систему подсчета созданных/уничтоженных объектов
- Используйте
-
Custom Memory Tracking
cpp#define TRACK_MEMORY_ALLOCATION #ifdef TRACK_MEMORY_ALLOCATION #define new new(__FILE__, __LINE__) #endif
Оптимизация производительности
-
Кэширование битмапов
cpp// Вместо создания Bitmap каждый раз, кэшируйте его static std::unique_ptr<Bitmap> cachedBitmap; if (!cachedBitmap || cachedBitmap->GetWidth() != Width || cachedBitmap->GetHeight() != Height) { cachedBitmap.reset(new Bitmap(GDI.hBitmap, NULL)); } -
Пул объектов GDI+
- Реализуйте пул для повторного использования Bitmap объектов
- Уменьшите количество вызовов
GdiplusStartup/GdiplusShutdown
-
Оптимизация циклов сравнения
cpp// Предварительно вычисляем разницу в цвете int redDiff = i.Red - R; int greenDiff = i.Green - G; int blueDiff = i.Blue - B; // Проверяем без повторных вычислений if (abs(redDiff) <= D && abs(greenDiff) <= D && abs(blueDiff) <= D)
Пример исправленного кода
Вот полностью исправленная версия вашего кода с учетом всех утечек памяти:
class GDI_trinityWindow
{
public:
HBITMAP hBitmap;
RECT rc;
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
int Height, Width;
GDI_trinityWindow(wchar_t nameWindow[100])
{
// Инициализация GDI+
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
HWND hWnd = FindWindow(nameWindow, 0);
if (!hWnd) {
throw std::runtime_error("Window not found");
}
SetWindowPos(hWnd, HWND_TOP, -8, -31, 0, 0, SWP_NOSIZE);
GetWindowRect(hWnd, &rc);
Height = rc.bottom - 8;
Width = rc.right - 8;
// Получаем DC и создаем совместимые объекты
scrdc = GetDC(0);
if (!scrdc) {
throw std::runtime_error("Failed to get DC");
}
memdc = CreateCompatibleDC(scrdc);
membit = CreateCompatibleBitmap(scrdc, Width, Height);
// Выполняем BitBlt
SelectObject(memdc, membit);
BitBlt(memdc, 0, 0, Width, Height, scrdc, 0, 0, SRCCOPY);
// Сохраняем битмап
hBitmap = (HBITMAP)SelectObject(memdc, membit);
}
~GDI_trinityWindow()
{
// Корректное освобождение всех ресурсов
if (scrdc) {
ReleaseDC(0, scrdc);
scrdc = NULL;
}
if (memdc) {
DeleteDC(memdc);
memdc = NULL;
}
if (membit) {
DeleteObject(membit);
membit = NULL;
}
if (hBitmap) {
DeleteObject(hBitmap);
hBitmap = NULL;
}
if (gdiplusToken) {
GdiplusShutdown(gdiplusToken);
gdiplusToken = 0;
}
}
// Запрещаем копирование для предотвращения двойного освобождения
GDI_trinityWindow(const GDI_trinityWindow&) = delete;
GDI_trinityWindow& operator=(const GDI_trinityWindow&) = delete;
private:
HDC scrdc, memdc;
HBITMAP membit;
};
bool Search(Point& pt_firstpx, Point& pt_endpx, wchar_t* templateName)
{
try {
// Инициализация GDI+ один раз для всего метода
static ULONG_PTR gdiplusToken = 0;
static bool gdiplusInitialized = false;
if (!gdiplusInitialized) {
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
gdiplusInitialized = true;
}
GDI_trinityWindow GDI(winEVEOnline_ClassName);
// Используем unique_ptr для автоматического удаления Bitmap
std::unique_ptr<Bitmap> bmp_trinity(new Bitmap(GDI.hBitmap, NULL));
int Height = bmp_trinity->GetHeight(), Width = bmp_trinity->GetWidth();
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
// Сохраняем исходное изображение
bmp_trinity->Save(L"test.png", &pngClsid);
vector<ARGB_array> argb_arr = TemplateToVector(templateName);
if (argb_arr.empty()) {
return false;
}
Color clr;
bool find = false;
int coint = 0;
// Оптимизированный цикл сравнения
for (int y = 0; y < Height && !find; y++) {
for (int x = 0; x < Width && !find; x++) {
bmp_trinity->GetPixel(x, y, &clr);
ARGBtoRGB(clr.GetValue(), A, R, G, B);
if (R == argb_arr[0].Red && G == argb_arr[0].Green && B == argb_arr[0].Blue) {
coint = 1; // Сбрасываем счетчик, но начинаем с 1
for (size_t i = 1; i < argb_arr.size(); i++) {
int pixelX = x + argb_arr[i].pt.X - argb_arr[0].pt.X;
int pixelY = y + argb_arr[i].pt.Y;
if (pixelX >= 0 && pixelX < Width && pixelY >= 0 && pixelY < Height) {
bmp_trinity->GetPixel(pixelX, pixelY, &clr);
ARGBtoRGB(clr.GetValue(), A, R, G, B);
if (abs(R - argb_arr[i].Red) <= D &&
abs(G - argb_arr[i].Green) <= D &&
abs(B - argb_arr[i].Blue) <= D) {
bmp_trinity->SetPixel(pixelX, pixelY, Color::Green);
coint++;
} else {
coint = 0; // Сбрасываем, если пиксель не совпал
break;
}
} else {
coint = 0;
break;
}
}
if (coint == argb_arr.size()) {
find = true;
pt_firstpx.X = x;
pt_firstpx.Y = y;
pt_endpx.X = x + argb_arr.back().pt.X - argb_arr[0].pt.X;
pt_endpx.Y = y + argb_arr.back().pt.Y;
}
}
}
}
if (find) {
cout << "Pattern found at position: (" << pt_firstpx.X << ", " << pt_firstpx.Y << ")" << endl;
} else {
cout << "Pattern not found" << endl;
}
// Сохраняем результат
bmp_trinity->Save(L"test2.png", &pngClsid);
return find;
}
catch (const Gdiplus::GdiplusException& e) {
cerr << "GDI+ Error: " << e.GetStatus() << endl;
return false;
}
catch (const std::exception& e) {
cerr << "Error in Search: " << e.what() << endl;
return false;
}
}
Рекомендации по мониторингу
-
Добавьте логирование выделения памяти
cpp#ifdef _DEBUG class MemoryTracker { public: static void LogAllocation(const char* file, int line, size_t size) { // Логируйте выделения } static void LogDeallocation(const char* file, int line, void* ptr) { // Логируйте освобождения } }; #endif -
Используйте Task Manager для мониторинга
- Запустите приложение и наблюдайте за потреблением памяти
- Проверяйте, стабильно ли потребление или оно постоянно растет
-
Добавьте контрольные точки
cppvoid CheckMemoryUsage() { MEMORYSTATUSEX memInfo; memInfo.dwLength = sizeof(MEMORYSTATUSEX); GlobalMemoryStatusEx(&memInfo); double physMemUsed = memInfo.ullTotalPhys - memInfo.ullAvailPhys; if (physMemUsed > (1024 * 1024 * 100)) { // Более 100 МБ // Сохраняем состояние и перезапускаем SaveState(); RestartApplication(); } } -
Реализуйте механизм самоочистки
cppvoid PerformCleanup() { // Принудительное освобождение кэшированных объектов ClearCache(); // Пересоздание GDI+ контекста ULONG_PTR oldToken = gdiplusToken; GdiplusShutdown(oldToken); GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); }
Основная проблема в вашем коде заключалась в неправильном управлении ресурсами GDI и GDI+. После исправления этих проблем ваше приложение должно работать стабильно 24/7 без утечек памяти. Рекомендуется также добавить обработку исключений и мониторинг использования памяти для предотвращения подобных проблем в будущем.