While migrating the chart module (TeeChart) version in a brokerage HTS (Home Trading System), I ran into one of those bizarre bugs that would make any C++ developer clutch the back of their neck in despair.

When 30 to 50 screens were open at once and then closed all together with the "Close All Screens" feature, the program would randomly crash, or the UI at the top would tear and warp. Here I've written down the cause of this vicious bug β€” one that tormented me for days and nights β€” and how I finally got to the bottom of it.


1. The Symptom: CResourceException and GDI Leaks

🚨 Exception and symptoms observed:
  • During stress testing that repeatedly opened and closed a large number of chart screens, the debugger halted at the line CClientDC dc(this);.
  • Exception message: CResourceException (the OS refused to issue a device context).
  • Checking Task Manager showed that, even after every window was closed, the GDI object count never dropped back to baseline β€” it kept climbing step by step (a GDI leak).
  • The top menu and toolbar UI started breaking apart even before the GDI count came anywhere near Windows' 10,000-object limit.

2. The Decisive Clue: "Clicking Works Fine β€” So Why Does Closing All Leak?"

I sprinkled GetSafeHwnd() guard checks throughout the code, suspected multithreading conflicts, and dug through COM object reference counts, but nothing pointed to a clear solution. Then I stumbled on a clue that was both decisive and almost embarrassingly simple.

πŸ’‘ Behavioral comparison clue:
  • Closing windows one by one by clicking the 'X' button with the mouse: memory was released perfectly cleanly, with no GDI leak whatsoever.
  • Closing them all at once with the 'Close All Screens' button: GDI objects leaked steadily and the UI fell apart.

The root cause wasn't a bug in the TeeChart component itself β€” it turned out to be 'message pump (Message Pump) starvation' and 'shared DC exhaustion', both of which sit at the very heart of Windows programming.


3. Root Cause Analysis: A Collapsed Message Pump and Desktop DC Exhaustion

The original "Close All Screens" logic looked roughly like this.

// [Structure of the original, problematic code]
for (int i = 0; i < nTrNum; i++) {
    ::PostMessage(hwndFrm, WM_CLOSE, 0, 0L); // Fire off async close commands to 30 windows at once
}

// ... message handling further down ...
result = ::MsgWaitForMultipleObjects(...);

if (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { // ⬅️ The fatal trap!
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

This code had two fatal flaws.

  1. The betrayal of if (PeekMessage):
    As 30 windows are destroyed, thousands of messages flood the queue β€” WM_DESTROY plus a mountain of internal ActiveX (COM) GDI cleanup messages among them. But with an if statement, even when the queue is piled sky-high with messages, only a single one gets pulled out and processed before moving on. The GDI-release messages that never got cleaned up were left orphaned in the queue, which is exactly what led to the GDI leak.
  2. Shared DC (Desktop DC) deadlock:
    Because all 30 frames entered the destruction procedure at the same time via the asynchronous PostMessage, they all scrambled to grab the Windows OS's shared frame DC to handle their final repaint, creating a bottleneck. Once the limited shared DC cache ran dry, the HTS main menu and toolbar could no longer be drawn, and the UI became distorted.

4. The Fix: "Serialized Destruction" and while-Loop Pumping

The fix was to perfectly simulate, in code, the same conditions as closing windows one by one with the mouse. I applied a "serialization" approach: issue the destroy command to a single window, then wait until every last message that window emits has been fully drained before moving on to the next one.

// [Improved code: serialized destruction mechanism]
for (int i = 0; i < nTrNum; i++) {
    hwnd = (HWND)saHandle.at(i);
    if (!::IsWindow(hwnd)) continue;

    HWND hwndFrm = tbc_GetFrameHwnd(hwnd);
    if (hwndFrm && ::IsWindow(hwndFrm)) {

        // 1. Synchronize the destroy procedure with SendMessage instead of PostMessage
        ::SendMessage(hwndFrm, WM_CLOSE, 0, 0L);

        // 2. Scrape up and process every cleanup message generated as the window dies, down to the last one (if -> while)
        MSG msg;
        while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT) {
                ::PostQuitMessage(msg.wParam);
                break;
            }
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }
    }
}
πŸ”‘ Summary of the key changes:
  • PostMessage βž” SendMessage: prevents all 30 windows from dying at once, synchronizing things so each window gets its funeral in order, one at a time.
  • if(PeekMessage) βž” while(PeekMessage): fully drains the dozens of GDI/COM release messages that pile up in the queue when a single window is destroyed, all within one loop.

5. Wrapping Up

After applying this code, even closing 30 chart screens at once brought Task Manager's GDI object count perfectly back down to its baseline, and the UI distortion vanished as if it had never happened.

This whole debugging saga drove home, once again, just how important it is β€” when handling a large number of heavy ActiveX controls in a C++/MFC environment β€” to give the operating system's message queue enough time to digest things without choking, rather than focusing solely on object creation and destruction. I hope this post offers a small hint to anyone out there suffering from unexplained crashes and leaks in the world of multithreading and COM.