MFC六大核心機制之一:MFC程式的初始化
簡單的MFC視窗程式
設計一個簡單完整MFC程式,產生一個視窗。
當然這不能讓AppWizard自動為我們生成。我們可以在Win32 Application工程下面那樣寫:
#include <afxwin.h> class MyApp : public CWinApp { public: BOOL InitInstance() //②程式入點 { CFrameWnd *Frame=new CFrameWnd();//構造框架 m_pMainWnd=Frame; //將m_pMainWnd設定為Frame; Frame->Create(NULL,"最簡單的視窗");//建立框架 Frame->ShowWindow(SW_SHOW); //顯示框架 return true; //返回 } };
MyApp theApp; //①建立應用程式。
設定連結MFC庫,執行,即可看見一個視窗。
從上面,大家可以看到建立一個MFC視窗很容易,只用兩步:一是從CWinApp派生一個應用程式類(這裡是MyApp),然後建立應用程式物件(theApp),就可以產生一個自己需要的視窗(即需要什麼樣就在InitInstance()裡建立就行了)。
整個程式,就改寫一個InitInstance()函式,建立那麼一個物件(theApp),就是一個完整的視窗程式。這就是“黑盒”操作的魔力!
在我們正想為微軟鼓掌的時候,我們突然覺得心裡空蕩蕩的,我們想知道微軟幫我們做了什麼事情,而我們想編自己的程式時又需要做什麼事情,哪怕在上面幾行的程式裡面,我們還有不清楚的地方,比如,幹嘛有一個m_pMainWnd指標變數,它從哪裡來,又要到哪裡去呢?想一想在DOS下程式設計是多麼美妙的一件事呵,我們需要什麼變數,就宣告什麼變數,需要什麼樣的函式,就編寫什麼樣的函式,或者引用函式庫……但是現在我們怎麼辦?
我們可以逆向思維一下,MFC要達到這種效果,它是怎麼做的呢?首先我們要弄明白,VC++不是一種語言,它就象我們學c語言的時候的一個類似記事本的編輯器(請原諒我的不貼切的比喻),所以,在VC裡面我們用的是C++語言程式設計,C++才是根本(初學者總是以為VC是一門什麼新的什麼語言,一門比C++先進很多的複雜語言,汗)。說了那麼多,我想用一句簡單的話概括“MFC黑箱’,就是為我們的程式加入一些固化的‘C++程式碼’的東西”。
既然MFC黑箱幫我們加入了程式碼,那麼大家想想它會幫我們加入什麼樣的程式碼呢?他會幫我們加入求解一元二次方程的程式碼嗎?當然不會,所以它加入的實際上是每次編寫視窗程式必須的,通用的程式碼。
再往下想,什麼才是通用的呢?我們每次視窗程式設計都要寫WinMain()函式,都要有註冊視窗,產生視窗,訊息迴圈,回撥函式……即然每次都要的東西,就讓它們從我們眼前消失,讓MFC幫忙寫入!
手動模擬MFC程式的初始化
要知道MFC初始化過程,大家當然可以跟蹤執行程式。但這種跟蹤很麻煩,我相信大家都會跟蹤的暈頭轉向。本人覺得哪怕你理解了MFC程式碼,也很容易讓人找不著北,我們完全不懂的時候,在成千上萬行程式的迷宮中如何能找到出口?
我們要換一種方法,不如就來重新編寫個MFC庫吧,譁!大家不要笑,小心你的大牙,我不是瘋子(雖然瘋子也說自己不瘋)。我們要寫的就是最簡單的MFC類庫,就是把MFC巨集觀上的,理論上的東西寫出來。我們要用最簡化的程式碼,簡化到剛好能執行。
1、需要“重寫”的MFC庫
既然,我們這一節寫的是MFC程式的初始化過程,上面我們還有了一個可執行的MFC程式。程式中只是用了兩個MFC類,一個是CWinApp,另一個是CFrameWnd。當然,還有很多同樣重要MFC類如檢視類,文件類等等。但在上面的程式可以不用到,所以暫時省去了它(總之是為了簡單)。
好,現在開始寫MFC類庫吧……唉,面前又有一個大難題,就是讓大家背一下MFC層次結構圖。天,那張魚網怎麼記得住,但既然我們要理解他,總得知道它是從那裡派生出來的吧。
考慮到大家都很辛苦,那我們看一下上面兩個類的父子關係(箭頭代表派生): CObject->CCmdTarget->CWinThread->CWinApp->自己的重寫了InitInstance()的應用程式類。
CObject(同上)->CCmdTarget(同上)->CWnd->CFrameWnd 看到層次關係圖之後,終於可以開始寫MFC類庫了。按照上面層次結構,我們可以寫以下六個類(為了直觀,省去了建構函式和解構函式)。
class CObiect{};//MFC類的基類。
class CCmdTarget : public CObject{};
------------------------------------------------
class CWinThread : public CCmdTarget{};
class CWinApp : public CWinThread{};
------------------------------------------------
class CWnd : public CCmdTarget{};
class CFrameWnd : public CWnd{};
/////////////////////////////////////////////////////////
大家再想一下,在上面的類裡面,應該有什麼?大家馬上會想到,CWinApp類或者它的基類CCmdTarget裡面應該有一個虛擬函式virtual BOOL InitInstance(),是的,因為那裡是程式的入口點,初始化程式的地方,那自然少不了的。可能有些朋友會說,反正InitInstance()在派生類中一定要過載,我不在CCmdTarget或CWinApp類裡定義,留待CWinApp的派生類去增加這個函式可不可以。扯到這個問題可能有點越說越遠,但我想信C++的朋友對虛擬函式應該是沒有太多的問題的。總的來說,作為程式設計師如果清楚知道基類的某個函式要被派生類用到,那定義為虛擬函式要方便很多。
也有很多朋友問,C++為什麼不自動把基類的所有函式定義為虛擬函式呢,這樣可以省了很多麻煩,這樣所有函式都遵照派生類有定義的函式就呼叫派生類的,沒定義的就呼叫基類的,不用寫virtual的麻煩多好!其實,很多面向物件的語言都這樣做了。但定義一個虛擬函式要生成一個虛擬函式表,要佔用系統空間,虛擬函式越多,表就越大,有時得不償失!這裡哆嗦幾句,是因為往後要說明的訊息對映中大家更加會體驗到這一點,好了,就此打往。
上面我們自己解決了一個問題,就是在CCmdTarge寫一個virtual BOOL InitInstance()。
2、WinMain()函式和CWinApp類
大家再往下想,我們還要我們MFC“隱藏”更多的東西:WinMain()函式,設計視窗類,視窗註冊,訊息迴圈,回撥函式……我們馬上想到封裝想封裝他們。大家似乎隱約地感覺到封裝WinMain()不容易,覺得WinMain()是一個特殊的函式,許多時候它代表了一個程式的起始和終結。所以在以前寫程式的時候,我們寫程式習慣從WinMain()的左大括寫起,到右大括弧返回、結束程式。
我們換一個角度去想,有什麼東西可以拿到WinMain()外面去做,許多初學者們,總覺得WinMain()函式是天大的函式,什麼函式都好象要在它裡面才能真正執行。其實這樣瞭解很片面,甚至錯誤。我們可以寫一個這樣的C++程式:
C++程式碼
////////////////////////////////////////////////////
#include <iostream.h>
class test{
public:
test(){
cout<<"請改變你對main()函式的看法!"<<endl;
}
};
test test1;
/**************************/
void main(){}
////////////////////////////////////////////////////
在上面的程式裡,入口的main()函式表面上什麼也不做,但程式執行了(注:實際入口函式做了一些我們可以不瞭解的事情),並輸出了一句話(注:全域性物件比main()首先執行)。現在大家可以知道我們的WinMain()函式可以什麼都不做,程式依然可以執行,但沒有這個入口函式程式會報錯。
那麼WinMain()函式會放哪個類上面呢,請看下面程式:
#include <afxwin.h>
class MyApp : public CWinApp
{
public:
BOOL InitInstance() //②程式入點
{
AfxMessageBox("程式依然可以執行!");
return true;
}
};
MyApp theApp; //①建立應用程式。
大家可以看到,我並沒有構造框架,而程式卻可以運行了——彈出一個對話方塊(如果沒有WinMain()函式程式會報錯)。上面我這樣寫還是為了直觀起見,其實我們只要寫兩行程式:
#include <afxwin.h>
CWinApp theApp; //整個程式只構造一個CWinApp類物件,程式就可以執行!
所以說,只要我們構造了CWinApp物件,就可以執行WinMain()函式。我們馬上相信WinMain()函式是在CWinApp類或它的基類中,而不是在其他類中。其實這種看法是錯誤的,我們知道編寫C++程式的時候,不可能讓你在一個類中包含入口函式,WinMain()是由系統呼叫,跟我們的平時程式自身呼叫的函式有著本質的區別。我們可以暫時簡單想象成,當CWinApp物件構造完的時候,WinMain()跟著執行。
現在大家明白了,大部分的“通用程式碼(我們想封裝隱藏的東西)”都可以放到CWinApp類中,那麼它又是怎樣執行起來的呢?為什麼構造了CWinApp類物件就“自動”執行那麼多東西。
大家再仔細想一下,CWinApp類物件構造之後,它會“自動”執行自己的建構函式。那麼我們可以把想要“自動”執行的程式碼放到CWinApp類的建構函式中。
那麼CWinApp類可能打算這樣設計(先不計較正確與否):
class CWinApp : public CWinThead{
public:
virtual BOOL InitInstance(); //解釋過的程式的入點
CWinApp ::CWinApp(){ //建構函式
////////////////////////
WinMain(); //這個是大家一眼看出的錯誤
Create(); //設計、建立、更新顯示視窗
Run(); //訊息迴圈 //////////////////////
}
};
寫完後,大家又馬上感覺到似乎不對,WinMain()函式在這裡好象真的一點用處都沒有,並且能這樣被呼叫嗎(請允許我把手按在聖經上宣告一下:WinMain()不是普通的函式,它要肩負著初始化應用程式,包括全域性變數的初始化,是由系統而不是程式本身呼叫的,WinMain()返回之後,程式就結束了,程序撤消)。再看Create()函式,它能確定設計什麼樣的視窗,建立什麼樣的視窗嗎?如果能在CWinApp的建構函式裡確定的話,我們以後設計MFC程式時視窗就一個樣,這樣似乎不太合理。
回過頭來,我們可以讓WinMain()函式一條語句都不包含嗎?不可以,我們看一下WinMain() 函式的四個引數:
WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
其中第一個引數指向一個例項控制代碼,我們在設計WNDCLASS的時候一定要指定例項控制代碼。我們視窗程式設計,肯定要設計視窗類。所以,WinMain()再簡單也要這樣寫:
int WinMain(HINSTANCE hinst,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
hInstance=hinst
}
既然例項控制代碼要等到程式開始執行才能知道,那麼我們用於建立視窗的Create()函式也要在WinMain()內部才能執行(因為如果等到WinMain()執行完畢後,程式結束,程序撤消,當然Create()也不可能建立視窗)。
再看Run()(訊息迴圈)函式,它能在WinMain()函式外面執行嗎?眾所周知,訊息迴圈就是相同的那麼幾句程式碼,但我們也不要企圖把它放在WinMain()函式之外執行。 所以我們的WinMain()函式可以像下面這樣寫:
WinMain(……)
{ ……視窗類物件執行建立視窗函式……
……程式類物件執行訊息迴圈函式……
}
對於WinMain()的問題,得總結一下,我們封裝的時候是不可以把它封裝到CWinApp類裡面,但由於WinMain()的不變性(或者說有規律可循),MFC完全有能力在我們構造CWinApp類物件的時候,幫我們完成那幾行程式碼。
轉了一個大圈,我們彷彿又回到了SDK程式設計的開始。但現在我們現在能清楚地知道,表面上MFC與SDK程式設計截然不同,但實質上MFC只是用類的形式封裝了SDK函式,封裝之後,我們在WinMain()函式中只需要幾行程式碼,就可以完成一個視窗程式。我們也由此知道了應如何去封裝應用程式類(CWinApp)和主框架視窗類(CFrameWnd)。下面把上開始設計這兩個類。
3、MFC庫的“重寫”
為了簡單起見,我們忽略這兩個類的基類和派生類的編寫,可能大家會認為這是一種很不負責任的做法,但本人覺得這既可減輕負擔,又免了大家在各類之間穿來穿去,更好理解一些(我們在關鍵的地方作註明)。還有,我把全部程式碼寫在同一個檔案中,讓大家看起來不用那麼吃力,但這是最不提倡的寫程式碼方法,大家不要學哦!
C++程式碼
#include <windows.h>
HINSTANCE hInstance;
class CFrameWnd
{
HWND hwnd;
public:
CFrameWnd(); //也可以在這裡呼叫Create()
virtual ~CFrameWnd();
int Create(); //類就留意這一個函式就行了!
BOOL ShowWnd();
};
class CWinApp1
{
public:
CFrameWnd* m_pMainWnd;//在真正的MFC裡面 //它是CWnd指標,但這裡由於不寫CWnd類 //只要把它寫成CFrameWnd指標
CWinApp1* m_pCurrentWinApp;//指向應用程式物件本身
CWinApp1();
virtual ~CWinApp1();
virtual BOOL InitInstance();//MFC原本是必須過載的函式,最重要的函式!!!!
virtual BOOL Run();//訊息迴圈
};
CFrameWnd::CFrameWnd(){}
CFrameWnd::~CFrameWnd(){}
int CFrameWnd::Create() //封裝建立視窗程式碼
{
WNDCLASS wndcls;
wndcls.style=0;
wndcls.cbClsExtra=0;
wndcls.cbWndExtra=0;
wndcls.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
wndcls.hCursor=LoadCursor(NULL,IDC_CROSS);
wndcls.hIcon=LoadIcon(NULL,IDC_ARROW);
wndcls.hInstance=hInstance;
wndcls.lpfnWndProc=DefWindowProc;//預設視窗過程函式。 //大家可以想象成MFC通用的視窗過程。
wndcls.lpszClassName="視窗類名";
wndcls.lpszMenuName=NULL;
RegisterClass(&wndcls);
hwnd=CreateWindow("視窗類名","視窗例項標題名",WS_OVERLAPPEDWINDOW,0,0,600,400,NULL,NULL,hInstance,NULL);
return 0;
}
BOOL CFrameWnd::ShowWnd()//顯示更新視窗
{
ShowWindow(hwnd,SW_SHOWNORMAL);
UpdateWindow(hwnd);
return 0;
}
/////////////
CWinApp1::CWinApp1()
{
m_pCurrentWinApp=this;
}
CWinApp1::~CWinApp1(){} //以下為InitInstance()函式,MFC中要為CWinApp的派生類改寫, //這裡為了方便理解,把它放在CWinApp類裡面完成! //你只要記住真正的MFC在派生類改寫此函式就行了。
BOOL CWinApp1::InitInstance()
{
m_pMainWnd=new CFrameWnd;
m_pMainWnd->Create();
m_pMainWnd->ShowWnd();
return 0;
}
BOOL CWinApp1::Run()//////////////////////封裝訊息迴圈
{
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
} //////////////////////////////////////////////////////封裝訊息迴圈
CWinApp1 theApp; //應用程式物件(全域性)
int WINAPI WinMain(
HINSTANCE hinst,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
hInstance=hinst;
CWinApp1* pApp=theApp.m_pCurrentWinApp; //真正的MFC要寫一個全域性函式AfxGetApp,以獲取CWinApp指標。
pApp->InitInstance();
pApp->Run();
class CWinApp : public CWinThead{
public:
virtual BOOL InitInstance(); //解釋過的程式的入點
CWinApp ::CWinApp(){ //建構函式
////////////////////////
WinMain(); //這個是大家一眼看出的錯誤
Create(); //設計、建立、更新顯示視窗
Run(); //訊息迴圈 //////////////////////
}
};
return 0;
}
程式碼那麼長,實際上只是寫了三個函式,一是CFrameWnd類的Create(),第二個是CWinApp類的InitInstance()和Run()。在此特別要說明的是InitInstance(),真正的MFC中,那是我們跟據自己構造視窗的需要,自己改寫這個函式。
大家可以看到,封裝了上面兩個類以後,在入口函式WinMain中就寫幾行程式碼,就可以產生一個視窗程式。在MFC中,因為WinMain函式就是固定的那麼幾行程式碼,所以MFC絕對可以幫我們自動完成(MFC的特長就是幫我們完成有規律的程式碼),也因此我們建立MFC應用程式的時候,看不到WinMain函式。
相關推薦
MFC六大核心機制之一:MFC程式的初始化流程
1,手動編寫MFC 下面就是我們要手寫MFC的程式碼, class CMyApp:public CWinApp { public: virtual BOOL InitInstance(); }; class CMainWnd:public CFrameWnd { public:
MFC六大核心機制之一:MFC程式的初始化
簡單的MFC視窗程式 設計一個簡單完整MFC程式,產生一個視窗。 當然這不能讓AppWizard自動為我們生成。我們可以在Win32 Application工程下面那樣寫: #include <afxwin.h>
MFC六大核心機制之一MFC程式的初始化
原文地址: 很多做軟體開發的人都有一種對事情刨根問底的精神,例如我們一直在用的MFC,很方便,不用學太多原理性的知識就可以做出各種視窗程式,但喜歡鑽研的朋友肯定想知道,到底微軟幫我們做了些什麼,讓我們在它的框架下可以簡單的寫程式。本文開始就跟大家分享一位同行前輩寫的
MFC六大核心機制——初始化
本人也是新手初學MFC(從Win32過渡過來),總結一下我的理解分享給大家,希望能產生共鳴。 要求:會點C++,知道基本關鍵字和類;至少能大概看明白一個簡單的Win32應用程式。 先來手敲一個最簡單的MFC程式: #include <afxwin.h> class CMyApp :p
VC++/MFC訊息對映機制(2):MFC訊息路由原理
VC++/ MFC訊息對映機制(2):模仿MFC的訊息路由 本文要求對C++語法比較熟悉(特別是虛擬函式的使用),若不熟悉建議參閱《C++語法詳解》一書,電子工業出版社出版。並且本文需結合上一篇文章《MFC訊息對映原理》閱讀。 訊息路由的目的就是把當前類沒有處理的訊息,上傳給其父類進
VC++/MFC訊息對映機制(1):MFC訊息對映原理
VC++/MFC訊息對映機制(1):模仿MFC的訊息對映原理 本文要求對C++語法比較熟悉(特別是虛擬函式的使用),若不熟悉建議參閱《C++語法詳解》一書,電子工業出版社出版 1、訊息對映:就是把指定的訊息交給指定的函式進行處理的方法,這樣就形成了一個<訊息,處理函式>對。
VC++/MFC訊息對映機制(3):MFC訊息路由的原始碼分析
VC++/MFC訊息對映機制(3):MFC訊息路由的原始碼分析 若對C++語法不熟悉,建議參閱《C++語法詳解》一書,電子工業出版社出版,該書語法示例短小精悍,對查閱C++知識點相當方便,並對語法原理進行了透徹、深入詳細的講解。 注意:本文最好結合本人所作前兩篇與訊息對映機制有關的文章一
MFC訊息對映機制(2):MFC訊息路由原理
MFC訊息對映機制(2):模仿MFC的訊息路由 本文要求對C++語法比較熟悉(特別是虛擬函式的使用),若不熟悉建議參閱《C++語法詳解》一書,電子工業出版社出版。並且本文需結合上一篇文章《MFC訊息對映原理》閱讀。 訊息路由的目的就是把當前類沒有處理的訊息,上傳
MFC的六大核心機制概述
這一篇主要講解一下MFC中的6個主要的關鍵技術,整個Application Framework其實從本質上來說都是建立在這6個關鍵技術上的。所以能夠了解它們的原理對理解MFC整個框架和程式設計有非常大的好處。 1、MFC程式的初始化工作 在MFC中所有的類都來源於一
VC++/MFC訊息對映機制(4):附:鉤子函式原理
VC++/MFC訊息對映機制(4):附:鉤子函式原理 若對C++語法不熟悉,建議參閱《C++語法詳解》一書,電子工業出版社出版,該書語法示例短小精悍,對查閱C++知識點相當方便,並對語法原理進行了透徹、深入詳細的講解。 一、鉤子SetWindowsHookEx 注意:本文的鉤子和
MFC六大關鍵技術之一---初始化過程
題外話: 我並不認為MFC減輕了程式設計師們的負擔,MFC出現的目的雖然似乎是為了讓程式設計師不用懂得太多就可以進行視窗程式設計,但本人在MFC裡徘徊了很久很久(因為那時沒有書本詳細介紹MFC的原理),毫無收穫。可能朋友們會說,怎麼一定要了解MFC的具體呢,“黑箱”作業不行
Ogre應用之一:MFC+Ogre+OSI搭建
Ogre應用之一MFC+Ogre+OSI搭建 by:唐崇(theone7) 蟲子||CSDN技術部落格(http://blog.csdn.net/theone7) 引言:這系列的文章來自於學校畢業設計的選題—“3D售樓處”,在小組中我負責3D圖形的渲染,採用的是Ogre引
知識回顧之一:WEB程式語言發展回顧...
WEB程式語言發展回顧… 今日學習python,學到Django框架,覺得有必要暫時停下來,回顧一下計算機程式設計及WEB程式設計的發展。 從世界上第一臺計算機ENIAC開始,計算機軟體開發經歷了從“伺服器/瘦客戶”到“個人計算機”再到“客戶端/伺服器(C/S)”再到WE
spring的兩大核心技術之一:控制反轉
Spring框架的核心就是控制反轉(Inversion of Control)和依賴注入(Dependency Injection),通過這兩方面來實現鬆耦合。 使用IoC,物件是被動的接受依賴類,而不是自己主動的去找。容器在例項化的時候主動將它的依賴類注入給它。可以這
CDN的核心技術之一:負載均衡
CDN是近年來從美國首先興起並迅速發展起來的一種解決網站加速的有效手段。隨著國內寬頻網路使用者的增多和網路視訊業務的增長,短短几年內,國內CDN技術發展迅速,很多網站都開始啟用CDN服務來改善網站速度,提高服務質量,為網路使用者帶去了更好的訪問體驗。 CDN主要是通過在現
SpringBoot引數校驗機制之一:基本驗證概念
引言 在實際專案開發中,我們會對Controller層接收到的引數進行基本的校驗,本文主要介紹SpringBoot專案中使用註解對輸入引數進行初步規則校驗的方法。本文將從以下幾個方面進行闡述。 Rest請求方式 校驗框架 常用的引數校驗註解 程式碼示例
Spring核心探索與總結(二):Spring容器初始化原始碼探索
Spring容器概述 容器是spring的核心,Spring容器使用DI管理構成應用的元件,它會建立相互協作的元件之間的關聯,負責建立物件,裝配它們,配置它們並管理它們的生命週期,從生存到死亡(在這裡,可能就是new 到 finalize())。 Sprin
tensorflow 1.0 學習:參數初始化(initializer)
正交矩陣 算子 smi esc one tor pytho ops ride CNN中最重要的就是參數了,包括W,b。 我們訓練CNN的最終目的就是得到最好的參數,使得目標函數取得最小值。參數的初始化也同樣重要,因此微調受到很多人的重視,那麽tf提供了哪些初始化參數的方法呢
復習:設備初始化配置命令
內容 ace 方式 加密 nihao name ip add dom outer 設備初始化配置命令:Router> //剛開始進入設備,處於用戶模式Router>enable //通過該命令進入到“特權模
Spring Core Container 源碼分析三:Spring Beans 初始化流程分析
turn raw time -c rri add 步驟 引用 lin 前言 本文是筆者所著的 Spring Core Container 源碼分析系列之一; 本篇文章主要試圖梳理出 Spring Beans 的初始化主流程和相關核心代碼邏輯; 本文轉載自本人的私人博客,傷神