技術演進中的開發沉思-30 MFC系列:五大機制

MFC,記得我剛畢業時在 CRT 顯示器前敲下第一行 MFC 代碼時,那時什么都不懂,沒有框架的概念。只覺得眼前的 CObject 像位沉默且復雜的大家族, 就像老北京胡同里的大家族,每個門牌號都藏著自己的故事。但現在看看,MFC 那些看似復雜的機制,其實都是為了讓程序員能快速梳能夠了解它熟悉它。MFC在我眼里最重要的就是:RTTI(運行時類型識別)、Dynamic Creation(動態創建)、Persistence(永久保存機制)、Message Mapping(消息映射)、Command Routing(命令傳遞)。這幾個部分構件成了MFC最為重要的內容,讓它能夠成為一把開發的利刃。

一、類層次:程序世界的家族圖譜

MFC 的類層次就像一棵枝繁葉茂的老槐樹。最頂端的 CObject 是所有類的老祖宗,它定下了家族的基本規矩:每個子孫都得會自我介紹(RTTI)、能自己生孩子(動態創建)、還得懂得把重要物件收進箱子(永久保存)。就像胡同里的長輩總會教晚輩 "出門要報家門,回家要鎖好門"。

往下看,CDocument 和 CView 像一對默契的夫妻:文檔負責管家里的 "存折"(數據),視圖負責把 "家底" 展示給外人看。而 CWnd 家族更像個熱鬧的大家庭,按鈕、編輯框、窗口都是它的孩子,每個孩子都繼承了 "與人打交道" 的本事(消息處理),又各有各的脾氣 —— 就像胡同里的張大爺愛下棋,李大媽愛聊天。


// 簡化的類層次關系示意class CObject {}; // 老祖宗class CCmdTarget : public CObject {}; // 能處理命令的長輩class CWnd : public CCmdTarget {}; // 窗口家族家長class CFrameWnd : public CWnd {}; // 框架窗口class CEdit : public CWnd {}; // 編輯框晚輩

二、初始化

MFC 程序的啟動過程,像極了劇院里一場演出的籌備。WinMain 函數就像幕后導演,先把舞臺搭好(注冊窗口類),再請出主角(CWinApp 對象)。當你雙擊 exe 文件時,就像拉開了大幕:

程序先鞠躬問好( AfxWinInit 初始化),然后主角登場(theApp 全局對象構造),接著導演喊 "開始"(Run 函數),主窗口這個 "舞臺" 才緩緩升起。整個過程環環相扣,就像包餃子時先和面、再搟皮、最后包餡,少一步都不成。


// 程序啟動的核心流程CMyApp theApp; // 全局應用對象,先于WinMain構造int WINAPI WinMain(...) {AfxWinInit(...); // 初始化MFC運行環境return theApp.Run(); // 進入消息循環}

三、五大機制

1. RTTI:對象的身份證

在沒有身份證的年代,人們靠熟人辨認身份。MFC 的 RTTI 就像給每個對象發了張帶芯片的身份證,用 IsKindOf 函數一刷,就知道它是不是某個家族的成員。我曾在調試時靠它揪出一個偽裝成按鈕的靜態文本框,就像居委會大媽一眼識破混進小區的陌生人。


// 運行時類型判斷if (pWnd->IsKindOf(RUNTIME_CLASS(CEdit))) {// 確認是編輯框對象((CEdit*)pWnd)->SetWindowText("我是編輯框");}

2. 動態創建

動態創建機制讓程序能根據類名 "打印" 出對象,就像點餐時說 "來份宮保雞丁",廚房就會按配方做出相應的菜。MFC 靠 DECLARE_DYNCREATE 和 IMPLEMENT_DYNCREATE 這對 "符咒",讓每個可創建的類都藏著自己的 "菜譜"。當年做插件系統時,這招幫我們實現了 "按需加載",就像旅行時只帶必要的行李。

3. 永久保存

Persistence 機制讓對象能把自己的狀態寫進文件,就像把夏天的西瓜放進冰箱,冬天還能嘗到清涼。Serialize 函數就是那個負責打包的保鮮膜,把數據一層層裹好。我至今記得第一次用它恢復誤刪的繪圖數據時,感覺像在廢墟里挖出了藏寶盒。


