1. 程式人生 > >MFC程式執行機制

MFC程式執行機制

用習慣了C的人要看一個程式時首先會想到找到那個main函式在哪,然後再順著往下看.因為main函式作為程式的入口點,整個程式都是從那開始執行的.當在C++中SDK(win32 API project)開發時也繼承沿用C的思維,是有個main函式,不過現在的main函式改名字了,叫WinMain,當然有時還有變體,比如叫_tWinMain,反正名字中總會帶個Main,讓我們一看就知道.而在QT中就跟C一樣,就老實的來個標準的main函式.

我們會發現C++中可以有一個單獨的main函式,不用包含在哪個類中,另外還有不屬於任何類的全域性變數或全域性函式這自然就不是純粹的面嚮物件語言了.所以說C++支援多種程式設計正規化嘛,可以是跟C完全一樣的面向過程正規化,或者再加些普通的類就是基於物件的正規化了,如果再用到繼承和多型就是面向物件了,而要是用到模板就是泛型正規化了.而且這些正規化可以互相混合用.而C#就是純的面向物件,所以它裡面雖然也有main函式,但也是要放在一個類裡面去,至於具體放哪個類無所謂,你隨便放.一般預設是放Program這個類裡.當然並不是說純的面向物件就比混合的正規化好,應該各有優缺點.

哎扯得有點遠了,言歸正傳.

SDK中的流程

開發一個帶介面的SDK程式大致流程是這樣的.首先自然是要有個main函式做入口點.然後按下面的步驟來(為了討論方便,只說大概流程,程式碼也是不完整的)

int _tWinain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)

{

   MSG msg;

   InitApplicatio(HINSTANCE hInstance)   //第1步,註冊窗體類,並在這裡指定了窗體過程WndProc

   InitInstance(HINSTANCE hInstance, int nCmdShow)   //第2步,建立窗體

while (GetMessage(&msg, NULL, 0, 0))    //第3步訊息迴圈,分派訊息

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return (int) msg.wParam;  //第4步,退出程式

}

BOOL InitApplicatio(HINSTANCE hInstance)

{

  return RegisterClass(...);

}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)

{

CreateWindow(...); //建立窗體

ShowWindow(...); //顯示窗體

UpdateWindow(...); //送出WM_PAINT

return TRUE;

}

LRESULT CALLBACK WndProc(...){ }

在MFC中生成一個有介面的程式大體過程也一樣,只不過封裝起來了.那我們感興趣的就是兩個問題.

1.MFC中有沒有main函數了,如果有它跑哪去了? 

2.如果有main函式,它裡面的那4步涉及到的具體操作是否也跟win32 API一樣?

下面我們就來一一解答下

MFC封裝背後流程

實際上候捷那本深入淺出MFC裡面有講的很清楚了.不過由於講的太詳細了,有幾十頁,看的容易暈,而且他舉的例都是老版本的MFC類,在新版本中一些類的函式會有一點點變化.

我這裡就只概括的講下最簡潔的流程.先假如有類CMyApp繼承自CWinApp吧

1.針對第一個問題,MFC裡是有用到main函式的

// export WinMain to force linkage to this module

extern int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

_In_ LPTSTR lpCmdLine, int nCmdShow);

就是這一個函式,在MFC的原始檔appmodul.cpp中能看到這些程式碼,那這個main怎麼被MFC呼叫的呢,你看那註釋,是linkage to this module,也就是被連結器去呼叫的.準確說是被C-Runtime DLL,C執行時動態連結庫呼叫的.

呼叫main的順序

我們知道在MFC中能從程式碼裡看到的入口點是定義一個全域性的繼承於CWinApp的類.比如CMyApp theApp;這樣定義下.在C++中全域性變數是先於main被執行的,所以先初始化theApp後才接著呼叫main

2.針對第2個問題,main函式裡具體的操作.

知道了有main函式,你心裡可能有一絲安慰了.但還是有些覺得不安的是這main函式裡的具體操作是否跟SDK中的一樣,是不是也來那麼幾步,先註冊視窗再建立視窗之類的.

答案是MFC呼叫的main函式大概流程差不多是那樣,但實現細節很不一樣.我們看下上面說的AfxWinMain裡面的內容是啥吧.你可以在winmain.cpp中看到詳細程式碼.

把這個main函式簡化一下,做的操作大概是這樣,

AfxWinMain(...)

{

  //先通過一個全域性函式獲得CWinApp和CWinThread的指標,因為呼叫main之前已經初始化了這兩個類.CMyApp初始化時也會初始化他的父類CWinApp,及父類的父類CWinThread

   CWinThread* pThread = AfxGetThread(); 

   CWinApp* pApp = AfxGetApp();

    //這下面幾個函式就差不多是完成前面講的SDK中的所有步驟

 pApp->InitApplication();

    pThread->InitInstance();

    pThread->Run();

    AfxWinTerm(); //結束程式

   }

反正結束程式我們就不用管了,重點關心前面的三步,註冊視窗,建立視窗,還有訊息分派.

前面的SDK程式中也恰好有函式InitApplication 註冊視窗, InitInstance建立並顯示視窗.而Run函式你猜想可能是分派訊息的..其實大體思路還是沒錯,但實現細節還是有蠻多區別.

pApp->InitApplication();這函式實際上並沒有註冊視窗.註冊視窗,建立顯示視窗全是在pThread->InitInstance();這函式中完成,InitInstance是個虛擬函式,而且我們在自己的程式碼中會重寫它.所以最後呼叫的是我們自己寫的那個InitInstance函式,這就是面向物件裡多型的功能了啊.你指標最終指向對應的子類定義的函式.

BOOL CMyApp::InitInstance()

{

 m_pMainWnd = new CMyFrameWnd; //這張操作會註冊並建立視窗,m_pMainWnd就是返回的視窗控制代碼

m_pMainWnd->ShowWindow(m_nCmdShow); //顯示視窗

m_pMainWnd->UpdateWindow();

}

pThread->Run();是分派訊息,你可以在thrdcore.cpp中檢視CWinThreed的run函式的原始碼,下面摘了一點點.

// acquire and dispatch messages until a WM_QUIT message is received.

for (;;)

{}

不過關於訊息的處理MFC用到了訊息對映機制,比如複雜.這裡不討論了,反正大概就把CMyFrameWnd當成是視窗過程就行了.

總結起來可以這樣簡單的說,MFC中有main函式,但是由系統去呼叫.然後main函式裡面執行的操作差不多,只不過它是通過CWinApp和CWinThread的指標去呼叫一些相關的函式.而指標嘛由於呼叫了虛擬函式,所以+用到了面向物件中的多型,於是轉來轉去的.然後最難的地方可能就是訊息機制在這裡更復雜一點了.不能簡單的與SDK中做一對一的對比.