MFC六大技術(shù)之MFC程序的初始化

分享源自:http://blog.csdn.net/liyi268/article/details/297875

MFC程序的初始化過(guò)程

簡(jiǎn)單的MFC窗口程序

設(shè)計(jì)一個(gè)簡(jiǎn)單完整MFC程序,產(chǎn)生一個(gè)窗口。當(dāng)然這不能讓AppWizard自動(dòng)為我們生成。我們可以在Win32 Application工程下面那樣寫(xiě):

#include <afxwin.h>   
class MyApp : public CWinApp  
 {   
 public:   
     BOOL InitInstance()  //②程序入點(diǎn)  
     {     
          CFrameWnd *Frame=new CFrameWnd();//構(gòu)造框架     
          m_pMainWnd=Frame; //將m_pMainWnd設(shè)定為Frame;     
          Frame->Create(NULL,"最簡(jiǎn)單的窗口");//建立框架     
          Frame->ShowWindow(SW_SHOW);  //顯示框架     
          return true;         //返回    
     }   
};   
MyApp theApp;  //①建立應(yīng)用程序。  

設(shè)定鏈接MFC庫(kù),運(yùn)行,即可看見(jiàn)一個(gè)窗口。

從上面,大家可以看到建立一個(gè)MFC窗口很容易,只用兩步:一是從CWinApp派生一個(gè)應(yīng)用程序類(這里是MyApp),然后建立應(yīng)用程序?qū)ο螅╰heApp),就可以產(chǎn)生一個(gè)自己需要的窗口(即需要什么樣就在InitInstance()里創(chuàng)建就行了)。

整個(gè)程序,就改寫(xiě)一個(gè)InitInstance()函數(shù),創(chuàng)建那么一個(gè)對(duì)象(theApp),就是一個(gè)完整的窗口程序。這就是“黑盒”操作的魔力!

在我們正想為微軟鼓掌的時(shí)候,我們突然覺(jué)得心里空蕩蕩的,我們想知道微軟幫我們做了什么事情,而我們想編自己的程序時(shí)又需要做什么事情,哪怕在上面幾行的程序里面,我們還有不清楚的地方,比如,干嘛有一個(gè)m_pMainWnd指針變量,它從哪里來(lái),又要到哪里去呢?想一想在DOS下編程是多么美妙的一件事呵,我們需要什么變量,就聲明什么變量,需要什么樣的函數(shù),就編寫(xiě)什么樣的函數(shù),或者引用函數(shù)庫(kù)……但是現(xiàn)在我們?cè)趺崔k?

我們可以逆向思維一下,MFC要達(dá)到這種效果,它是怎么做的呢?首先我們要弄明白,VC++不是一種語(yǔ)言,它就象我們學(xué)c語(yǔ)言的時(shí)候的一個(gè)類似記事本的編輯器(請(qǐng)?jiān)徫业牟毁N切的比喻),所以,在VC里面我們用的是C++語(yǔ)言編程,C++才是根本(初學(xué)者總是以為VC是一門什么新的什么語(yǔ)言,一門比C++先進(jìn)很多的復(fù)雜語(yǔ)言,汗)。說(shuō)了那么多,我想用一句簡(jiǎn)單的話概括“MFC黑箱’,就是為我們的程序加入一些固化的‘C++代碼’的東西”。

既然MFC黑箱幫我們加入了代碼,那么大家想想它會(huì)幫我們加入什么樣的代碼呢?他會(huì)幫我們加入求解一元二次方程的代碼嗎?當(dāng)然不會(huì),所以它加入的實(shí)際上是每次編寫(xiě)窗口程序必須的,通用的代碼。

再往下想,什么才是通用的呢?我們每次視窗編程都要寫(xiě)WinMain()函數(shù),都要有注冊(cè)窗口,產(chǎn)生窗口,消息循環(huán),回調(diào)函數(shù)……即然每次都要的東西,就讓它們從我們眼前消失,讓MFC幫忙寫(xiě)入!

