首页 > C/C++ > 金山卫士开源代码—-消息机制浅析

金山卫士开源代码—-消息机制浅析

2012年7月1日 发表评论 阅读评论 4665次阅读    

金山卫士开源代码----消息机制浅析 (上)

代码地址:http://download.csdn.net/source/3301518

今天简化了金山的开源代码,用来学习一下,先谢谢金山的开源精神了,呵呵···直接弄最简单的,窗口见下图,关键代码如下,全部的代码放附件里吧。分析中关于累的继承机制等没有详细的说明了,在函数申明中我会用SON: public BASE ::Func() 的方式指出的.今天主要学习一下其消息机制和路由。因为没有学过ATL,所以理解错了希望大家指教一下哦···
先谢谢了,呵呵···

///////////////////////////////////////////////////////////////////////////////
//Main.cpp
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow) {
	if ( FAILED( _Module.Init( hInstance ) ) )
		return -1;
	::CoInitializeEx( NULL , COINIT_APARTMENTTHREADED );

	_Module.Run( );
	_Module.Uninit( );

	::CoUninitialize( );
	return 0;
}

HRESULT KAppModule::Init( HINSTANCE hInstance ) {
	HRESULT hRet = __super::Init( NULL, hInstance );
	if ( FAILED(hRet) )
		return hRet;
	return hRet;
}
HRESULT KAppModule::Run(  ) {
	_InitUIResource(  );//加载各种资源,从XML中
	if ( m_hModRichEdit2 == NULL )
		m_hModRichEdit2 = ::LoadLibrary( _T( "RICHED20.DLL" ) );
	//显示对话框
	CKulvDlg wdlg ;
	return wdlg.DoModal( ) ;
}

void KAppModule::_InitUIResource() {//加载各种资源,从XML中
	BkFontPool::SetDefaultFont(_T("宋体"),-12);
	BkSkin::LoadSkins(IDR_SKIN_DEF);
	BkStyle::LoadStyles(IDR_STYLE_DEF);
	BkString::Load(IDR_STRING_DEF);
}

//DLG.h
#pragma once
class CKulvDlg
	: public CBkDialogImpl
{
public:
	CKulvDlg()
		: CBkDialogImpl(IDR_WARMING_DLG){//IDR_WARMING_DLG : 107, "res//warning_dlg.xml")
	}
	~CKulvDlg();
protected:
	BOOL			OnInitDialog(CWindow /*wndFocus*/ , LPARAM /*lInitParam*/ );
	void			OnBtnClose( );
	void			OnBtnClickText( );
public:
	BK_NOTIFY_MAP(IDC_RICHVIEW_WIN)
		BK_NOTIFY_ID_COMMAND(100, OnBtnClose)
		BK_NOTIFY_ID_COMMAND(101, OnBtnClickText)
		BK_NOTIFY_MAP_END()

		BEGIN_MSG_MAP_EX(CKulvDlg)
		MSG_BK_NOTIFY(IDC_RICHVIEW_WIN)
		CHAIN_MSG_MAP(CBkDialogImpl)
		MSG_WM_INITDIALOG(OnInitDialog)
		END_MSG_MAP()

};
//DLG.cpp
#include "stdafx.h"
#include "DLG.h"

CKulvDlg::~CKulvDlg(){
}

BOOL CKulvDlg::OnInitDialog(CWindow /*wndFocus*/, LPARAM /*lInitParam*/){
	return TRUE;
}

void CKulvDlg::OnBtnClose(){
	__super::EndDialog(IDOK);
}

void CKulvDlg::OnBtnClickText(){
	DontShowWindow() ;
	CString str = CString(__FILE__) ;
	str.Format(TEXT("%s : %d"),str.GetBuffer() , __LINE__) ;
	MessageBox(str);
}

/////////////////////////////////////////////////////////////////////////////////
/*我们来看如下消息机制的宏替换,容易看出,其酷像MFC的宏!开始我还看错了呢,呵呵*/

