MFC 對話方塊銷毀機制在模態 (DoModal) 與非模態 (Create) 間存在本質差異,錯誤用法會導致記憶體洩漏、Debug 斷言失敗甚至崩潰。本文系統梳理銷毀流程、正確重寫點與自動清理模式。
// 使用者點擊「確定」
OnOK() → UpdateData(TRUE) → EndDialog(IDOK) → DestroyWindow() → OnDestroy() → PostNcDestroy()
// 使用者點擊「取消」/Esc
OnCancel() → EndDialog(IDCANCEL) → DestroyWindow() → OnDestroy() → PostNcDestroy()
// 關閉按鈕 (系統選單)
OnClose() → OnCancel() → ... (同上)
// 關鍵等價關係
EndDialog(IDOK) ≡ OnOK() (含 UpdateData)
EndDialog(IDCANCEL) ≡ OnCancel() ≡ OnClose() (不含 UpdateData)
CDialog::OnOK()、CDialog::OnCancel()OnOK 中 return 阻止關閉OnDestroy 或 PostNcDestroy
// 建立
CMyDlg* pDlg = new CMyDlg;
pDlg->Create(IDD_MYDLG, this);
pDlg->ShowWindow(SW_SHOW);
// 關閉按鈕處理 (必須重寫,不可用基類預設)
void CMyDlg::OnOK() { DestroyWindow(); } // 不呼叫 CDialog::OnOK()!
void CMyDlg::OnCancel() { DestroyWindow(); } // 不呼叫 CDialog::OnCancel()!
void CMyDlg::OnClose() { DestroyWindow(); } // 同樣
// 銷毀鏈
DestroyWindow() → OnDestroy() → PostNcDestroy()
| 特性 | 模態 | 非模態 (堆疊建立) |
|---|---|---|
| 建立方式 | DoModal() (棧上/內部管理) | new + Create() (堆上/手動管理) |
| 關閉觸發 | EndDialog | DestroyWindow |
| OnOK/OnCancel 預設 | 呼叫 EndDialog | **不關閉對話方塊** (需重寫呼叫 DestroyWindow) |
| 物件銷毀責任 | 框架自動 | **程式設計師負責 delete** |
| 自動清理模式 | 不需要 | PostNcDestroy 中 delete this |
class CMyModelessDlg : public CDialog {
// ...
virtual void PostNcDestroy() {
CDialog::PostNcDestroy(); // 基類清理
delete this; // 自我刪除 (僅限 new 建立的物件!)
}
};
// 使用端
void CMainDlg::OnShowModeless() {
CMyModelessDlg* pDlg = new CMyModelessDlg;
pDlg->Create(IDD_MODELESS, this);
pDlg->ShowWindow(SW_SHOW);
// 無需手動 delete,關閉時自動清理
}
Warning: calling DestroyWindow in CWnd::~CWnd OnDestroy or PostNcDestroy in derived class will not be called
原因:在 CWnd 解構函數中呼叫 DestroyWindow,導致虛函數機制失效,OnDestroy/PostNcDestroy 不被呼叫。
解決:非模態對話方塊**絕不**在解構函數中呼叫 DestroyWindow,改用 PostNcDestroy 自刪模式。
OnOK/OnCancel → 呼叫基類 → 清理資源放 OnDestroyOnOK/OnCancel/OnClose → 呼叫 DestroyWindow → 實作 PostNcDestroy { CDialog::PostNcDestroy(); delete this; }NULL 後不可再存取std::unique_ptr 配合自訂刪除器管理非模態對話方塊生命週期 (現代 C++)