手動(dòng)模擬MFC程序的初始化

要知道MFC初始化過(guò)程,大家當(dāng)然可以跟蹤執(zhí)行程序。但這種跟蹤很麻煩,我相信大家都會(huì)跟蹤的暈頭轉(zhuǎn)向。本人覺(jué)得哪怕你理解了MFC代碼,也很容易讓人找不著北,我們完全不懂的時(shí)候,在成千上萬(wàn)行程序的迷宮中如何能找到出口?

我們要換一種方法,不如就來(lái)重新編寫(xiě)個(gè)MFC庫(kù)吧,嘩!大家不要笑,小心你的大牙,我不是瘋子(雖然瘋子也說(shuō)自己不瘋)。我們要寫(xiě)的就是最簡(jiǎn)單的MFC類庫(kù),就是把MFC宏觀上的,理論上的東西寫(xiě)出來(lái)。我們要用最簡(jiǎn)化的代碼,簡(jiǎn)化到剛好能運(yùn)行。

1、需要“重寫(xiě)”的MFC庫(kù)

既然,我們這一節(jié)寫(xiě)的是MFC程序的初始化過(guò)程,上面我們還有了一個(gè)可執(zhí)行的MFC程序。程序中只是用了兩個(gè)MFC類,一個(gè)是CWinApp,另一個(gè)是CFrameWnd。當(dāng)然,還有很多同樣重要MFC類如視圖類,文檔類等等。但在上面的程序可以不用到,所以暫時(shí)省去了它(總之是為了簡(jiǎn)單)。

好,現(xiàn)在開(kāi)始寫(xiě)MFC類庫(kù)吧……唉,面前又有一個(gè)大難題,就是讓大家背一下MFC層次結(jié)構(gòu)圖。天,那張魚(yú)網(wǎng)怎么記得住,但既然我們要理解他,總得知道它是從那里派生出來(lái)的吧。

考慮到大家都很辛苦,那我們看一下上面兩個(gè)類的父子關(guān)系(箭頭代表派生):

       CObject->CCmdTarget->CWinThread->CWinApp->自己的重寫(xiě)了InitInstance()的應(yīng)用程序類。
       CObject(同上)->CCmdTarget(同上)->CWnd->CFrameWnd

看到層次關(guān)系圖之后,終于可以開(kāi)始寫(xiě)MFC類庫(kù)了。按照上面層次結(jié)構(gòu),我們可以寫(xiě)以下六個(gè)類(為了直觀,省去了構(gòu)造函數(shù)和析構(gòu)函數(shù))。

/////////////////////////////////////////////////////////   
class CObiect{};//MFC類的基類。   
class CCmdTarget : public CObject{};   
------------------------------------------------   
class CWinThread : public CCmdTarget{};   
class CWinApp : public CWinThread{};   
------------------------------------------------   
class CWnd : public CCmdTarget{};  
class CFrameWnd : public CWnd{};   
/////////////////////////////////////////////////////////

大家再想一下,在上面的類里面,應(yīng)該有什么?大家馬上會(huì)想到,CWinApp類或者它的基類CCmdTarget里面應(yīng)該有一個(gè)虛函數(shù)virtual BOOL InitInstance(),是的,因?yàn)槟抢锸浅绦虻娜肟邳c(diǎn),初始化程序的地方,那自然少不了的??赡苡行┡笥褧?huì)說(shuō),反正InitInstance()在派生類中一定要重載,我不在CCmdTarget或CWinApp類里定義,留待CWinApp的派生類去增加這個(gè)函數(shù)可不可以。扯到這個(gè)問(wèn)題可能有點(diǎn)越說(shuō)越遠(yuǎn),但我想信C++的朋友對(duì)虛函數(shù)應(yīng)該是沒(méi)有太多的問(wèn)題的??偟膩?lái)說(shuō),作為程序員如果清楚知道基類的某個(gè)函數(shù)要被派生類用到,那定義為虛函數(shù)要方便很多。