BK_NOTIFY_MAP(IDC_RICHVIEW_WIN)
BK_NOTIFY_ID_COMMAND(100, OnBtnClose)
BK_NOTIFY_ID_COMMAND(101, OnBtnClickText)
BK_NOTIFY_MAP_END()

/*如上宏声明替换后实际上就是在类里面添加了下面这样的消息分配机制也就是根据控件的ID来一个个比较,然后调用相应控件的处理函数,这个函数就是我们在宏申明的时候指定的。
*/

LRESULT CKulvDlg::OnBkNotify_1000(LPNMHDR pnmh)  {
	UINT_PTR  uCode = pnmh->code;
	if (BKNM_COMMAND == uCode && 100 == ((LPBKNMCOMMAND)pnmh)->uItemID)
	{
		OnBtnClose();
		return TRUE;
	}
	if (BKNM_COMMAND == uCode && 101 == ((LPBKNMCOMMAND)pnmh)->uItemID)
	{
		OnBtnClickText();
		return TRUE;
	}
	SetMsgHandled(FALSE);
	return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
///////////////
/*在对话框类中,还有如下宏申明,看看其到底是什么吧*/
BEGIN_MSG_MAP_EX(CKulvDlg)
MSG_BK_NOTIFY(IDC_RICHVIEW_WIN)
CHAIN_MSG_MAP(CBkDialogImpl)
MSG_WM_INITDIALOG(OnInitDialog)
END_MSG_MAP()

/*替换后变成如下代码:*/
//BEGIN_MSG_MAP_EX(CKulvDlg)
public:
	BOOL m_bMsgHandled;
	/* "handled" management for cracked handlers */
	BOOL IsMsgHandled() const {
		return m_bMsgHandled;
	}
	void SetMsgHandled(BOOL bHandled)   {
		m_bMsgHandled = bHandled;  //设置消息处理状态
	}
	BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
		LRESULT& lResult, DWORD dwMsgMapID = 0)
	{ //本对话框所有的消息处理从这开始
		BOOL bOldMsgHandled = m_bMsgHandled;
		BOOL bRet = _ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult,
			dwMsgMapID);
		m_bMsgHandled = bOldMsgHandled;
		return bRet;
	}
	BOOL _ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam
		, LRESULT& lResult, DWORD dwMsgMapID)
	{ //下面就是短短的消息长征了,相对于复杂的MFC
		BOOL bHandled = TRUE;
		hWnd;
		uMsg;
		wParam;
		lParam;
		lResult;
		bHandled;
		switch(dwMsgMapID)
		{
		case 0:
			//MSG_BK_NOTIFY(IDC_RICHVIEW_WIN)
			if (uMsg == WM_NOTIFY && 1000 == ((LPNMHDR)lParam)->idFrom)
			{ //第一步,如果是控件通告消息,那么消息处理函数是:
				//OnBkNotify_1000,别忘了,这个东西我们上面说过的,那里还有所以的控件消息映射
					//实际上就是根据ID,一个个找。
					SetMsgHandled(TRUE);
				lResult = OnBkNotify_1000((LPNMHDR)lParam);
				if(IsMsgHandled())
					return TRUE;
			}
			//CHAIN_MSG_MAP(CBkDialogImpl)
			if( CBkDialogImpl::ProcessWindowMessage(hWnd, uMsg, wParam, lParam
				, lResult) )
				return TRUE;

			//给父类处理一下,我不明白为何这么早给父类干嘛,如果有些我们要自己处理呢?哦,这样没事的
			//MSG_WM_INITDIALOG(OnInitDialog)
			if (uMsg == WM_INITDIALOG)
			{ //实际上父类没有处理WM_INITDIALOG消息,所以一定传到这里了,进行自定义初始化
				SetMsgHandled(TRUE);
				lResult = (LRESULT)OnInitDialog((HWND)wParam, lParam);
				if(IsMsgHandled())
					return TRUE;
			}
			//MSG_WM_INITDIALOG(OnInitDialog)
			break;
		default://用户ID写错了,那么在输出窗口打印错误消息,以便调试
			ATLTRACE(ATL::atlTraceWindowing, 0, _T("Invalid message map ID (%i)/n"),
				dwMsgMapID);
			ATLASSERT(FALSE);
			break;
		} //消息“长征正式结束”
		return FALSE;
	}
 /*这个消息分配方法清除了,那驱动在哪呢?对,从源头开始找吧。下面的函数一般在Winmain里面调用,是主要的函数,其他都是些初始化什么的*/
