Visual Basic、C++ Builder、Delphi、Visual C++ —— 每種工具皆有其設計哲學與適用場景。VC++ 搭配 MFC (Microsoft Foundation Classes) 雖學習曲線陡峭,卻能讓開發者觸及 Windows 核心機制,掌握最底層的控制權。本文從經典 SDK 「Hello World」出發,層層剖析訊息驅動架構,再對應至 MFC 封裝層,幫助讀者建立清晰的心智模型。
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
// 1. 註冊視窗類別
MyRegisterClass(hInstance);
// 2. 建立並顯示視窗
if (!InitInstance(hInstance, nCmdShow)) return FALSE;
// 3. 訊息迴圈 —— 程式靈魂所在
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg); // 虛擬鍵 → 字元訊息
DispatchMessage(&msg); // 分發至視窗程序
}
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT: // 系統要求重繪
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rt; GetClientRect(hWnd, &rt);
DrawText(hdc, "Hello World!", -1, &rt, DT_CENTER);
EndPaint(hWnd, &ps);
break;
}
case WM_DESTROY: // 視窗銷毀
PostQuitMessage(0); // 發送 WM_QUIT 結束迴圈
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam); // 預設處理
}
return 0;
}
MSG 結構,放入佇列,迴圈取出分發。message 分派處理邏輯。BeginPaint/EndPaint 之間繪圖。| SDK 概念 | MFC 類別/機制 | 說明 |
|---|---|---|
WinMain | CWinApp::InitInstance | 應用程式物件初始化,建立主視窗 |
WNDCLASS 註冊 | AfxRegisterWndClass / CWnd::Create | 框架自動處理類別註冊 |
HWND | CWnd::m_hWnd | 封裝視窗控制代碼 |
WndProc | CWnd::WindowProc → 訊息對映 | 虛擬函數 + 巨集對映表替代巨大 switch |
GetMessage/DispatchMessage | CWinApp::Run | 內建訊息迴圈,支援 Idle 處理 |
DefWindowProc | CWnd::Default | 預設訊息處理 |
CObject (根基類,支援執行期型別資訊、序列化)
└─ CCmdTarget (命令目標,支援訊息/命令路由)
└─ CWnd (視窗基類,封裝 HWND 與訊息處理)
├─ CFrameWnd (框架視窗,SDI/MDI 主視窗)
├─ CDialog (對話方塊)
├─ CView (檢視,Document/View 架構核心)
└─ 控制項類別 (CButton, CEdit, CListCtrl...)
MFC 標志性的 Document/View 架構將資料 (Document) 與呈現 (View) 解耦:
CDocument:管理資料、序列化 (儲存/開啟檔案)、通知檢視更新。CView:繪製資料、處理使用者輸入、轉發命令給 Document。CDocTemplate:管理 Document、View、Frame 三者關聯與建立。單一文件介面 (SDI) 一次開啟一份文件;多文件介面 (MDI) 允許子視窗並存。
// 標頭檔
class CMyWnd : public CWnd
{
DECLARE_MESSAGE_MAP()
afx_msg void OnPaint();
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
};
// 實作檔
BEGIN_MESSAGE_MAP(CMyWnd, CWnd)
ON_WM_PAINT()
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
void CMyWnd::OnPaint()
{
CPaintDC dc(this); // 自動 BeginPaint/EndPaint
CRect rect; GetClientRect(&rect);
dc.DrawText(_T("Hello MFC!"), &rect, DT_CENTER);
}
優勢: 類型安全、支援繼承覆寫、可視化編輯器 (Class Wizard) 維護、效能優化 (快取查找)。
MFC 建立完整命令傳遞鏈:
主框架視窗 → 檢視 → 文件 → 應用程式物件 → 模板 → ...
ON_COMMAND、ON_UPDATE_COMMAND_UI 分別處理命令執行與 UI 狀態更新 (啟用/禁用、勾選)。
| 陷阱 | 解決方案 |
|---|---|
| 在建構子中呼叫虛擬函數 | 改寫 PreCreateWindow、OnInitialUpdate 等虛擬函數 |
| 忘記呼叫基類實作 | 覆寫 OnCmdMsg、WindowProc 等時務必呼叫 BaseClass::Func() |
| 資源洩漏 (GDI 物件、記憶體) | 善用 CPaintDC、CClientDC、CBitmap 等 RAII 封裝類 |
| 跨執行緒操作 UI | 使用 PostMessage、SendMessage 或 Control.Invoke |
| Unicode/ANSI 混用導致亂碼 | 專案設定統一 Unicode,字串字面值加 _T() 或 L"" |
雖 MFC 進入維護模式,微軟仍持續更新 (Visual Studio 2022 含 MFC 14.3x);新專案建議考慮:
理解 SDK 訊息迴圈與視窗程序,是掌握 MFC 乃至所有 Windows GUI 框架的鑰匙。MFC 以 C++ 物件導向封裝了繁瑣的 API 呼叫,提供訊息對映、命令路由、文件/檢視等企業級架構。雖然新技術層出不窮,但 MFC 所體現的「薄封裝、高控制、貼近金屬」設計哲學,仍是系統級開發者寶貴的財富。