也有很多朋友問(wèn),C++為什么不自動(dòng)把基類的所有函數(shù)定義為虛函數(shù)呢,這樣可以省了很多麻煩,這樣所有函數(shù)都遵照派生類有定義的函數(shù)就調(diào)用派生類的,沒(méi)定義的就調(diào)用基類的,不用寫(xiě)virtual的麻煩多好!其實(shí),很多面向?qū)ο蟮恼Z(yǔ)言都這樣做了。但定義一個(gè)虛函數(shù)要生成一個(gè)虛函數(shù)表,要占用系統(tǒng)空間,虛函數(shù)越多,表就越大,有時(shí)得不償失!這里哆嗦幾句,是因?yàn)橥笠f(shuō)明的消息映射中大家更加會(huì)體驗(yàn)到這一點(diǎn),好了,就此打往。

上面我們自己解決了一個(gè)問(wèn)題,就是在CCmdTarge寫(xiě)一個(gè)virtual BOOL InitInstance()。

2、WinMain()函數(shù)和CWinApp類

大家再往下想,我們還要我們MFC“隱藏”更多的東西:WinMain()函數(shù),設(shè)計(jì)窗口類,窗口注冊(cè),消息循環(huán),回調(diào)函數(shù)……我們馬上想到封裝想封裝他們。大家似乎隱約地感覺(jué)到封裝WinMain()不容易,覺(jué)得WinMain()是一個(gè)特殊的函數(shù),許多時(shí)候它代表了一個(gè)程序的起始和終結(jié)。所以在以前寫(xiě)程序的時(shí)候,我們寫(xiě)程序習(xí)慣從WinMain()的左大括寫(xiě)起,到右大括弧返回、結(jié)束程序。

我們換一個(gè)角度去想,有什么東西可以拿到WinMain()外面去做,許多初學(xué)者們,總覺(jué)得WinMain()函數(shù)是天大的函數(shù),什么函數(shù)都好象要在它里面才能真正運(yùn)行。其實(shí)這樣了解很片面,甚至錯(cuò)誤。我們可以寫(xiě)一個(gè)這樣的C++程序:

////////////////////////////////////////////////////  
#include <iostream.h>   
class test
{   
public:    
      test()
      {
            cout<<"請(qǐng)改變你對(duì)main()函數(shù)的看法!"<<endl;
      }   
};  
test test1;   
/**************************/  
void main(){}  
 ////////////////////////////////////////////////////  

在上面的程序里,入口的main()函數(shù)表面上什么也不做,但程序執(zhí)行了(注:實(shí)際入口函數(shù)做了一些我們可以不了解的事情),并輸出了一句話(注:全局對(duì)象比main()首先運(yùn)行)?,F(xiàn)在大家可以知道我們的WinMain()函數(shù)可以什么都不做,程序依然可以運(yùn)行,但沒(méi)有這個(gè)入口函數(shù)程序會(huì)報(bào)錯(cuò)。

那么WinMain()函數(shù)會(huì)放哪個(gè)類上面呢,請(qǐng)看下面程序:

#include <afxwin.h>
class MyApp : public CWinApp   
{   
public:    
      BOOL InitInstance()  //②程序入點(diǎn)    
      {     
            AfxMessageBox("程序依然可以運(yùn)行!");     
            return true;   
      }   
};     
MyApp theApp;  //①建立應(yīng)用程序。  

大家可以看到,我并沒(méi)有構(gòu)造框架,而程序卻可以運(yùn)行了——彈出一個(gè)對(duì)話框(如果沒(méi)有WinMain()函數(shù)程序會(huì)報(bào)錯(cuò))。上面我這樣寫(xiě)還是為了直觀起見(jiàn),其實(shí)我們只要寫(xiě)兩行程序:

#include <afxwin.h>       
CWinApp theApp;     //整個(gè)程序只構(gòu)造一個(gè)CWinApp類對(duì)象,程序就可以運(yùn)行!

所以說(shuō),只要我們構(gòu)造了CWinApp對(duì)象,就可以執(zhí)行WinMain()函數(shù)。我們馬上相信WinMain()函數(shù)是在CWinApp類或它的基類中,而不是在其他類中。其實(shí)這種看法是錯(cuò)誤的,我們知道編寫(xiě)C++程序的時(shí)候,不可能讓你在一個(gè)類中包含入口函數(shù),WinMain()是由系統(tǒng)調(diào)用,跟我們的平時(shí)程序自身調(diào)用的函數(shù)有著本質(zhì)的區(qū)別。我們可以暫時(shí)簡(jiǎn)單想象成,當(dāng)CWinApp對(duì)象構(gòu)造完的時(shí)候,WinMain()跟著執(zhí)行。

現(xiàn)在大家明白了,大部分的“通用代碼(我們想封裝隱藏的東西)”都可以放到CWinApp類中,那么它又是怎樣運(yùn)行起來(lái)的呢?為什么構(gòu)造了CWinApp類對(duì)象就“自動(dòng)”執(zhí)行那么多東西。

大家再仔細(xì)想一下,CWinApp類對(duì)象構(gòu)造之后,它會(huì)“自動(dòng)”執(zhí)行自己的構(gòu)造函數(shù)。那么我們可以把想要“自動(dòng)”執(zhí)行的代碼放到CWinApp類的構(gòu)造函數(shù)中。

那么CWinApp類可能打算這樣設(shè)計(jì)(先不計(jì)較正確與否):

class CWinApp : public CWinThead
 {    
public:    
      virtual BOOL InitInstance(); //解釋過(guò)的程序的入點(diǎn)      
      CWinApp ::CWinApp()
      {//構(gòu)造函數(shù)         
            ////////////////////////        
            WinMain();  //這個(gè)是大家一眼看出的錯(cuò)誤        
            Create();    //設(shè)計(jì)、創(chuàng)建、更新顯示窗口        
            Run();     //消息循環(huán)        //////////////////////     
      }   
};  

寫(xiě)完后,大家又馬上感覺(jué)到似乎不對(duì),WinMain()函數(shù)在這里好象真的一點(diǎn)用處都沒(méi)有,并且能這樣被調(diào)用嗎(請(qǐng)?jiān)试S我把手按在圣經(jīng)上聲明一下:WinMain()不是普通的函數(shù),它要肩負(fù)著初始化應(yīng)用程序,包括全局變量的初始化,是由系統(tǒng)而不是程序本身調(diào)用的,WinMain()返回之后,程序就結(jié)束了,進(jìn)程撤消)。再看Create()函數(shù),它能確定設(shè)計(jì)什么樣的窗口,創(chuàng)建什么樣的窗口嗎?如果能在CWinApp的構(gòu)造函數(shù)里確定的話,我們以后設(shè)計(jì)MFC程序時(shí)窗口就一個(gè)樣,這樣似乎不太合理。

回過(guò)頭來(lái),我們可以讓W(xué)inMain()函數(shù)一條語(yǔ)句都不包含嗎?不可以,我們看一下WinMain() 函數(shù)的四個(gè)參數(shù):

WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

其中第一個(gè)參數(shù)指向一個(gè)實(shí)例句柄,我們?cè)谠O(shè)計(jì)WNDCLASS的時(shí)候一定要指定實(shí)例句柄。我們窗口編程,肯定要設(shè)計(jì)窗口類。所以,WinMain()再簡(jiǎn)單也要這樣寫(xiě):

int WinMain(HINSTANCE hinst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{  
      hInstance=hinst 
}

既然實(shí)例句柄要等到程序開(kāi)始執(zhí)行才能知道,那么我們用于創(chuàng)建窗口的Create()函數(shù)也要在WinMain()內(nèi)部才能執(zhí)行(因?yàn)槿绻鹊絎inMain()執(zhí)行完畢后,程序結(jié)束,進(jìn)程撤消,當(dāng)然Create()也不可能創(chuàng)建窗口)。