HRESULT KAppModule::Run(  )
	{
		_InitUIResource(  );//这地方初始化各种UI库什么的,下回再仔细看看
		//金山界面库是如何用XML实现的,不过应该基本也是封装各种Win32 API
		if ( m_hModRichEdit2 == NULL )
			m_hModRichEdit2 = ::LoadLibrary( _T( "RICHED20.DLL" ) );

		CKulvDlg wdlg ;
		return wdlg.DoModal( ) ;
		//从这里开始吧,我们的自定义子类一般不会重定义这个函数,到父类中,看下面:

	}
	//CBkDialogImpl : public CWindowImpl

	UINT_PTR CBkDialogImpl::DoModal(HWND hWndParent = NULL, LPRECT rect = NULL)
	{
		//····这里省略··字
		//Create函数加载了XML资源,然后设置了窗口什么的,下回在看这部分,专注,专注···
		HWND hWnd = Create(hWndParent, rect);
		//····这里省略··字
		_ModalMessageLoop();//···看下面
		//····这里省略··字
		DestroyWindow();
		return m_uRetCode;
	}

	void CBkDialogImpl::_ModalMessageLoop()
	{
		BOOL bRet;
		MSG msg;
		for(;;)
		{//这个循环很长····直到接收到WM_QUIT,break为止
			if (::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
			{//PeekMessage和GetMessage什么区别去了?对,后者过门不入
				if (WM_QUIT == msg.message)
					break;
			}
			//···
			::TranslateMessage(&msg);
			::DispatchMessage(&msg);
		}
	}

/*这···无限循环取得消息,这已经到操作系统API层了,那么具体的DispatchMessage是发给哪个函数去分发了呢??对,某大作说过,Create,RegisterWindow,那一定有一个消息驱动函数,我们得告诉操作系统的,而且是个回调函数,by the way ,为什么要做成回调函数?直接在dispathMessage 里我们调不就行了吗?···恩,侯捷说过,操作系统想有点控制权。
下面继续*/

/*先说下哦,因为我没看过ATL,WTL,所以不知道哪些部分是属于库的,暂且就这样走到根吧···
经过消息跟踪,发现每次DispatchMessage的时候,下一个被调用的用户自定义函数是:
c:/Program Files/Microsoft Visual Studio 9.0/VC/atlmfc/include/atlwin.h中的3072行:
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)*/

 template
	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_hWnd = hWnd;
		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);
	}
	virtual WNDPROC template
		LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >
		::GetWindowProc(){
			return WindowProc;
	}

