NeuroAgent

How to Find and Fix Memory Leaks in C++ with GDI+

Fix memory leaks in C++ applications with GDI+. Step-by-step guide to diagnosing and fixing issues with GDI+ objects and resources.

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:

cpp
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:

cpp
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?

NeuroAgent

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

Incorrect GDI object management:

  • DeleteDC(scrdc) is incorrect because scrdc was obtained through GetDC(0), and should be released using ReleaseDC
  • DeleteObject(scrdc) and DeleteObject(memdc) are incorrect because scrdc and memdc are of type HDC, not HBITMAP
  • In DeleteAllObject(), you’re trying to delete scrdc and memdc through DeleteObject, which causes undefined behavior

GDI+ issues:

  • In the Search method, a Bitmap is created from HBITMAP, but it’s not explicitly deleted
  • Color objects are created in a loop but not freed
  • GdiplusStartup is called multiple times without corresponding GdiplusShutdown calls

Logical errors:

  • In the GDI_trinityWindow constructor, hBitmap is assigned the result of SelectObject, 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

  1. Bitmap object leaks: Each call to Search creates Bitmap bmp_trinity(GDI.hBitmap, NULL), but this object is never explicitly deleted. In GDI+, Bitmaps should be deleted by calling Delete() or in the destructor.

  2. GDI context leaks: scrdc was obtained through GetDC(0) and should be released via ReleaseDC(0, scrdc), not DeleteDC.

  3. GDI+ token leaks: Although the code has GdiplusShutdown calls, they might not execute when exceptions occur or when exiting functions prematurely.

  4. Temporary object leaks: In loops, Color objects are created that accumulate in memory during long-running operations.


How to fix the problems

Fixing the GDI_trinityWindow class

cpp
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

cpp
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

  1. Visual Studio Memory Diagnostic Tool

    • Use Visual Studio’s built-in memory profiler
    • Run the application under the debugger with Memory Usage enabled
  2. Valgrind (for Linux)

    • Although your code is Windows-based, Valgrind can help in understanding leak detection principles
  3. GDI+ Leak Detection

    • Use GdiplusSetBatch with allocation tracking
    • Implement a system for counting created/destroyed objects
  4. Custom Memory Tracking

    cpp
    #define TRACK_MEMORY_ALLOCATION
    
    #ifdef TRACK_MEMORY_ALLOCATION
    #define new new(__FILE__, __LINE__)
    #endif
    

Performance optimization

  1. 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));
    }
    
  2. GDI+ object pool

    • Implement a pool for reusing Bitmap objects
    • Reduce the number of GdiplusStartup/GdiplusShutdown calls
  3. 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:

cpp
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

  1. 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
    
  2. Use Task Manager for monitoring

    • Run the application and observe memory consumption
    • Check if consumption is stable or constantly growing
  3. Add checkpoints

    cpp
    void 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();
        }
    }
    
  4. Implement self-cleaning mechanism

    cpp
    void 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.