再看Run()(消息循環(huán))函數(shù),它能在WinMain()函數(shù)外面運(yùn)行嗎?眾所周知,消息循環(huán)就是相同的那么幾句代碼,但我們也不要企圖把它放在WinMain()函數(shù)之外執(zhí)行。

所以我們的WinMain()函數(shù)可以像下面這樣寫(xiě):

WinMain(……)       
{              
      ……
      窗口類對(duì)象執(zhí)行創(chuàng)建窗口函數(shù)
      ……              
      ……
      程序類對(duì)象執(zhí)行消息循環(huán)函數(shù)
      ……       
}

對(duì)于WinMain()的問(wèn)題,得總結(jié)一下,我們封裝的時(shí)候是不可以把它封裝到CWinApp類里面,但由于WinMain()的不變性(或者說(shuō)有規(guī)律可循),MFC完全有能力在我們構(gòu)造CWinApp類對(duì)象的時(shí)候,幫我們完成那幾行代碼。

轉(zhuǎn)了一個(gè)大圈,我們仿佛又回到了SDK編程的開(kāi)始。但現(xiàn)在我們現(xiàn)在能清楚地知道,表面上MFC與SDK編程截然不同,但實(shí)質(zhì)上MFC只是用類的形式封裝了SDK函數(shù),封裝之后,我們?cè)赪inMain()函數(shù)中只需要幾行代碼,就可以完成一個(gè)窗口程序。我們也由此知道了應(yīng)如何去封裝應(yīng)用程序類(CWinApp)和主框架窗口類(CFrameWnd)。下面把上開(kāi)始設(shè)計(jì)這兩個(gè)類。

3、MFC庫(kù)的“重寫(xiě)”

為了簡(jiǎn)單起見(jiàn),我們忽略這兩個(gè)類的基類和派生類的編寫(xiě),可能大家會(huì)認(rèn)為這是一種很不負(fù)責(zé)任的做法,但本人覺(jué)得這既可減輕負(fù)擔(dān),又免了大家在各類之間穿來(lái)穿去,更好理解一些(我們?cè)陉P(guān)鍵的地方作注明)。還有,我把全部代碼寫(xiě)在同一個(gè)文件中,讓大家看起來(lái)不用那么吃力,但這是最不提倡的寫(xiě)代碼方法,大家不要學(xué)哦!