/*实际上StartWindowProc调用的是WindowProc
c:/Program Files/Microsoft Visual Studio 9.0/VC/atlmfc/include/atlwin.h中的3072行:
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
WindowProc,对,这个函数名我们非常熟悉了!!
对,由此可知,StartWindowProc一定是我们要找的回调函数,CALLBACK不就告诉我们了嘛,呵呵,其申明也确实是静态的:
static LRESULT CALLBACK StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
下面贴一下代码看看:
*/

 template
	LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		/*把窗口句柄强制转换成基类指针,为什么呢?首先这个窗口句柄应该是窗口类的首地址?
		这是window系统的实现方式,许多句柄其实都是虚拟地址*/
		CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd;
		//····此处省略几行
		/*注意,ProcessWindowMessage是一个虚函数,
		所以···实际上调用的是我们自定义的窗口类!!于是消息路由跑到我们的CKulvDlg中了,
		这一点之前的宏展开说过。不过还得提一下,消息从CKulvDlg中会一步步的往基类中走
		只要没被处理,最终如果运气实在···,还没处理,那就返回吧···*/
		BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);
		//····
		if(!bRet)
		{//如果消息还没被处理,那么进入DefWindowProc,如果不是WM_NCDESTROY
			if(uMsg != WM_NCDESTROY)
				lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
			else
			{//下面准备退出了
				// unsubclass, if needed
				LONG_PTR pfnWndProc = ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC);
				lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
				if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC) == pfnWndProc)
					::SetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC, (LONG_PTR)pThis->m_pfnSuperWindowProc);
				// mark window as destryed
				//上面一注释不是我写的哦,ATL设计者竟然不小心把destroyed写错了,呵呵···
				pThis->m_dwState |= WINSTATE_DESTROYED;
			}
		}
		//····
		return lRes;
	}
	/*值得说明一下的是: ProcessWindowMessage是一个虚函数.
	的吗?宏定义里面没写virtual,但是其基类里面有就够了,如下
	基类CBkWindow中有如下宏定义,确实是虚函数,放心了。*/
	class CBkWindow  BKWIN_BEGIN_MSG_MAP(): public CBkObject
#define BKWIN_BEGIN_MSG_MAP()                                       /
	protected:															/
	virtual BOOL ProcessWindowMessage(								/
	HWND hWnd, UINT uMsg, WPARAM wParam,						/
	LPARAM lParam, LRESULT& lResult)							/
	{

/*读者看到这也基本明白了这消息映射的路由。不过总感觉有点别扭,为什么消息处理函数是StartWindowProc??? 谁告诉windows的?操作系统怎么知道的??那咱们继续吧:没记错的话我们之前说DoModal的时候有如下一句
HWND hWnd = Create(hWndParent, rect);
//····这里省略··字 被我故意省略了很多字···
下面补上吧,看代码
*/

HWND  CBkDialogImpl : public CWindowImpl
	::Create(HWND hWndParent = ::GetActiveWindow(), LPRECT rect = NULL)
	{
		//···
		//调用父类的函数创建窗口,窗口标志什么的是从XML中读入的
		HWND hWnd = __super::Create(hWndParent, rcWnd, lpszCaption, m_richView.GetDlgStyle(), m_richView.GetDlgExStyle());
		//···
		return hWnd;
	}

	//继续看代码,到父类CWindowImpl中去
	HWND  CWindowImpl : public CWindowImplBaseT< TBase, TWinTraits >
		::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);
		//···
		return CWindowImplBaseT< TBase, TWinTraits >::Create(hWndParent, rect, szWindowName,
			dwStyle, dwExStyle, MenuOrID, atom, lpCreateParam);
	}

/*上面的代码调用GetWndClassInfo得到窗口结构的基本信息,其中即设置了窗口处理函数有必要贴一下的代码GetWndClassInfo,它返回一个窗口类的基本信息。其中就包括了窗口处理函数,这是我们用来向操作系统注册的回调函数。*/

