zoukankan      html  css  js  c++  java
  • 深入解析Windows窗体创建和消息分发

    Windows GUI採用基于事件驱动的编程模型,其实差点儿全部的界面库都是这样做的。在纯粹的Window32 SDK编程时代。人们还能够搞懂整个Windows窗口创建和消息的流通过程。可是在如今各种框架的包装下非常多在Window32 SDK下非常明显易懂的东西显得不是那么简单了。本文力图去繁求简,教你看懂全部框架的基本构造。而其实对于了解这一切的人来说。这些界面框架的设计都是如出一辙的,希望看完本文。再去看常见的MFC/WTL等框架时,不会再认为有不论什么的不适。

    C程序的处理办法

    1.基本原理

    先说古老的Win32 SDK的做法,他们非常明显,这里还是先贴上代码。为了缩减篇幅非常多地方我就省略了

    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
    {
        static TCHAR szAppName[] = TEXT ("TestClass");
        HWND         hwnd;
        MSG          msg;
        WNDCLASSEX   wndclassex = {0};
    
    	//1.设计窗体类
        wndclassex.cbSize        = sizeof(WNDCLASSEX);
        wndclassex.style         = CS_HREDRAW | CS_VREDRAW;
        wndclassex.lpfnWndProc   = WndProc ...
    	
    	//2.注冊窗体类
        if (!RegisterClassEx (&wndclassex))
        {
            MessageBox (NULL, TEXT ("RegisterClassEx failed!"), szAppName, MB_ICONERROR);
            return 0;
        }
    
    	//3.创建窗体
        hwnd = CreateWindowEx (WS_EX_OVERLAPPEDWINDOW, 
    		                  szAppName, 
            		          ...
    	
    	//4.显示窗体
        ShowWindow (hwnd, iCmdShow);
        UpdateWindow (hwnd);
    	
    	//5.開始消息循环,又称消息泵
        while (GetMessage (&msg, NULL, 0, 0))
        {
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }
        return msg.wParam;
    }
    
    //回调函数中做消息分发
    LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        HDC hdc;
        PAINTSTRUCT ps;
    
    	//分发
        switch (message)
        {
        case WM_CREATE:
            return (0);
    		
        case WM_PAINT:
            ...
            return (0);
    		
        case WM_DESTROY:
            PostQuitMessage (0);
            return (0);
        }
    
    	//默认处理函数
        return DefWindowProc (hwnd, message, wParam, lParam);
    }
    
    设计窗体类和注冊窗体类可称为InitApplication,即初始化Windows 应用所须要做的工作,这个窗体类能够是公用的

    创建一个窗口和显示可称为InitInstance,即初始化一个Windows 应用实例所须要做的工作,对每一个窗口来说这都是唯一的。可做定制化改动。

    开启消息泵可称为Run,一单消息泵开启,意味着一个程序開始接受消息和分发消息,整个应用程序算是開始执行了。

    在WndProc中做的是推断相应的消息。然后做相应的处理工作。


    2.改进窗体创建

    能够看到,最原始的Win32 SDK编程全然是面向过程编程创建,比較繁琐。为了简化编写。可在VS2008里面打开新建一个Win32 程序能够看到代码例如以下:

    	// 1.设计和注冊消息类
    	...
    	MyRegisterClass(hInstance);
    
    	// 2.运行应用程序初始化:
    	if (!InitInstance (hInstance, nCmdShow))
    	{
    		return FALSE;
    	}
    
    	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN321));
    
    	// 3.主消息循环:
    	while (GetMessage(&msg, NULL, 0, 0))
    	{
    		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    		{
    			TranslateMessage(&msg);
    			DispatchMessage(&msg);
    		}
    	}
    
    	return (int) msg.wParam;

    能够看到依照在基本原理中讲的。这里微软的做法也一样。依照三大部分封装到函数中,简化操作,InitApplication命名成了MyRegisterClass而已。


    3.改进消息分发

    前面讲了改进窗体创建,可是消息分发仍然是一团乱麻,全部的消息响应都塞在switch case中,这里我们自然想到和窗体创建一样。相应的处理分发到函数中。而其实微软也确实是这么做的,微软提供了头文件WindowsX.h来帮助我们分发消息,详细例如以下:

    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    	switch (message)
    	{
    		HANDLE_MSG(hWnd, WM_PAINT, Cls_OnPaint);
    		HANDLE_MSG(hWnd, WM_DESTROY, Cls_OnDestroy);
    	}
    
    	return DefWindowProc(hWnd, message, wParam, lParam);
    }
    
    void Cls_OnPaint(HWND hwnd)
    {
    	HDC hdc;
    	PAINTSTRUCT ps;
    
    	hdc = BeginPaint(hwnd, &ps);
    	//...
    	EndPaint(hwnd, &ps);
    }
    
    void Cls_OnDestroy(HWND hwnd)
    {
    	PostQuitMessage(0);
    }
    能够看到,这里借助于HANDLE_MSG宏让消息相应到详细的处理函数上,HANDLE_MSG展开例如以下:

    #define HANDLE_MSG(hwnd, message, fn)    
        case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
    HANDLE##message为处理函数
    可看到这里借助宏来降低switch case代码的编写量,但实际代码内容是一样的。

    实际上对话框的处理略有不同。例如以下:

    #define chHANDLE_DLGMSG(hwnd, message, fn) case (message): 
    	return (SetDlgMsgResult(hwnd, uMsg, HANDLE_##message((hwnd), (wParam), (lParam), (fn))))
    
    INT_PTR CALLBACK Dlg_Proc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
    {
    	UNREFERENCED_PARAMETER(lParam);
    	switch (message)
    	{
    		chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
    		chHANDLE_DLGMSG(hwnd, WM_COMMAND,    Dlg_OnCommand);
    	}
    
    	return (INT_PTR)FALSE;
    }
    这里的chHANDLE_DLGMSG是仿照HANDLE_MSG自己定义的。

    C++程序的处理办法

    在C++时代。人们提倡面向对象编程,对于窗体的创建和消息的分发响应都是窗体的行为。所以差点儿全部的框架都是想办法把这两者封装在一起,这也是我们解说的重点。对于C++程序我们先讲大框架,再讲窗体类封装。

    1.MFC大框架

    盗用侯捷先生一张图,MFC的基本层次结构例如以下:


    MFC将开启消息循环放到CWinThread中,将窗体注冊、创建、消息分发响应均放到CWnd中处理,这样全部和窗体处理相关的都是由同一个类来完毕,符合C++的封装特性,也便于使用。

    VS安装完文件夹VCatlmfcsrcmfc下有部分mfc源代码,我们直接看微软的实现。

    首先,入口文件appmodul.cpp中定义入口例如以下:

    extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, int nCmdShow)
    #pragma warning(suppress: 4985)
    {
    	// call shared/exported WinMain
    	return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
    }

    然后,在winmain.cpp查看定义AfxWinMain例如以下

    int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, int nCmdShow)
    {
    	...
    
    	// 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())
    	{
    		...
    	}
    	nReturnCode = pThread->Run();
    
        ...
    }

    所以还是InitApplication、InitInstance、Run三大块,AfxWinInit用于做一些框架的初始化工作。

    CWinApp::InitApplication在appcore.cpp中,和C程序略有不同,这里的工作主要是Doc模板管理器的初始化工作。

    CThread::InitInstance虚函数会被用户改写。在这其中调用CWnd完毕窗体的注冊和创建,这个在之后一起讲

    CThread::Run在thrdcore.cpp中,Run-》PumpMessage-》AfxInternalPumpMessage完毕消息泵的开启,例如以下:

    BOOL AFXAPI AfxInternalPumpMessage()
    {
    	_AFX_THREAD_STATE *pState = AfxGetThreadState();
    
    	if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
    	{
    ...
    		return FALSE;
    	}
    
    ...
    
    	if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
    	{
    		::TranslateMessage(&(pState->m_msgCur));
    		::DispatchMessage(&(pState->m_msgCur));
    	}
      return TRUE;
    }

    2.MFC封装窗体创建和消息分发

    利用C++面向对象的特征。将窗体创建和消息分发、响应分装在一个类中,这样一个窗体类相应一个实际窗体,很easy直观。

    首先我们思考下,把窗体创建和消息分发封装在一起有哪些难点?

    1.怎么将不同的窗体过程勾到一起

    历史经验告诉我们。专制往往有时候好办事。

    假设每一个窗体都有自己的窗体过程。那样处理起来就比較麻烦。最好的做法是全部的窗体在同一个窗体过程中控制分发。

    2.同一窗体过程中如何将不同的hwnd消息分发给相应的CWnd类去处理响应

    由于窗体回调函数的限制,回调函数不能拥有相应CWnd类的this指针,也就是说来了窗体消息,如何才干辨别相应的hwnd相应的CWnd,把消息分发给CWnd去处理呢?

    3.最后。假设CWnd拿到了消息。如何去简单有效的去处理和响应呢

    我们说过消息的响应也是在Cwnd中处理,如何将拿到的消息相应成详细的类成员函数呢?


    这些问题串通后,MFC的做法,我们画一张消息流通图例如以下:


    a).窗体创建

    相同我们拿源代码来解释,


    在MFC中我们自己定义的窗体类继承关系例如以下:

    CWnd->CFrameWnd->CMyFrameWnd

    winfrm.cpp中CFrameWnd::LoadFrame

    首先,调用GetIconWndClass->AfxRegisterWndClass完毕窗体类设计和注冊

    然后。调用CFrameWnd::Create->CWnd::CreateEx完毕窗体创建,例如以下:

    BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext)
    {
    ...
    	LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
    	CString strTitle = m_strTitle;
    	if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault, pParentWnd, ATL_MAKEINTRESOURCE(nIDResource), 0L, pContext))
    	{
    		return FALSE;   // will self destruct on failure normally
    	}
    
    ...
    }
    LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource)
    {
    ...
    	if (hIcon != NULL)
    	{
    ...
    		{
    			// register a very similar WNDCLASS
    			return AfxRegisterWndClass(wndcls.style, wndcls.hCursor, wndcls.hbrBackground, hIcon);
    		}
    	}
    	return NULL;        // just use the default
    }

    在wincore.cpp的CWnd::CreateEx中,创建窗体
    BOOL CWnd::CreateEx(...)
    {
    ...
    	// allow modification of several common create parameters
    	CREATESTRUCT cs;
    	cs.dwExStyle = dwExStyle;
    	cs.lpszClass = lpszClassName;
    	cs.lpszName = lpszWindowName;
    	cs.style = dwStyle;
    	cs.x = x;
    	cs.y = y;
    	cs.cx = nWidth;
    	cs.cy = nHeight;
    	cs.hwndParent = hWndParent;
    	cs.hMenu = nIDorHMenu;
    	cs.hInstance = AfxGetInstanceHandle();
    	cs.lpCreateParams = lpParam;
    
    	...
    
    	AfxHookWindowCreate(this);
    	HWND hWnd = ::AfxCtxCreateWindowEx(cs.dwExStyle, cs.lpszClass,
    			cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
    			cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
    
        ...
    }
    当中。在AfxHookWindowCreate中安装钩子使全部窗体消息勾到一起处理例如以下:

    void AFXAPI AfxHookWindowCreate(CWnd* pWnd)
    {
    ...
    		pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT, _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
    	...
    }
    在_AfxCbtFilterHook中代码例如以下:

    LRESULT CALLBACK _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
    {
    ...
    		if (pWndInit != NULL)
    		{
    			AFX_MANAGE_STATE(pWndInit->m_pModuleState);
    
    			// the window should not be in the permanent map at this time
    			ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);
    
    			// connect the HWND to pWndInit...
    			pWndInit->Attach(hWnd);
    			// allow other subclassing to occur first
    			pWndInit->PreSubclassWindow();
    
    			WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr();
    			ASSERT(pOldWndProc != NULL);
    
    			// subclass the window with standard AfxWndProc
    			WNDPROC afxWndProc = AfxGetAfxWndProc();
    			oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,
    				(DWORD_PTR)afxWndProc);
    			ASSERT(oldWndProc != NULL);
    			if (oldWndProc != afxWndProc)
    				*pOldWndProc = oldWndProc;
    
    		...
    }
    当中pWndInit->Attach完毕句柄hwnd和窗体类CWnd*的绑定,建立一张hash表,相应的HashMap结构可參照CWnd::afxMapHWND相应的winhand_.h中的CHandleMap

    SetWindowLongPtr使全部的窗体响应都走AfxWndProc中,在AfxWndProc中完毕消息分发到相应的Cwnd中。

    b).消息的分发和响应

    LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
    {
    ...
    	// all other messages route through message map
    	CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
    	...
    	return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
    }
    能够看到,依据hwnd取得相应的CWnd*,然后看AfxCallWndProc例如以下:

    LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0)
    {
    ...
    
    		// delegate to object's WindowProc
    		lResult = pWnd->WindowProc(nMsg, wParam, lParam);
    ...
    }

    在这里開始调用CWnd成员响应函数,最终又回到CWnd中了。接着往下看

    LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
    {
    ...
    	if (!OnWndMsg(message, wParam, lParam, &lResult))
    		...
    }

    在OnWndMsg中做了什么呢?看以下代码

    BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
    {
    ...
        //WM_COMMAND特殊处理
    	if (message == WM_COMMAND)
    	{
    		if (OnCommand(wParam, lParam))
    		{
    			lResult = 1;
    			goto LReturnTrue;
    		}
    		return FALSE;
    	}
    ...
    
        //找到当前的CWnd类的MessageMap表,查表得到相应响应函数并处理
    	const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();
    	...
    
    		for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;
    			pMessageMap = (*pMessageMap->pfnGetBaseMap)())
    		{
    			...
    		}
    
    ...
    }

    能够看到。到此完毕了CWnd中的查表调用消息相应的处理函数,至于详细的OnCommand消息处理和详细响应函数调用过程,恕不详述。

    可是等等,另一个问题没有解决。那就是CWnd中的消息-处理函数表怎么来的,这就是我们常见的例如以下结构

    BEGIN_MESSAGE_MAP(CMainFrame, ...)
    	ON_WM_CREATE()
    	ON_WM_SETFOCUS()
    ...
    END_MESSAGE_MAP()
    头文件里的DECLARE_MESSAGE_MAP定义例如以下,能够看到回调函数中取消息映射表的函数GetMessageMap在此定义

    #define DECLARE_MESSAGE_MAP() 
    protected: 
    	static const AFX_MSGMAP* PASCAL GetThisMessageMap(); 
    	virtual const AFX_MSGMAP* GetMessageMap() const; 

    BEGIN_MESSAGE_MAP结构展开例如以下

    #define BEGIN_MESSAGE_MAP(theClass, baseClass) 
    	PTM_WARNING_DISABLE 
    	const AFX_MSGMAP* theClass::GetMessageMap() const 
    		{ return GetThisMessageMap(); } 
    	const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() 
    	{ 
    		typedef theClass ThisClass;						   
    		typedef baseClass TheBaseClass;					   
    		static const AFX_MSGMAP_ENTRY _messageEntries[] =  
    		{
    可见真正的映射表结构_massgeEntries在此定义,ON_WM_CRATE完毕实际的表内容填充。比如:
    #define ON_WM_CREATE() 
    	{ WM_CREATE, 0, 0, 0, AfxSig_is, 
    		(AFX_PMSG) (AFX_PMSGW) 
    		(static_cast< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > ( &ThisClass :: OnCreate)) },
    WM_CREATE和OnCreate函数绑定


    至此,窗体类的封装过程尽在眼前,可能你认为过程比較繁琐,那么我把它概括例如以下:

    1.Create窗体时完毕两件事:(1)窗体过程勾到一起处理(2)hwnd和相应的CWnd*绑定

    2.CWnd中利用BEGIN_MESSAGE_MAP结构定义【消息-响应函数】的路由表

    3.响应函数中依据传入的hwnd查表得到CWnd*,调用CWnd->GetMassageMap获取【消息-响应函数】表,查相应消息的响应函数。调用完毕响应

    如今再返回去看,是不是清晰明朗了?

    3.ATL大框架

    MFC出如今C++尚未完好时。没有採用c++的高级特性,基本上都是继承和虚函数、查表,类的层次过多,显得比較臃肿。

    相比而言。ATL就好多了,採用模板技术简化了设计,也没有那么多的层次结构,很轻量,在此基础上上封装的WTL界面库被越来越多的人使用。WTL尽管是在ATL上封装的,可是窗体的创建和消息分发原理并没有变,所以我们仍然以ATL来解说整个过程。

    ATL的框架基本上是自己搭建起来的,自己编写_tWinMain函数,期间可借助CMessageLoop完毕消息泵的开启,例如以下:

    int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
    {
    ...
    	int nRet = Run(lpstrCmdLine, nCmdShow);
    ...
    }
    
    
    int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT)
    {
    	CMessageLoop theLoop;
    	_Module.AddMessageLoop(&theLoop);
    
    	CMainFrame wndMain;
    
    	if(wndMain.CreateEx() == NULL)
    	{
    		ATLTRACE(_T("Main window creation failed!
    "));
    		return 0;
    	}
    
    	wndMain.ShowWindow(nCmdShow);
    
    	int nRet = theLoop.Run();
    
    	_Module.RemoveMessageLoop();
    	return nRet;
    }

    可知CMainFrame::CreateEx完毕窗体创建。atlapp.h中CMessageLoop完毕消息泵开启。代码例如以下:

    // message loop
    	int Run()
    	{
    ...
    
    		for(;;)
    		{
    ...
    
    			bRet = ::GetMessage(&m_msg, NULL, 0, 0);
    ...
    			if(!PreTranslateMessage(&m_msg))
    			{
    				::TranslateMessage(&m_msg);
    				::DispatchMessage(&m_msg);
    			}
    ...
    		}
    		return (int)m_msg.wParam;
    	}

    整个大框架和Win32 SDK非常像,没什么封装,唯一不同的是全部的窗体创建和消息分发都封装到窗体类中了,这个接下来重点说说。

    4.ATL封装窗体创建和消息分发

    和MFC封装窗体类一样,这里相同须要考虑之前说的三个问题。重要的事情说三遍,我就再贴一次之前的话。

    1.怎么将不同的窗体过程勾到一起

    2.同一窗体过程中如何将不同的hwnd消息分发给相应的CWnd类去处理响应

    3.最后,假设CWnd拿到了消息。如何去简单有效的去处理和响应呢


    这里和MFC一样,

    1.全部的窗口窗口过程函数一样。保证统一处理

    2.hwnd和相应窗体类是通过汇编强制粘连起来的

    3.CWnd拿到消息后类似前面的C语言通过一组宏简化switch case结构调用相应的消息响应函数

    相同我们从源代码開始入手:

    全部的窗口类都继承于CWndImpl,我们关注这个类就可以

    a).窗体创建

    atlwin.app中CWindowImpl::Create中例如以下。取得窗体信息,注冊窗体类

    HWND Create(HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL,
                DWORD dwStyle = 0, DWORD dwExStyle = 0,
                _U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL)
    {
        if (T::GetWndClassInfo().m_lpszOrigName == NULL)
            T::GetWndClassInfo().m_lpszOrigName = GetWndClassName();
        ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);
    
        dwStyle = T::GetWndStyle(dwStyle);
        dwExStyle = T::GetWndExStyle(dwExStyle);
    
        ...
    
        return CWindowImplBaseT< TBase, TWinTraits >::Create(hWndParent, rect, szWindowName,
                                                             dwStyle, dwExStyle, MenuOrID, atom, lpCreateParam);
    }

    能够看到调用GetWndClassInfo.Register注冊窗体类,每一个类中使用DECLARE_WND_CLASS等宏来填充相应信息。

    DECLARE_WND_CLASS展开例如以下:

    #define DECLARE_WND_CLASS(WndClassName) 
    static ATL::CWndClassInfo& GetWndClassInfo() 
    { 
    	static ATL::CWndClassInfo wc = 
    	{ 
    		{ sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc, 
    		  0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL }, 
    		NULL, NULL, IDC_ARROW, TRUE, 0, _T("") 
    	}; 
    	return wc; 
    }
    可知默认的全部窗体的窗体过程函数是StartWindowProc。完毕统一控制
    实际的窗体创建函数例如以下:

    template <class TBase, class TWinTraits>
    HWND CWindowImplBaseT< TBase, TWinTraits >::Create(...)
    {
    	BOOL result;
    	ATLASSUME(m_hWnd == NULL);
    
    	// 初始化Thunk结构体
    	result = m_thunk.Init(NULL,NULL);
    ...
    
        //保存当前窗体类指针到全局
    	_AtlWinModule.AddCreateWndData(&m_thunk.cd, this);
    
    	//创建窗体
    	HWND hWnd = ::CreateWindowEx(dwExStyle, MAKEINTATOM(atom), szWindowName,
    		dwStyle, rect.m_lpRect->left, rect.m_lpRect->top, rect.m_lpRect->right - rect.m_lpRect->left,
    		rect.m_lpRect->bottom - rect.m_lpRect->top, hWndParent, MenuOrID.m_hMenu,
    		_AtlBaseModule.GetModuleInstance(), lpCreateParam);
    ...
    }

    这里的Thunk和保存指针到全局之后再说。

    至此创建过程完毕。

    b).消息分发和响应

    前面说了,全部的窗体类的响应函数都是在StartWndProc中。例如以下:

    template <class TBase, class TWinTraits>
    LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
    	CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)_AtlWinModule.ExtractCreateWndData();
    	...
    	pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);
    	WNDPROC pProc = pThis->m_thunk.GetWNDPROC();
    	WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);
    	...
    	return pProc(hWnd, uMsg, wParam, lParam);
    }
    可知,第一次窗体响应会进入到此函数。这里的代码从全局结构中拿到当前窗体类的指针,初始化Thunk。设置Thunk为代理窗体响应函数,通过pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);将窗体的this指针和窗体消息处理函数WindowProc初始化到thunk静态结构里。

    设置全部的窗体过程函数为WindowProc。


    这里用到了Thunk转换技术,所谓Thunk就是转换的意思,这里的基本思想是替换掉传统的WndProc的第一个句柄參数hwnd,让这里的hwnd实际上是相应的CWndImpl的指针。这样完毕了hwnd到窗口类的映射。

    详细的实如今atlstdthunk.h中。例如以下:

    #pragma pack(push,1)
    struct _stdcallthunk
    {
    	DWORD   m_mov;          // 替换hwnd參数为相应CWndImpl指针 mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)
    	DWORD   m_this;         //
    	BYTE    m_jmp;          // 跳转到WndProc
    	DWORD   m_relproc;      // relative jmp
    	BOOL Init(DWORD_PTR proc, void* pThis)
    	{
    		m_mov = 0x042444C7;  //C7 44 24 0C
    		m_this = PtrToUlong(pThis);
    		m_jmp = 0xe9;
    		m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(_stdcallthunk)));   // write block from data cache and
    		FlushInstructionCache(GetCurrentProcess(), this, sizeof(_stdcallthunk));    //  flush from instruction cache
    		return TRUE;
    	}
    ...
    };
    #pragma pack(pop)

    WindowProc处理例如以下:

    template <class TBase, class TWinTraits>
    LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
    	CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd;//hwnd转换成CWindowImplBaseT指针
    	...
    	BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);//调用相应的窗体类的ProcessWindowMessage处理函数
    	...
    }
    可知在详细的窗体过程函数中,将hWnd转换成相应的窗体类。接着调用窗体类的ProcessWindowMessage调用相应的窗体类处理函数。


    每一个窗口类都有ProcessWindowMessage函数,它使用一组宏定义例如以下:

    BEGIN_MSG_MAP(CMainFrame)
        MESSAGE_HANDLER(WM_CREATE, OnCreate)
        ...
    END_MSG_MAP()

    展开显演示样例如以下:

    #define BEGIN_MSG_MAP(theClass) 
    public: 
    	BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) 
    	{ 
    		BOOL bHandled = TRUE; 
    		(hWnd); 
    		(uMsg); 
    		(wParam); 
    		(lParam); 
    		(lResult); 
    		(bHandled); 
    		switch(dwMsgMapID) 
    		{ 
    		case 0:
            
    #define MESSAGE_HANDLER(msg, func) 
    	if(uMsg == msg) 
    	{ 
    		bHandled = TRUE; 
    		lResult = func(uMsg, wParam, lParam, bHandled); 
    		if(bHandled) 
    			return TRUE; 
    	}
    事实上就是宏定义的switch case结构

    至此整个步骤例如以下:

    1.Create中指定统一的窗体过程StartWindowProc

    2.StartWindowProc第一次响应时完毕hwnd和CWndImpl的映射绑定。设置响应函数为WindowProc

    3.WindowProc中转hwnd为CWndImpl*,调用相应类的ProcessWindowMessage分发处理消息

    4.BEGIN_MSG_MAP简化switch case结构,在每一个窗体类中分发处理


    总之封装窗体类须要考虑之前说的三点,搞懂了这三点其它的问题也就迎刃而解了。最后不要嫌我烦,再贴一遍我一直强调的重点,牢记这三点。看对应的框架封装过程大同小异:

    1.怎么将不同的窗体过程勾到一起

    2.同一窗体过程中如何将不同的hwnd消息分发给相应的CWnd类去处理响应

    3.最后。假设CWnd拿到了消息,如何去简单有效的去处理和响应呢


    原创,转载请注明来自http://blog.csdn.net/wenzhou1219

  • 相关阅读:
    [转载]DataView详解
    (转)C#中“EQUALS”与“==”的速度比较
    CS0016: 未能写入输出文件“c:WindowsMicrosoft.NETFramework64v2.0.50727Temporary ASP.NET Files oot921bbfc4ca7cf42App_Code.fu98jwep.dll”--“拒绝访问。 ”
    C# 配置错误定义了重复的“system.web.extensions/scripting/scriptResourceHandler”节
    C# 利用mysql.data 在mysql中创建数据库及数据表
    【转载】经典SQL语句大全
    我不是一个做产品人,但我有一颗做产品的心--浅谈“痛点”
    软件工程--个人总结
    第十六周进度条
    梦断代码阅读笔记3
  • 原文地址:https://www.cnblogs.com/yutingliuyl/p/7249445.html
Copyright © 2011-2022 走看看