#include <windows.h>    
HINSTANCE hInstance;     
class CFrameWnd     
{    
      HWND hwnd;   
public:    
      CFrameWnd();   //也可以在這里調(diào)用Create()    
      virtual ~CFrameWnd();    
      int Create();    //類就留意這一個(gè)函數(shù)就行了!    
      BOOL ShowWnd();   
};  
class CWinApp1    
{   
public:    
      CFrameWnd* m_pMainWnd;//在真正的MFC里面   
      //它是CWnd指針,但這里由于不寫(xiě)CWnd類   
      //只要把它寫(xiě)成CFrameWnd指針    
      CWinApp1* m_pCurrentWinApp;//指向應(yīng)用程序?qū)ο蟊旧?   
      CWinApp1();    
      virtual ~CWinApp1();    
      virtual BOOL InitInstance();//MFC原本是必須重載的函數(shù),最重要的函數(shù)?。。?!    
      virtual BOOL Run();//消息循環(huán)   
};   
CFrameWnd::CFrameWnd(){}   
CFrameWnd::~CFrameWnd(){}     
int CFrameWnd::Create()   //封裝創(chuàng)建窗口代碼   
{    
      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;//默認(rèn)窗口過(guò)程函數(shù)。   
      //大家可以想象成MFC通用的窗口過(guò)程。                
      wndcls.lpszClassName="WindowClassName";       
      wndcls.lpszMenuName=NULL;    
      RegisterClass(&wndcls);          
      hwnd=CreateWindow("WindowClassName","WindowInstanceTitle",
                        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()函數(shù),MFC中要為CWinApp的派生類改寫(xiě),   
//這里為了方便理解,把它放在CWinApp類里面完成!   
//你只要記住真正的MFC在派生類改寫(xiě)此函數(shù)就行了。   
BOOL CWinApp1::InitInstance()   
{          
      m_pMainWnd=new CFrameWnd;   
      m_pMainWnd->Create();   
      m_pMainWnd->ShowWnd();    
      return 0;   
}     
BOOL CWinApp1::Run()//////////////////////封裝消息循環(huán)   
{    
      MSG msg;                
      while(GetMessage(&msg,NULL,0,0))    
      {     
            TranslateMessage(&msg);    
            DispatchMessage(&msg);    
      }    
      return 0;   
} //////////////////////////////////////////////////////封裝消息循環(huán)     
CWinApp1 theApp;   //應(yīng)用程序?qū)ο螅ㄈ郑?    
int WINAPI WinMain( HINSTANCE hinst, HINSTANCE hPrevInstance,
         LPSTR lpCmdLine,  int nCmdShow)   
{    
      hInstance=hinst;    
      CWinApp1* pApp=theApp.m_pCurrentWinApp;   //真正的MFC要寫(xiě)一個(gè)全局函數(shù)AfxGetApp,
                                                //以獲取CWinApp指針。    
      pApp->InitInstance();    
      pApp->Run();    
      return 0;   
}   

代碼那么長(zhǎng),實(shí)際上只是寫(xiě)了三個(gè)函數(shù),一是CFrameWnd類的Create(),第二個(gè)是CWinApp類的InitInstance()和Run()。在此特別要說(shuō)明的是InitInstance(),真正的MFC中,那是我們跟據(jù)自己構(gòu)造窗口的需要,自己改寫(xiě)這個(gè)函數(shù)。

大家可以看到,封裝了上面兩個(gè)類以后,在入口函數(shù)WinMain中就寫(xiě)幾行代碼,就可以產(chǎn)生一個(gè)窗口程序。在MFC中,因?yàn)閃inMain函數(shù)就是固定的那么幾行代碼,所以MFC絕對(duì)可以幫我們自動(dòng)完成(MFC的特長(zhǎng)就是幫我們完成有規(guī)律的代碼),也因此我們創(chuàng)建MFC應(yīng)用程序的時(shí)候,看不到WinMain函數(shù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 30,286評(píng)論 8 265
  • 1.面向?qū)ο蟮某绦蛟O(shè)計(jì)思想是什么? 答:把數(shù)據(jù)結(jié)構(gòu)和對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行操作的方法封裝形成一個(gè)個(gè)的對(duì)象。 2.什么是類?...
    少帥yangjie閱讀 5,130評(píng)論 0 14
  • 整理了Node.js、PHP、Go、JAVA、Ruby、Python等語(yǔ)言的爬蟲(chóng)框架。不知道讀者們都用過(guò)什么爬蟲(chóng)框...
    SeanCheney閱讀 5,812評(píng)論 0 16
  • 1 傳說(shuō),古老儒家智慧,秘傳心法只有八個(gè)字: 道心惟微,人心惟危。 想看懂這八個(gè)字,需要八百萬(wàn)本書(shū)——或是,換成另...
    元小野閱讀 578評(píng)論 0 2
  • 春節(jié)假期還沒(méi)有結(jié)束,我就和妻子帶著小女兒回到了鄭州。其實(shí)在內(nèi)心里還是有很多不舍的,畢竟年還沒(méi)有過(guò)完,不舍得離開(kāi)這個(gè)...
    梧_桐_樹(shù)閱讀 290評(píng)論 0 0

友情鏈接更多精彩內(nèi)容