/*上面的代码调用GetWndClassInfo得到窗口结构的基本信息,其中即设置了窗口处理函数有必要贴一下的代码GetWndClassInfo,它返回一个窗口类的基本信息。其中就包括了窗口处理函数,这是我们用来向操作系统注册的回调函数。*/
 static ATL::CWndClassInfo& CBkDialogImpl::GetWndClassInfo()
	{
		static ATL::CWndClassInfo wc = {
			{ sizeof(WNDCLASSEX),
			CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | (IsWinXPAndLater() ? CS_DROPSHADOW : 0),
			StartWindowProc, 0, 0, NULL, NULL, NULL,
			(HBRUSH)(COLOR_WINDOW + 1), NULL, NULL, NULL },
			NULL, NULL, IDC_ARROW, TRUE, 0, _T("")
		};
		return wc;
	}

	//其中 ATL::CWndClassInfo 经过一系列的宏定义,最终等于_ATL_WNDCLASSINFOW
	struct _ATL_WNDCLASSINFOW
	{//实际上这个结构基本只用了m_wc,其他初始化为空
		WNDCLASSEXW m_wc;//下面看起定义
		LPCWSTR m_lpszOrigName;
		WNDPROC pWndProc;
		LPCWSTR m_lpszCursorID;
		BOOL m_bSystemCursor;
		ATOM m_atom;
		WCHAR m_szAutoName[5+sizeof(void*)*CHAR_BIT];
		ATOM Register(WNDPROC* p)  	{//这个函数待会会提一下,想操纵系统注册窗口就是通过它的
			return AtlWinModuleRegisterWndClassInfoW(&_AtlWinModule, &_AtlBaseModule, this, p);
		}
	};

	typedef struct tagWNDCLASSEXW {
		UINT        cbSize;
		UINT        style;
		WNDPROC     lpfnWndProc;//仔细的读者会发现,这个成员其实就是注册的消息处理函数:StartWindowProc!!!!
		int         cbClsExtra;
		int         cbWndExtra;
		HINSTANCE   hInstance;
		HICON       hIcon;
		HCURSOR     hCursor;
		HBRUSH      hbrBackground;
		LPCWSTR     lpszMenuName;
		LPCWSTR     lpszClassName;
		HICON       hIconSm;
	} WNDCLASSEXW, *PWNDCLASSEXW, NEAR *NPWNDCLASSEXW, FAR *LPWNDCLASSEXW;

/*好了,到这就清楚了这个窗口处理函数是如何向操作系统注册的,为了结个尾,看一下创建窗口的代码吧
窗口的注册是在上述CWindowImpl的Create函数中,一个很隐蔽的地方
ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);
这个Register其实就是CWndClassInfo结构的一个成员函数,里面经过了一些繁琐的调用,最终调用了下面的代码
p->m_atom = T::RegisterClassEx(pWinModule, &p->m_wc);
很明显,这就是注册窗口类的地方。那么,创建窗口呢?上述函数中注册后紧接着调用了下面的函数创建窗口
*/

template
	HWND CWindowImplBaseT< TBase, TWinTraits > : public CWindowImplRoot< TBase >
		::Create(HWND hWndParent, _U_RECT rect, LPCTSTR szWindowName,
		DWORD dwStyle, DWORD dwExStyle, _U_MENUorID MenuOrID, ATOM atom, LPVOID lpCreateParam)
	{
		//···
		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);

		ATLASSUME(m_hWnd == hWnd);

		return hWnd;
	}

/*不好意思哦,因为刚看金山的代码,所以很多地方都很生疏,
漏掉了许多重要的精彩的地方,写的文笔一塌糊涂,随便在代码中乱注释
影响阅读还忘见谅···前阵子问邹欣老师一个问题,
他说"有些东西不懂的话试着写出来,写个博客也许就懂了",诚然。
理解错的地方(肯定有)还请大家不吝赐教:hw_henry2008@126.com

Todo:ATL::CSimpleMap* m_pMsgLoopMap
这句代码好像也能引出一个消息映射的另一种方式,改天看看。
另外XML的实现机制那块改天也得好好看看了
先这样吧···*/

Share
分类: C/C++ 标签:
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.

注意: 评论者允许使用'@user空格'的方式将自己的评论通知另外评论者。例如, ABC是本文的评论者之一,则使用'@ABC '(不包括单引号)将会自动将您的评论发送给ABC。使用'@all ',将会将评论发送给之前所有其它评论者。请务必注意user必须和评论者名相匹配(大小写一致)。