1. 程式人生 > >深入淺出MFC筆記2-MFC程式如何包裝Win32程式

深入淺出MFC筆記2-MFC程式如何包裝Win32程式

從上一節的Win32程式與MFC程式的對比中可以看到,MFC程式中可執行程式碼只有MyApp::InitInstance()和MyFrame::MyFrame(),可以想到MFC程式一定會呼叫此兩個函式,如果你手中有一個好的整合開發環境(如果沒有,那你最好去安裝一個^_^),可以在這兩個函式中設定斷點,然後執行程式,程式首先會在InitInstance函式中中斷,此時檢視程式呼叫堆疊,可以看到如下圖所示的函式呼叫順序:

嗯!我們看到堆疊中有Win32程式入口函式WinMain,說明MFC已經為我們定義了WinMain函式,MFC程式也是從WinMain函式開始執行。

然後進入函式WinMain發現其只是呼叫了AfxWinMain函式,其程式碼如下:

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	__in LPTSTR lpCmdLine, int nCmdShow)
{
	ASSERT(hPrevInstance == NULL);

	int nReturnCode = -1;
	CWinThread* pThread = AfxGetThread();
	CWinApp* pApp = AfxGetApp();

	// AFX internal initialization
	if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
		goto InitFailure;

	// App global initializations (rare)
	if (pApp != NULL && !pApp->InitApplication())
		goto InitFailure;

	// Perform specific initializations
	if (!pThread->InitInstance())
	{
		if (pThread->m_pMainWnd != NULL)
		{
			TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
			pThread->m_pMainWnd->DestroyWindow();
		}
		nReturnCode = pThread->ExitInstance();
		goto InitFailure;
	}
	nReturnCode = pThread->Run();

InitFailure:
#ifdef _DEBUG
	// Check for missing AfxLockTempMap calls
	if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
	{
		TRACE(traceAppMsg, 0, "Warning: Temp map lock count non-zero (%ld).\n",
			AfxGetModuleThreadState()->m_nTempMapLock);
	}
	AfxLockTempMaps();
	AfxUnlockTempMaps(-1);
#endif

	AfxWinTerm();
	return nReturnCode;
}

主要的函式呼叫過程如下:

AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
pApp->InitApplication();
pThread->InitInstance();
pThread->Run();
AfxWinTerm();

這裡先不對每個呼叫進行說明,只用記住這裡有這些過程呼叫。

這部分裡面就有pThread->InitInstance()呼叫,可以知道pThread一定是指向MyApp物件的(CWinThread是CWinApp的基類,CWinApp是MyApp的基類)。

這裡順便說明下MFC的類層次結構:

CObject

|-CCmdTarget

  |-CWinThread

  | |-CWinApp

  |

  |-CWnd

  | |-CFrameWnd

  | |-Cview

  | |-CDialog

  |

  |-CDocument

  |

  |-CDocTemplate

    |-CSingleDocTemplate

    |-CMultiDocTemplate

上面只是列出了和MFC底層機制相關的部分類,更加詳細的內容可以參看MFC幫助文件。

在InitInstance中我們建立了一個MyFrame物件賦值給m_pMainWindow指標,之後顯示更新視窗,我們有理由猜測註冊視窗類別和建立Windows視窗物件的過程是在建立MyFrame物件時完成的,下面深入MyFrame的建立過程。

還是在整合環境中執行進入MyFrame的建立過程,最終可以看到如下圖所示的呼叫堆疊:

可以看到依次呼叫CFrameWnd::Create,CWnd::CreateEx,CFrameWnd::PreCreateWindow,AfxEndDefRegisterClass,_AfxRegisterWithIcon函式,_AfxRegisterWithIcon程式碼如下:

AFX_STATIC BOOL AFXAPI _AfxRegisterWithIcon(WNDCLASS* pWndCls,
	LPCTSTR lpszClassName, UINT nIDIcon)
{
	pWndCls->lpszClassName = lpszClassName;
	HINSTANCE hInst = AfxFindResourceHandle(
		ATL_MAKEINTRESOURCE(nIDIcon), ATL_RT_GROUP_ICON);
	if ((pWndCls->hIcon = ::LoadIcon(hInst, ATL_MAKEINTRESOURCE(nIDIcon))) == NULL)
	{
		// use default icon
		pWndCls->hIcon = ::LoadIcon(NULL, IDI_APPLICATION);
	}
	return AfxRegisterClass(pWndCls);
}
上面的程式碼中就有AfxRegisterClass函式呼叫,此函式又呼叫了::AfxCtxRegisterClass,這個函式是用巨集定義的,呼叫了RegisterClass函式註冊視窗。

定義的巨集如下:AFX_ISOLATIONAWARE_STATICLINK_FUNC(ATOM ,RegisterClassW,(const WNDCLASSW*lpWndClass),(lpWndClass),0)

書中寫的是在AfxRegisterClass函式中直接呼叫了::RegisterClass,和現在分析的並不一樣,可能是版本更新後的調整。

現在註冊視窗類的程式碼已經找到,下面是建立Windows物件的過程。

通過上面的呼叫堆疊進入CWnd::CreateEx函式,在呼叫CFrameWnd::PreCreateWindow函式後有::AfxCtxCreateWindowEx呼叫,此函式和上面::AfxCtxRegisterClass一樣是通過巨集定義的,實際上就是呼叫了::CreateWindowEx,這樣建立視窗的程式碼也找到了,接著看下視窗函式的指定。

在註冊視窗前有呼叫AfxEndDeferRegisterClass,程式碼如下:

BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
{
	// mask off all classes that are already registered
	AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
	fToRegister &= ~pModuleState->m_fRegisteredClasses;
	if (fToRegister == 0)
		return TRUE;

	LONG fRegisteredClasses = 0;

	// common initialization
	WNDCLASS wndcls;
	memset(&wndcls, 0, sizeof(WNDCLASS));   // start with NULL defaults
	wndcls.lpfnWndProc = DefWindowProc;
	wndcls.hInstance = AfxGetInstanceHandle();
	wndcls.hCursor = afxData.hcurArrow;
...
}
嗯?上面用的視窗函式竟然是DefWindowProc!這其中的玄機等學習MFC的訊息處理時再來細說。

現在我們再MFC程式中也找的WinMain入口函式,在視窗類中設定了視窗處理函式,註冊視窗類RegisterClass函式,建立視窗CreateWindowEx函式,顯示視窗函式ShowWindow以及更新視窗函式UpdateWindow,在第一節的問題中只剩下訊息迴圈的啟動了。

我們回到AfxWinMain函式中,在pThread->InitInstance後可以看到呼叫了pThread->Run函式,深入此函式,看見呼叫堆疊如下:

棧頂函式AfxInternalPumpMessage程式碼如下:

BOOL AFXAPI AfxInternalPumpMessage()
{
	_AFX_THREAD_STATE *pState = AfxGetThreadState();

	if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
	{
		// Note: prevents calling message loop things in 'ExitInstance'
		// will never be decremented
		return FALSE;
	}

  // process this message

	if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
	{
		::TranslateMessage(&(pState->m_msgCur));
		::DispatchMessage(&(pState->m_msgCur));
	}
  return TRUE;
}
可以看到上面的程式碼中就有Win32程式進行訊息提取的呼叫過程,迴圈在CWinThread::Run中進行。

對我們MFC的程式執行過程作一個總結:

從WinMain開始,呼叫AfxWinMain,呼叫過載的MyApp::InitInstance,建立MyFrame物件,呼叫CFrameWnd::Create,呼叫CWnd::CreateEx,呼叫CFrameWnd::PreCreateWindow,呼叫::RegisterClass註冊視窗,呼叫::CreateWindowEx建立視窗,呼叫CWinApp::Run,呼叫CWinThread::Run(迴圈),呼叫::GetMessage獲取訊息進行處理。以上除了全域性函式外,其他函式都可以在MyFrame和MyApp進行重寫從而修改程式的執行。

至此,我們已經在MFC的程式碼中找到了Win32程式的各個必要的組成部分,但是對訊息處理部分還不甚明瞭,接著來詳細分析下MFC的訊息處理過程。