// 序列化示例void CMyData::Serialize(CArchive& ar) {if (ar.IsStoring()) {// 保存數據,像把東西裝進箱子ar << m_nValue << m_strText;} else {// 讀取數據,從箱子里取東西ar >> m_nValue >> m_strText;}}

4. 消息映射

如果說 Windows 系統是座巨型寫字樓,那每個窗口都是一間辦公室,而用戶的每一次操作 —— 點擊鼠標、敲擊鍵盤、拖動窗口 —— 都是一封亟待投遞的信件。消息映射機制,就是 MFC 為這座寫字樓打造的智能郵政系統,比普通郵局多了幾分 "未卜先知" 的智慧。?

記得 2003 年做工業監控軟件時,車間的操作臺有 16 個按鈕,每個按鈕按下都要觸發不同的設備動作。最初我像個新手郵差,在代碼里寫滿 if-else 逐個判斷消息來源,就像捧著一堆信件挨家挨戶敲門。直到用上消息映射,才明白什么叫 "精準投遞"—— 每個按鈕的點擊消息都像貼了電子標簽,會自動飛向對應的處理函數,效率比手工分揀提升了何止十倍。?

這個系統的核心是三張 "郵政清單":消息映射表(message map)、消息哈希表(hash table)和消息處理函數指針數組。當鼠標在窗口上點擊時,Windows 內核會生成一封特殊的 "信"(MSG 結構體),信封上寫著接收窗口的 HWND(就像辦公室門牌號)、消息類型(WM_LBUTTONDOWN 相當于 "緊急快遞")和附加信息(坐標值如同包裹里的物品清單)。?

MFC 收到這封信后,先查消息映射表。這個表是用 BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 宏自動生成的,看起來像本厚厚的通訊錄:?

?


BEGIN_MESSAGE_MAP(CMyWnd, CWnd)?ON_WM_LBUTTONDOWN() // 鼠標左鍵按下?ON_WM_KEYDOWN() // 鍵盤按鍵?ON_BN_CLICKED(IDC_OK, &CMyWnd::OnOK) // OK按鈕點擊?END_MESSAGE_MAP()??

這些宏會在編譯時變成類似這樣的結構:?

