Visual C++ 6.0 發布於 1998 年,MFC (Microsoft Foundation Classes) 與 ATL (Active Template Library) 這雙子星框架支撐了 Windows 桌面生態二十餘年。雖然 WinUI 3 / WinAppSDK / .NET MAUI 成為新寵,但海量存量系統、工業控制軟件、科學儀器界面、遊戲編輯器、金融終端仍運行在 MFC/ATL/WTL 之上。本文系統梳理 VC++ Windows 應用的核心架構、消息機制、資源管理、構建體系,並提供現代化遷移路徑與最佳實踐。
// 標準 Win32 入口 (Unicode 版)
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {
// 1. 單例檢查 (互斥體/原子)
// 2. 公共控件初始化 (InitCommonControlsEx)
// 3. OLE/COM 初始化 (AfxOleInit / CoInitializeEx)
// 4. MFC/ATL 模塊初始化
// 5. 主窗口創建與顯示
// 6. 消息迴圈 (GetMessage/DispatchMessage 或 CWinApp::Run)
// 7. 清理與退出
return 0;
}
// MFC 封裝後的入口 (隱藏在 MFC 源碼中)
class CWinApp {
public:
virtual BOOL InitInstance(); // 核心初始化:創建主窗口、加載配置、註冊類別
virtual int Run(); // 消息迴圈 + 空閒處理
virtual int ExitInstance(); // 清理
CWnd* m_pMainWnd = nullptr; // 主窗口指針
// ...
};
關鍵差異:控制台程序 `main()` 由 CRT 啟動代碼調用;GUI 程序 `wWinMain()` 由 Windows 加載器直接調用,**無控制台窗口**,標準輸出需重定向或使用 `OutputDebugString`/`Trace`。
| 類別 | 典型消息 | 處理要點 |
|---|---|---|
| 窗口生命週期 | `WM_CREATE`、`WM_DESTROY`、`WM_NCCREATE`、`WM_NCDESTROY`、`WM_CLOSE`、`WM_QUERYENDSESSION` | `WM_CREATE` 分配資源、`WM_DESTROY` 釋放資源並 `PostQuitMessage`;`WM_CLOSE` 可攔截關閉確認 |
| 繪圖與視覺 | `WM_PAINT`、`WM_ERASEBKGND`、`WM_PRINTCLIENT`、`WM_NCPAINT`、`WM_CTLCOLOR*` | `BeginPaint/EndPaint` 配對;雙緩衝 (`CMemDC`/`CDC::BeginPath`) 防閃爍;高 DPI 感知 (`WM_DPICHANGED`) |
| 輸入處理 | `WM_KEYDOWN/UP`、`WM_CHAR`、`WM_MOUSEMOVE`、`WM_LBUTTONDOWN/UP/DBLCLK`、`WM_MOUSEWHEEL`、`WM_TOUCH`/`WM_POINTER` | 鍵盤加速鍵 (`LoadAccelerators` + `TranslateAccelerator`);滑鼠捕獲 (`SetCapture/ReleaseCapture`);觸控/筆輸入區分 |
| 系統/通知 | `WM_TIMER`、`WM_POWERBROADCAST`、`WM_DEVICECHANGE`、`WM_SETTINGCHANGE`、`WM_COPYDATA`、`WM_DROPFILES` | 定時器精度 (`SetTimer` vs `CreateTimerQueueTimer`);設備到達/移除處理;進程間數據傳遞 (`COPYDATASTRUCT`);拖放文件 (`DragAcceptFiles` + `DragQueryFile`) |
| 命令路由 (MFC) | `WM_COMMAND`、`WM_NOTIFY`、`CN_UPDATE_COMMAND_UI` | 命令目標鏈:視圖 → 幀 → 文檔 → 模板 → 應用程序;`ON_UPDATE_COMMAND_UI` 動態啟用/禁用/勾選菜單項 |
// 傳統巨集 (編譯期靜態表)
BEGIN_MESSAGE_MAP(CMyView, CView)
ON_WM_PAINT()
ON_WM_LBUTTONDOWN()
ON_COMMAND(ID_FILE_PRINT, &CMyView::OnFilePrint)
ON_UPDATE_COMMAND_UI(ID_FILE_PRINT, &CMyView::OnUpdateFilePrint)
ON_NOTIFY_REFLECT(LVN_ITEMCHANGED, &CMyView::OnItemChanged)
ON_REGISTERED_MESSAGE(WM_MY_CUSTOM_MSG, &CMyView::OnCustomMsg)
END_MESSAGE_MAP()
// 手寫消息映射 (C++17 折疊表達式 + 概念, 僅示意)
template <typename Derived>
struct MessageMap {
static LRESULT Dispatch(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
if constexpr (requires { Derived::OnPaint(); }) {
if (msg == WM_PAINT) return Derived::OnPaint(hWnd), 0;
}
// ... 其他消息
return DefWindowProc(hWnd, msg, wp, lp);
}
};
| 層級 | API/類 | 特點 | 現代化建議 |
|---|---|---|---|
| GDI (C API) | `HDC`、`HPEN`/`HBRUSH`/`HFONT`、`BitBlt`/`StretchBlt`/`AlphaBlend`、`GDI` 對象選入/釋放 | 兼容性最好、性能基線、無抗鋸齒/透明度原生支持 | 僅用於簡單繪圖、兼容舊驅動、打印 |
| GDI+ (C++ 類) | `Graphics`、`Pen`/`Brush`/`Font`/`Image`、`Matrix` 變換、抗鋸齒、Alpha 混合 | 功能豐富、托管封裝 (`System.Drawing`)、但 **單線程、性能一般、Win7+ 內建** | 漸進替換為 Direct2D/DirectWrite |
| Direct2D/DirectWrite (現代) | `ID2D1Factory`/`ID2D1HwndRenderTarget`/`ID2D1Brush`、`IDWriteFactory`/`IDWriteTextFormat` | **硬件加速、保留模式/即時模式、高性能、高質量文本、DirectX 互操作** | **新專案首選**,MFC `CD2DRenderTarget`/`CD2DBrush` 封裝可用 |
| DirectComposition | `DCompositionCreateDevice`、`IDCompositionVisual`、`IDCompositionTarget` | 視覺樹合成、獨立渲染線程、流暢動畫、Win8+ | 複雜 UI 動畫、混合 Web/原生內容 |
PerMonitorV2
| DLL 類型 | 用途 | 導出方式 | 運行時加載 |
|---|---|---|---|
| MFC 擴展 DLL | 共享 MFC 類、對象、資源 (需同版本 MFC 共享運行時) | `AFX_EXT_CLASS` / `AFX_EXT_API` (宏自動處理 `__declspec(dllexport/import)`) | 隱式鏈接 (`.lib`) 或顯式 `LoadLibrary`/`GetProcAddress` |
| 常規 MFC DLL | 獨立 MFC 狀態、可被非 MFC EXE 調用 (複製 MFC 狀態) | 同上,但需 `AFX_MANAGE_STATE(AfxGetStaticModuleState())` 切換模塊狀態 | 同上 |
| 純 C/C++ DLL (無 MFC) | 跨語言/跨編譯器 ABI 穩定、插件系統、腳本引擎綁定 | `extern "C" __declspec(dllexport)` + `.def` 文件 (序數/名稱導出) | 顯式加載、插件工廠模式 |
| ATL COM DLL | ActiveX 控件、Shell 擴展、Office 加載項、系統組件 | 模塊類 `CAtlDllModuleT` + `OBJECT_ENTRY_AUTO` + 註冊腳本 | COM 激活 (`CoCreateInstance`)、註冊表/清單激活 |
DLL 地雷區:
- **跨 DLL 邊界傳遞 STL/標準庫對象** → ABI 不兼容、堆不共享 → **嚴禁**。改用 POD、介面、不透明指針、序列化。
- **運行時庫不一致** (`/MT` vs `/MD` vs `/MTd` vs `/MDd`) → 堆損壞、雙重釋放 → **全專案統一 `/MD` (Release) / `/MDd` (Debug)**。
- **MFC 版本不匹配** (VC6 vs VS2019 MFC) → 類佈局不同、虛函數表偏移 → **同方案統一工具集、遷移時全量重編**。
| 框架 | 定位 | 優勢 | 劣勢 | 適用場景 |
|---|---|---|---|---|
| MFC | 文檔/視圖架構、豐富控件封裝、向導生成、企業級桌面應用 | 功能全、向導強、文檔多、遷移舊代碼易 | 體積大、耦合深、現代 C++ 風格弱、單線程消息迴圈 | 存量系統維護、複雜 MDI/SDI 文檔應用、向導驅動開發 |
| ATL | COM 組件、ActiveX 控件、Shell 擴展、輕量級窗口、無運行時依賴 | 極小、極快、COM 支持最好、模板元編程風格、無 MFC 依賴 | 無文檔/視圖、控件封裝少、學習曲線陡 (模板/COM) | ActiveX/COM 組件、高性能窗口、IE BHO、輕量級工具、插件 |
| WTL (Windows Template Library) | ATL 之上輕量封裝、接近 MFC API、無運行時、開源 (SourceForge/GitHub) | API 熟悉 (CWindow/CDialogImpl)、體積小、靈活、現代 C++ 風格較好 | 非官方支持、文檔少、控件庫不如 MFC 全 | 新開發輕量級 GUI、替代 MFC 減小體積、ATL 開發者偏好 |
# CMakeLists.txt 現代化範例 (MFC 專案)
cmake_minimum_required(VERSION 3.20)
project(MyMfcApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") # /MD 或 /MDd
# 依賴管理 (vcpkg/Conan)
find_package(fmt CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
# MFC/ATL 為系統組件,通常不經包管理器
# 預編譯標頭 (PCH) - 關鍵加速
set(PCH_HEADER "stdafx.h")
set(PCH_SOURCE "stdafx.cpp")
target_precompile_headers(MyMfcApp PRIVATE ${PCH_HEADER})
# VS 生成器自動處理 /Yc /Yu
add_executable(MyMfcApp WIN32
src/Main.cpp
src/MyApp.cpp
src/MyFrame.cpp
src/MyView.cpp
res/Resource.rc # 資源文件直接參與編譯
)
target_link_libraries(MyMfcApp PRIVATE
fmt::fmt
spdlog::spdlog
# 系統庫自動鏈接 (kernel32, user32, gdi32, comdlg32, ole32, oleaut32, uuid)
)
# 版本資源自動生成
configure_file(res/Version.rc.in res/Version.rc @ONLY)
target_sources(MyMfcApp PRIVATE res/Version.rc)
# 安裝/打包 (CPack/NSIS/WiX)
include(CPack)
A: 1) 啟用預編譯標頭 (PCH) 2) 延遲加載非關鍵 DLL (`LoadLibrary` 在首次使用時) 3) 減少靜態初始化 (全局對象構造函數) 4) 使用 `/OPT:ICF` `/OPT:REF` 優化鏈接 5) 分析 `WM_CREATE`/`InitInstance` 耗時點。
A: vcpkg/Conan 安裝 → `find_package` → `target_link_libraries`。注意:庫必須與主項目**相同運行時庫 (`/MD`/`/MDd`)** 編譯。避免跨 DLL 邊界傳遞標準庫對象,改用介面/序列化。
A: 指針截斷 (`LONG` → `LONG_PTR`/`INT_PTR`)、`SetWindowLong` → `SetWindowLongPtr`、`GetWindowLong` → `GetWindowLongPtr`、`DWORD` 指針差值 → `size_t`/`ptrdiff_t`、結構體對齊/填充變化、驅動/COM 組件 64 位版本、第三方庫 64 位構建、測試大內存分配、調試器 64 位模式。
Visual C++ 的 MFC/ATL 雙框架見證了 Windows 桌面開發的黃金時代。今天,我們不需要盲目推翻它們,但必須用現代工程標準 (CMake、C++20、靜態分析、Sanitizers、模塊化、CI/CD) 重新武裝它們。**核心邏輯庫化、UI 層薄化、渲染現代化、構建自動化**——這才是讓經典代碼庫在未來十年繼續創造價值的正確姿勢。技術棧會迭代,**工程卓越永不過時**。