How can I determine the cause of a memory leak in a C++ application that uses GDI+ for image comparison?
My application needs to run 24/7, but after several hours of continuous operation, it displays a blank Microsoft Visual C++ Runtime Library window and crashes. Task Manager shows memory consumption around 8-20 MB. I’m deleting created objects through destructors.
I’m using the GDI+ library to compare two images: a desktop screenshot and a cropped template (pattern).
Here’s the class for creating desktop images:
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;
};
Here’s the method where I use the received data:
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 - Range
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;
}
What are the possible causes of memory leaks in this code and how can they be fixed?
The main problem in your code is related to improper management of GDI and GDI+ resources, as well as the lack of proper Bitmap object disposal. Memory leaks occur because many GDI+ objects are not properly deleted, and some GDI objects are handled incorrectly.
Table of Contents
- Main problems in the code
- Specific memory leaks
- How to fix the problems
- Tools for detecting leaks
- Performance optimization
- Example of corrected code
- Monitoring recommendations
Main problems in the code
Incorrect GDI object management:
DeleteDC(scrdc)is incorrect becausescrdcwas obtained throughGetDC(0), and should be released usingReleaseDCDeleteObject(scrdc)andDeleteObject(memdc)are incorrect becausescrdcandmemdcare of typeHDC, notHBITMAP- In
DeleteAllObject(), you’re trying to deletescrdcandmemdcthroughDeleteObject, which causes undefined behavior
GDI+ issues:
- In the
Searchmethod, aBitmapis created fromHBITMAP, but it’s not explicitly deleted - Color objects are created in a loop but not freed
GdiplusStartupis called multiple times without correspondingGdiplusShutdowncalls
Logical errors:
- In the
GDI_trinityWindowconstructor,hBitmapis assigned the result ofSelectObject, which returns the previous DC object, not the new bitmap - In the destructor,
DeleteObject(hBitmap)might delete a different object than the one that was created
Specific memory leaks
-
Bitmap object leaks: Each call to
SearchcreatesBitmap bmp_trinity(GDI.hBitmap, NULL), but this object is never explicitly deleted. In GDI+, Bitmaps should be deleted by callingDelete()or in the destructor. -
GDI context leaks:
scrdcwas obtained throughGetDC(0)and should be released viaReleaseDC(0, scrdc), notDeleteDC. -
GDI+ token leaks: Although the code has
GdiplusShutdowncalls, they might not execute when exceptions occur or when exiting functions prematurely. -
Temporary object leaks: In loops,
Colorobjects are created that accumulate in memory during long-running operations.
How to fix the problems
Fixing the GDI_trinityWindow class
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;
};
Fixing the Search method
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);
// Keep a copy for safe deletion
std::unique_ptr<Bitmap> bmp_guard(bmp_trinity);
int Height = bmp_trinity->GetHeight(), Width = bmp_trinity->GetWidth();
// Use safe saving method
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;
// Optimization: pre-allocate color object
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;
// Save the result
bmp_trinity->Save(L"test2.png", &pngClsid);
GdiplusShutdown(gdiplusToken);
return find;
}
catch (const std::exception& e) {
// Handle exceptions
cerr << "Error in Search: " << e.what() << endl;
return false;
}
}
Tools for detecting leaks
-
Visual Studio Memory Diagnostic Tool
- Use Visual Studio’s built-in memory profiler
- Run the application under the debugger with Memory Usage enabled
-
Valgrind (for Linux)
- Although your code is Windows-based, Valgrind can help in understanding leak detection principles
-
GDI+ Leak Detection
- Use
GdiplusSetBatchwith allocation tracking - Implement a system for counting created/destroyed objects
- Use
-
Custom Memory Tracking
cpp#define TRACK_MEMORY_ALLOCATION #ifdef TRACK_MEMORY_ALLOCATION #define new new(__FILE__, __LINE__) #endif
Performance optimization
-
Bitmap caching
cpp// Instead of creating Bitmap each time, cache it static std::unique_ptr<Bitmap> cachedBitmap; if (!cachedBitmap || cachedBitmap->GetWidth() != Width || cachedBitmap->GetHeight() != Height) { cachedBitmap.reset(new Bitmap(GDI.hBitmap, NULL)); } -
GDI+ object pool
- Implement a pool for reusing Bitmap objects
- Reduce the number of
GdiplusStartup/GdiplusShutdowncalls
-
Optimized comparison loops
cpp// Pre-calculate color differences int redDiff = i.Red - R; int greenDiff = i.Green - G; int blueDiff = i.Blue - B; // Check without repeated calculations if (abs(redDiff) <= D && abs(greenDiff) <= D && abs(blueDiff) <= D)
Example of corrected code
Here’s a fully corrected version of your code addressing all memory leaks:
class GDI_trinityWindow
{
public:
HBITMAP hBitmap;
RECT rc;
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
int Height, Width;
GDI_trinityWindow(wchar_t nameWindow[100])
{
// Initialize 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;
// Get DC and create compatible objects
scrdc = GetDC(0);
if (!scrdc) {
throw std::runtime_error("Failed to get DC");
}
memdc = CreateCompatibleDC(scrdc);
membit = CreateCompatibleBitmap(scrdc, Width, Height);
// Perform BitBlt
SelectObject(memdc, membit);
BitBlt(memdc, 0, 0, Width, Height, scrdc, 0, 0, SRCCOPY);
// Save the bitmap
hBitmap = (HBITMAP)SelectObject(memdc, membit);
}
~GDI_trinityWindow()
{
// Properly release all resources
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;
}
}
// Disable copying to prevent double freeing
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 {
// Initialize GDI+ once for the entire method
static ULONG_PTR gdiplusToken = 0;
static bool gdiplusInitialized = false;
if (!gdiplusInitialized) {
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
gdiplusInitialized = true;
}
GDI_trinityWindow GDI(winEVEOnline_ClassName);
// Use unique_ptr for automatic Bitmap deletion
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);
// Save the original image
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;
// Optimized comparison loop
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; // Reset counter but start with 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; // Reset if pixel doesn't match
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;
}
// Save the result
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;
}
}
Monitoring recommendations
-
Add memory allocation logging
cpp#ifdef _DEBUG class MemoryTracker { public: static void LogAllocation(const char* file, int line, size_t size) { // Log allocations } static void LogDeallocation(const char* file, int line, void* ptr) { // Log deallocations } }; #endif -
Use Task Manager for monitoring
- Run the application and observe memory consumption
- Check if consumption is stable or constantly growing
-
Add checkpoints
cppvoid CheckMemoryUsage() { MEMORYSTATUSEX memInfo; memInfo.dwLength = sizeof(MEMORYSTATUSEX); GlobalMemoryStatusEx(&memInfo); double physMemUsed = memInfo.ullTotalPhys - memInfo.ullAvailPhys; if (physMemUsed > (1024 * 1024 * 100)) { // More than 100 MB // Save state and restart SaveState(); RestartApplication(); } } -
Implement self-cleaning mechanism
cppvoid PerformCleanup() { // Force release of cached objects ClearCache(); // Recreate GDI+ context ULONG_PTR oldToken = gdiplusToken; GdiplusShutdown(oldToken); GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); }
The main problem in your code was improper management of GDI and GDI+ resources. After fixing these issues, your application should run stably 24/7 without memory leaks. It’s also recommended to add exception handling and memory usage monitoring to prevent similar issues in the future.