From 2dc2136b787815e0baafb2beedf453e78d97ea22 Mon Sep 17 00:00:00 2001 From: Kamil Marczak Date: Thu, 8 Oct 2026 15:59:26 +0200 Subject: [PATCH] fix(mfcwx): stop deferred tab activation from using a deleted main frame Closing an MDI child posted a call to the main frame's OnChildActivated, and the notebook's page-changed handler captured the frame pointer. When the main window closed right after (closing the app with documents open), the CMainFrame object was already deleted when those ran, which crashed in CMDIFrameWnd::OnChildActivated. Both now look the frame up by its window when they run and do nothing if it is gone. Also routes ID_APP_EXIT to CWinApp::OnAppExit as MFC's CWinApp message map does (File > Exit did nothing), and adds a close step to MFCWX_AUTOMATION. --- port/mfcwx/src/app.cpp | 1 + port/mfcwx/src/docview.cpp | 24 ++++++++++++++++++------ port/mfcwx/src/snapshot.cpp | 5 +++++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/port/mfcwx/src/app.cpp b/port/mfcwx/src/app.cpp index 4217b043..12850ea0 100644 --- a/port/mfcwx/src/app.cpp +++ b/port/mfcwx/src/app.cpp @@ -236,6 +236,7 @@ int main(int argc, char** argv) { return wxEntry(argc, argv); } IMPLEMENT_DYNAMIC(CWinApp, CWinThread) BEGIN_MESSAGE_MAP(CWinApp, CWinThread) + ON_COMMAND(ID_APP_EXIT, &CWinApp::OnAppExit) ON_COMMAND_EX_RANGE(ID_FILE_MRU_FILE1, ID_FILE_MRU_FILE16, &CWinApp::OnOpenRecentFile) ON_UPDATE_COMMAND_UI(ID_FILE_MRU_FILE1, &CWinApp::OnUpdateRecentFileMenu) END_MESSAGE_MAP() diff --git a/port/mfcwx/src/docview.cpp b/port/mfcwx/src/docview.cpp index 8f2477a8..a463f6e1 100644 --- a/port/mfcwx/src/docview.cpp +++ b/port/mfcwx/src/docview.cpp @@ -88,6 +88,14 @@ wxAuiNotebook* ClientNotebook(CMDIFrameWnd* frame) { return frame && frame->m_hWndMDIClient ? wxDynamicCast(ToWx(frame->m_hWndMDIClient), wxAuiNotebook) : nullptr; } +// Resolves a frame by its window at call time, so deferred calls never use a deleted frame object. +CMDIFrameWnd* LiveMDIFrame(HWND hWnd) { + if (!hWnd || !IsWindow(hWnd)) + return nullptr; + CWnd* wnd = PermanentWnd(ToWx(hWnd)); + return wnd && wnd->IsKindOf(RUNTIME_CLASS(CMDIFrameWnd)) ? static_cast(wnd) : nullptr; +} + CMDIFrameWnd* MainMDIFrame() { CWinApp* app = AfxGetApp(); CWnd* main = app ? app->m_pMainWnd : nullptr; @@ -893,9 +901,11 @@ BOOL CMDIFrameWnd::OnCreateClient(LPCREATESTRUCT, CCreateContext*) { auto* sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(book, 1, wxEXPAND); frame->SetSizer(sizer); - book->Bind(wxEVT_AUINOTEBOOK_PAGE_CHANGED, [this](wxAuiNotebookEvent& e) { + HWND frameWnd = m_hWnd; + book->Bind(wxEVT_AUINOTEBOOK_PAGE_CHANGED, [frameWnd](wxAuiNotebookEvent& e) { e.Skip(); - OnChildActivated(); + if (CMDIFrameWnd* mdi = LiveMDIFrame(frameWnd)) + mdi->OnChildActivated(); }); book->Bind(wxEVT_AUINOTEBOOK_PAGE_CLOSE, [book](wxAuiNotebookEvent& e) { e.Veto(); @@ -1117,11 +1127,13 @@ BOOL CMDIChildWnd::DestroyWindow() { book->RemovePage(static_cast(index)); } DestroyWxWindow(panel); - if (mdi) - PostToMainThread([mdi] { - if (mdi->m_hWnd) - mdi->OnChildActivated(); + if (mdi) { + HWND frameWnd = mdi->m_hWnd; + PostToMainThread([frameWnd] { + if (CMDIFrameWnd* live = LiveMDIFrame(frameWnd)) + live->OnChildActivated(); }); + } return TRUE; }); } diff --git a/port/mfcwx/src/snapshot.cpp b/port/mfcwx/src/snapshot.cpp index 3acd640b..9413efa0 100644 --- a/port/mfcwx/src/snapshot.cpp +++ b/port/mfcwx/src/snapshot.cpp @@ -129,6 +129,7 @@ wxWindow* ActiveDialog() { // Development aid: MFCWX_AUTOMATION=file runs one step per line on a timer, for UI tests: // command WM_COMMAND to the main window // button WM_COMMAND (BN_CLICKED) to the topmost dialog +// close WM_CLOSE to the main window // text sets the text of a control of the topmost dialog // tab / closetab clicks the n-th MDI tab or its close button // tabs print the MDI tabs @@ -161,6 +162,10 @@ class AutomationTimer : public wxTimer { target->PostMessage(WM_COMMAND, MAKEWPARAM(id, BN_CLICKED), 0); else fprintf(stderr, "mfcwx automation: no target window\n"); + } else if (op == "close") { + CWnd* main = AfxGetMainWnd(); + if (main && main->m_hWnd) + main->PostMessage(WM_CLOSE, 0, 0); } else if (op == "text") { int id = 0; in >> id;