static const AFX_MSGMAP_ENTRY _messageEntries[] = {?{ WM_LBUTTONDOWN, 0, 0, 0, AfxSig_vwp, (AFX_PMSG)&CMyWnd::OnLButtonDown },?{ WM_KEYDOWN, 0, 0, 0, AfxSig_vw, (AFX_PMSG)&CMyWnd::OnKeyDown },?{ WM_COMMAND, IDC_OK, IDC_OK, 0, AfxSig_v, (AFX_PMSG)&CMyWnd::OnOK },?{0, 0, 0, 0, AfxSig_end, NULL } // 表結束標記?};?

?就像郵政系統的分揀機,MFC 會用消息 ID 做哈希運算,快速定位到對應的處理函數。如果當前窗口處理不了這封信(比如子窗口收到本應由父窗口處理的命令),消息會沿著類層次向上傳遞,就像前臺收信員處理不了的文件會遞給部門經理,這就是所謂的 "消息冒泡" 機制。?

最妙的是 ON_COMMAND 這類宏,能把菜單、工具欄按鈕和快捷鍵的命令消息統一處理。當年做文本編輯器時,我給 "復制" 功能同時綁定了菜單選項、工具欄按鈕和 Ctrl+C 快捷鍵,消息映射像個貼心的秘書,自動把這三種操作都引向同一個 Copy 函數,省去了大量重復代碼。?

但這個系統也有 "脾氣"。有次調試打印功能,點擊菜單后毫無反應,查了三天才發現是把 ON_COMMAND (ID_PRINT, &OnPrint) 寫成了 ON_WM_COMMAND (ID_PRINT, &OnPrint)—— 就像把 "航空郵件" 的標簽貼成了 "平郵",信件自然被送進了錯誤的分揀通道。那時沒有現在的調試工具,只能靠在消息循環里加斷點,看著消息一個個流過,像在監控錄像里找丟失的包裹。?

如今想來,消息映射最偉大的地方,是把 Windows 復雜的消息機制包裝成了程序員能理解的 "人類語言"。它就像架在機器指令和人類思維之間的翻譯機,讓我們不用背誦枯燥的消息常量,也能和操作系統順暢對話。這種 "隱藏復雜性" 的智慧,正是所有優秀框架的共同特質。

5. 命令傳遞

命令傳遞機制,就像一家運轉有序的公司里的審批流程,每個環節都有明確的分工和流轉規則,確保每一個指令都能找到最合適的處理者。?

舉個例子:如果開發了一個圖書管理系統,其中有個 "借閱統計" 的功能按鈕。按常理,這個按鈕在工具欄上,點擊后該由誰來處理呢?當時我犯了難,是讓工具欄自己處理,還是交給顯示圖書列表的視圖,或是負責數據管理的文檔?后來才明白,命令傳遞機制早就為我們設計好了清晰的路徑。?

就像員工(工具欄按鈕)提交了一份審批單(命令消息),首先會交給直屬部門經理(視圖)。視圖會看自己是否有權限和能力處理,如果它處理不了,就會把審批單交給分管副總(框架窗口)。框架窗口要是也處理不了,就會上報給總經理(文檔),最后還可能提交給公司最高層(應用程序對象)。?

在 MFC 中,這個流程是通過一系列函數協作完成的。當命令消息產生后,首先會調用視圖的 OnCmdMsg 函數,視圖會檢查自己的消息映射表,如果有對應的處理函數,就像部門經理能直接審批,事情就解決了。如果沒有,它會調用 GetParent 函數找到框架窗口,把命令傳遞過去,就像部門經理簽上 "轉上級處理" 后遞交給副總。?

框架窗口收到后,同樣會先查看自己的消息映射表,要是處理不了,會通過 GetActiveDocument 找到文檔對象,繼續傳遞命令,這就像副總再轉交給總經理。文檔如果也無法處理,最終會傳到應用程序對象那里。?

?


// 命令傳遞的大致流程示意?BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) {?// 視圖先嘗試處理命令?if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) {?return TRUE;?}?// 處理不了則傳遞給框架窗口?CFrameWnd* pFrame = GetParentFrame();?if (pFrame != NULL && pFrame->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) {?return TRUE;?}?// 再傳遞給文檔?CDocument* pDoc = GetDocument();?if (pDoc != NULL && pDoc->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) {?return TRUE;?}?return FALSE;?}?

?就之前提到的?"借閱統計" 功能,視圖負責顯示統計結果,文檔負責從數據庫讀取借閱數據。當點擊按鈕時,命令先到視圖,視圖知道自己沒有數據處理能力,就把命令傳給了文檔。文檔處理完數據后,再通知視圖更新顯示,整個過程行云流水,就像一場配合默契的接力賽。?

但這個流程也有需要注意的地方。如果在框架窗口和視圖中都定義了同一個命令的處理函數,你胡發現,結果發現總是視圖先處理。這是因為命令傳遞是有優先級的,就像審批流程中,低級別的管理者如果能處理,就不會麻煩上級。這就要求我們在設計時,要明確每個命令最適合的處理者,避免出現混亂。?

命令傳遞機制的巧妙之處在于,它讓程序的各個模塊既能各司其職,又能高效協作。就像一家公司,每個部門有自己的職責,但當遇到跨部門的問題時,有明確的流程讓問題得到妥善處理。這種機制不僅讓代碼結構更清晰,也大大提高了開發效率,讓程序員能更專注于業務邏輯的實現,而不用過多操心命令的傳遞路徑。

最后小結:

如今的程序員可能都不知道 MFC,就像我的孩子看不懂 BB 機一樣。但那些隱藏在代碼背后的設計思想 —— 如何讓復雜系統變得有序,如何讓機器理解人類的意圖 —— 永遠不會過時。MFC 就像一座橋,一頭連著底層的 Windows API,一頭連著程序員的創意。而我們這些老程序員,不過是在橋上往返穿梭的趕路人,把經驗刻在欄桿上,供后來者參考。

技術會迭代,但對簡潔與美的追求,對問題本質的探索,永遠是程序員的初心。未完待續.....

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/88437.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/88437.shtml
英文地址,請注明出處:http://en.pswp.cn/web/88437.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

機器學習-06(Optimization-自動調整學習率)

臨界點其實不一定是在訓練神經網絡過程中遇到的最大阻礙。隨著對參數的不斷更新&#xff0c;Loss值會不斷下降&#xff0c;直至幾乎沒有變化&#xff0c;不再下降。當參數更新到臨界點時&#xff0c;意味著gradient非常小&#xff0c;所以要認定參數是否到達臨界點應當確認grad…

Uniapp中的uni.scss

uni.scss為uni-app新建項目自帶工程文件&#xff0c;使用的預處理器為sass/scss&#xff0c;由此可見&#xff0c;uni-app官方推薦的是scss。 uni.scss特點 無需引入&#xff0c;uni-app在編譯時&#xff0c;會自動引入此文件在此中定義的scss變量&#xff0c;可以全局使用&…

PreparedStatement 實現分頁查詢詳解

PreparedStatement 實現分頁查詢詳解 在 JDBC 中使用 PreparedStatement 實現分頁查詢是高效安全的方式&#xff0c;可以避免 SQL 注入并提升性能。下面我將詳細說明實現步驟和原理。 &#x1f4d0; 分頁查詢核心參數參數名說明計算公式pageNum當前頁碼&#xff08;從1開始&…

ClamAV 和 FreshClam:Linux 服務器上的開源殺毒解決方案

ClamAV 和 FreshClam:Linux 服務器上的開源殺毒解決方案 1. 概述 ClamAV 是一款開源的防病毒引擎,專為 Linux 服務器設計,用于檢測惡意軟件、病毒、木馬和其他安全威脅。它廣泛應用于郵件服務器、文件存儲系統和 Web 服務器,提供高效的病毒掃描功能。 主要特點: 免費開…

PySpark中python環境打包和JAR包依賴

在 PySpark 中打包 Python 環境并調度到集群是處理依賴一致性的關鍵步驟。以下是完整的解決方案&#xff0c;包含環境打包、分發和配置方法&#xff1a; 一、環境打包方法 使用 Conda 打包環境 # 創建 Conda 環境 conda create -n pyspark_env python3.8 conda activate pyspar…

和鯨社區深度學習基礎訓練營2025年關卡2(1)純numpy

擬分3種實現方法&#xff1a;1.純numpy2.sklearn中的MLPClassifier3.pytorch題目&#xff1a; 在 MNIST 數據集上訓練 MLP 模型并比較不同的激活函數和優化算法任務描述&#xff1a;使用 MNIST 數據集中的前 20,000 個樣本訓練一個多層感知機 (MLP) 模型。你需要比較三種不同的…

Sequential Thinking:AI深度思考的新范式及其與CoT、ReAct的對比分析

引言&#xff1a;AI深度思考的演進與Sequential Thinking的崛起在人工智能技術快速發展的今天&#xff0c;AI模型的思考能力正經歷著從簡單應答到深度推理的革命性轉變。這一演進過程不僅反映了技術本身的進步&#xff0c;更體現了人類對機器智能認知邊界的持續探索。早期的大語…

云原生詳解:構建現代化應用的未來

引言 在數字化轉型的浪潮中,"云原生"已成為技術領域最熱門的話題之一。從初創公司到全球500強企業,都在積極探索云原生技術以提升業務敏捷性和創新能力。本文將全面解析云原生的概念、核心技術、優勢以及實踐路徑,幫助您深入理解這一改變IT格局的技術范式。 什么…

SSE事件流簡單示例

文章目錄1、推送-SseEmitter2、接收-EventSourceListenerSSE&#xff08;Server-Sent Events&#xff0c;服務器推送事件&#xff09;是一種基于HTTP的服務器向客戶端實時推送數據的技術標準。1、推送-SseEmitter SseEmitter用于實現服務器向客戶端單向、長連接的實時數據推送…

Elasticsearch RESTful API入門:基礎搜索與查詢DSL

Elasticsearch RESTful API入門&#xff1a;基礎搜索與查詢DSL 本文為Elasticsearch初學者詳細解析RESTful API的核心操作與查詢DSL語法&#xff0c;包含大量實戰示例及最佳實踐。 一、Elasticsearch與RESTful API簡介 Elasticsearch&#xff08;ES&#xff09;作為分布式搜索…

(六)復習(OutBox Message)

文章目錄 項目地址一、OutBox Message1.1 OutBox表配置1. OutBoxMessage類2. OutboxMessage表配置3. 給每個模塊生成outboxmessage表1.2 發布OutBox Message1. 修改Intercepotor2. 配置Quartz3. 創建Quatz方法發布領域事件4. 創建Quatz定時任務5. 注冊Quatz服務和配置6. 流程梳…

STM32-ADC內部溫度

在通道16無引腳&#xff08;測量溫度不準確&#xff09;跟ADC代碼差不多&#xff1b;不需要使能引腳時鐘&#xff1b;將內部溫度測量打開/*** brief 啟用或禁用溫度傳感器和內部參考電壓功能* param NewState: 新的功能狀態&#xff0c;取值為ENABLE或DISABLE* retval 無* no…

「Linux命令基礎」文本模式系統關閉與重啟

關機重啟基本命令 直接拔掉計算機電源可能損壞內部元件;Linux系統通過命令關閉計算機則是安全流程,讓所有程序有機會保存數據、釋放資源。 關機命令:shutdown Linux系統提供了多種用于關閉或重啟系統的命令,其中 shutdown 是最常用的一種,它可以安全地通知用戶系統即將…

射頻信號(大寬高比)時頻圖目標檢測anchors配置

一、大寬高比目標YOLO檢測參數設置 這是yolov7的一個label的txt文件&#xff1a; 1 0.500 0.201 1.000 0.091 2 0.500 0.402 1.000 0.150 3 0.500 0.604 1.000 0.093 0 0.500 0.804 1.000 0.217 對應的樣本&#xff1a; 長寬比分別是&#xff1a;1/0.09110.98, 1/0.1506.67…

OpenStack 鑒權服務介紹.md

引言 OpenStack是一個開源的云計算管理平臺&#xff0c;其中的Keystone組件承擔了身份認證和授權的關鍵任務。Keystone的主要功能包括管理用戶及其權限、維護OpenStack Services的Endpoint&#xff0c;以及實現認證&#xff08;Authentication&#xff09;和鑒權&#xff08;Au…

Linux_3:進程間通信

IPC1.什么是IPC&#xff1f;Inter Process Communication2.進程間通信常用的幾種方式1&#xff0c;管道通信&#xff1a;有名管道&#xff0c;無名管道2&#xff0c;信號- 系統開銷小3&#xff0c;消息隊列-內核的鏈表4&#xff0c;信號量-計數器5&#xff0c;共享內存6&#x…

【Springboot】Bean解釋

在 Spring Boot 中&#xff0c;Bean 就像是你餐廳里的一名員工。比如&#xff0c;你有一名服務員&#xff08;Service&#xff09;、一名廚師&#xff08;Chef&#xff09;和一名收銀員&#xff08;Cashier&#xff09;。這些員工都是餐廳正常運轉所必需的&#xff0c;他們各自…

axios的post請求,數據為什么要用qs處理?什么時候不用?

為什么使用 qs 處理 POST 數據axios 的 POST 請求默認將 JavaScript 對象序列化為 JSON 格式&#xff08;Content-Type: application/json&#xff09;。但某些后端接口&#xff08;尤其是傳統表單提交&#xff09;要求數據以 application/x-www-form-urlencoded 格式傳輸&…

【unitrix】 4.21 類型級二進制數基本結構體(types.rs)

一、源碼 這段代碼定義了一個類型級數值系統的 Rust 實現&#xff0c;主要用于在編譯時表示和操作各種數值類型。 use crate::sealed::Sealed; use crate::number::{NonZero, TypedInt, Unsigned, Primitive}; // // 特殊浮點值枚舉 ///// 特殊浮點值&#xff08;NaN/∞&#x…

UI前端與數字孿生結合實踐案例:智慧零售的庫存管理優化系統

hello寶子們...我們是艾斯視覺擅長ui設計和前端數字孿生、大數據、三維建模、三維動畫10年經驗!希望我的分享能幫助到您!如需幫助可以評論關注私信我們一起探討!致敬感謝感恩!一、引言&#xff1a;數字孿生重構零售庫存的 “人 - 貨 - 場” 協同在零售行業利潤率持續承壓的背景…