如何剖析WTL源码?
如何剖析WTL源码?
	作者:姜江
	QQ:457283
	E-mail:jznsmail@163.net
	
	ATLAPP.H包含了消息循环类、接口类、和产生应用程序所必需的一些基础类定义。
类定义如下:
CmessageFilter类---用于消息过滤的
CidleHandler 类---用于空闲消息处理的
CmessageLoop类---用于消息循环的
CappModule 类---应用程序基础类
CserverAppModule类---用于Com服务构架的应用程序类
另外还有3个全局函数:
AtlGetDefaultGuiFont()获得默认的显示字体
AtlCreateBoldFont() 产生一个粗体字体
AtlInitCommonControls()初始化一些控件所需共同的DLL
	WTL程序的结构
	一个窗口程序的创建到销毁过程主要经过如下几个阶段
	1. 注册窗口类
	2. 创建窗口
	3. 进入消息循环
	如果用C写过Win32窗口程序的人一定会记得如下的结构:
	//窗口过程处理函数
	LRESULT CALLBACK WndProc(HWND hwnd,UINT Message,WPARAM wParam,LPARAM lParam);
	int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR szCmdLine,int iCmdShow)
	{
	HWND hwnd = NULL;
	MSG msg;
	…
	WNDCLASS wndclass;
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndProc;
	…
	//注册窗口
	if(!RegisterClass(&wndclass))
	{
	MessageBox(NULL,TEXT("Porgram requires Windows NT!"),szAppName,MB_ICONERROR);
	return 0;
	}
	//创建窗口
	hwnd = CreateWindow(szAppName,TEXT("My Application"),
	WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
	CW_USEDEFAULT,CW_USEDEFAULT,
	CW_USEDEFAULT,CW_USEDEFAULT,
	NULL,NULL,hInstance,NULL);
	
	ShowWindow(hwnd,iCmdShow);
	UpdateWindow(hwnd);
	
	//进入消息循环
	while(GetMessage(&msg,NULL,0,0))
	{
	TranslateMessage(&msg);
	DispatchMessage(&msg);
	}
	
	return msg.wParam;
	}
	那么你可能会问WTL的WinMain函数再哪里?如果你通过WTL/ATL导向生成一个应用程序,那么你会在跟工程名字同名的.cpp文件中发现如下的代码:
	int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
	{
	HRESULT hRes = ::CoInitialize(NULL);
	// If you are running on NT 4.0 or higher you can use the following call instead to 
	// make the EXE free threaded. This means that calls come in on a random RPC thread.
	// HRESULT hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
	ATLASSERT(SUCCEEDED(hRes));
	
	// this resolves ATL window thunking problem when Microsoft Layer for Unicode (MSLU) is used
	::DefWindowProc(NULL, 0, 0, 0L);
	
	AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES); // add flags to support other controls
	
	hRes = _Module.Init(NULL, hInstance); //等下分析它的实现 ATLASSERT(SUCCEEDED(hRes));
	
	int nRet = Run(lpstrCmdLine, nCmdShow);//程序的关键分
	
	_Module.Term();
	::CoUninitialize();
	
	return nRet;
	} 
从这个_tWinMain函数的定义,你可以发现程序的关键部分是我紫色标记出来的Run()函数。这个函数是一个自定义的函数,不过如果通过ATL/WTL导向程序,那么会自动生成这样一个Run()函数的,下面我们先分析一下这个自动生成的Run函数。
	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!/n"));
	return 0;
	}
	
	//显示框架
	wndMain.ShowWindow(nCmdShow);
	
	//运行消息循环
	int nRet = theLoop.Run();
	
	//清除消息
	_Module.RemoveMessageLoop();
	return nRet;
	}
通过这个Run函数我们可以看到在函数中完成了如下几个过程:
1. 生成一个消息循环对象(theLoop)
2. 在全局的_Module中加入这个消息循环
3. 生成一个应用程序框架对象
4. 显示应用程序框架
5. 开始消息循环
6. 结束消息循环
7. 返回WinMain函数,结束程序
	实现分析
在这篇文章我不想过多的分析应用程序框架和窗口的细节,这些内容将放在以后的几篇文章中详细分析,本文主要对ATLAPP.H头文件中实现的一些过程进行详细分析。
	首先从全局变量_Module开始。
	_Module维持着生成应用程序的主线程,控制着程序的消息循环队列,是一个CAppModule的对象。该CAppModule从ATL::CcomModule继承。
	在WTL::CappModule中定义了8个公有成员函数,分别为:
	AddMessageLoop()添加一个消息循环,进入消息循环队列里。
	RemoveMessageLoop()移除消息循环队列。
	GetMessageLoop()获得消息循环。
	InitSettingChangeNotify()初始化环境
	AddSettingChangeNotify()添加一个窗口句柄。
	RemoveSettingChangeNotify()清理环境
	除了8个公有成员函数外,该类还定义了3个公有成员变量
	m_dwMainThreadID负责保存该应用程序的主线程ID
	m_pMsgLoopMap负责存储消息循环
	m_pSettingChangeNotify负责存放窗口句柄
	下面分别来分析几个主要成员函数的实现:
	BOOL AddMessageLoop(CMessageLoop* pMsgLoop)
	{
	CStaticDataInitCriticalSectionLock lock;
	//锁住关键片断,由于进程同步的关系!!!
	if(FAILED(lock.Lock()))
	{
	ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::AddMessageLoop./n"));
	ATLASSERT(FALSE);
	return FALSE;
	}
	
	ATLASSERT(pMsgLoop != NULL);
	ATLASSERT(m_pMsgLoopMap->Lookup(::GetCurrentThreadId()) == NULL); // not in map yet
	
	BOOL bRet = m_pMsgLoopMap->Add(::GetCurrentThreadId(), pMsgLoop);
	
	lock.Unlock();
	
	return bRet;
	}
	关键部分我用红色的字体标记出来了,意思是什么?通过当前线程的Id来标示一个消息循环,存储在m_pMsgLoopMap中。
	
	BOOL RemoveMessageLoop()
	{
	CStaticDataInitCriticalSectionLock lock;
	if(FAILED(lock.Lock()))
	{
	ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::RemoveMessageLoop./n"));
	ATLASSERT(FALSE);
	return FALSE;
	}
	
	BOOL bRet = m_pMsgLoopMap->Remove(::GetCurrentThreadId());
	
	lock.Unlock();
	
	return bRet;
	}
	关键部分同样通过红色字体标记出来,嗯,没错正如AddMessageLoop函数一样,该函数也是通过线程Id来寻找消息循环移除对象的。
	
	CMessageLoop* GetMessageLoop(DWORD dwThreadID = ::GetCurrentThreadId()) const
	{
	CStaticDataInitCriticalSectionLock lock;
	if(FAILED(lock.Lock()))
	{
	ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::GetMessageLoop./n"));
	ATLASSERT(FALSE);
	return NULL;
	}
	
	CMessageLoop* pLoop = m_pMsgLoopMap->Lookup(dwThreadID);
	
	lock.Unlock();
	
	return pLoop;
	}
	该函数通过线程Id在m_pMsgLoopMap消息队列中寻找对应的消息循环,找到后返回。
	
	BOOL InitSettingChangeNotify(DLGPROC pfnDlgProc = _SettingChangeDlgProc)
	{
	CStaticDataInitCriticalSectionLock lock;
	if(FAILED(lock.Lock()))
	{
	ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::InitSettingChangeNotify./n"));
	ATLASSERT(FALSE);
	return FALSE;
	}
	
	if(m_pSettingChangeNotify == NULL)
	{
	typedef ATL::CSimpleArray<HWND> _notifyClass;
	ATLTRY(m_pSettingChangeNotify = new _notifyClass);
	ATLASSERT(m_pSettingChangeNotify != NULL);
	}
	
	BOOL bRet = (m_pSettingChangeNotify != NULL);
	if(bRet && m_pSettingChangeNotify->GetSize() == 0)
	{
	// init everything
	_ATL_EMPTY_DLGTEMPLATE templ;
	//增加一个无模式对话框
	HWND hNtfWnd = ::CreateDialogIndirect(GetModuleInstance(), &templ, NULL, pfnDlgProc);
	ATLASSERT(::IsWindow(hNtfWnd));
	if(::IsWindow(hNtfWnd))
	{
	// need conditional code because types don't match in winuser.h
	#ifdef _WIN64
	::SetWindowLongPtr(hNtfWnd, GWLP_USERDATA, (LONG_PTR)this);
	#else
	::SetWindowLongPtr(hNtfWnd, GWLP_USERDATA, PtrToLong(this));
	#endif
	//加入该窗口句柄
	bRet = m_pSettingChangeNotify->Add(hNtfWnd);
	}
	else
	{
	bRet = FALSE;
	}
	}
	
	lock.Unlock();
	
	return bRet;
	}
	该函数用来初始化一个存放窗口句柄的对象
	
	BOOL InitSettingChangeNotify(DLGPROC pfnDlgProc = _SettingChangeDlgProc)
	{
	CStaticDataInitCriticalSectionLock lock;
	if(FAILED(lock.Lock()))
	{
	ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::InitSettingChangeNotify./n"));
	ATLASSERT(FALSE);
	return FALSE;
	}
	
	if(m_pSettingChangeNotify == NULL)
	{
	typedef ATL::CSimpleArray<HWND> _notifyClass;
	ATLTRY(m_pSettingChangeNotify = new _notifyClass);
	ATLASSERT(m_pSettingChangeNotify != NULL);
	}
	
	BOOL bRet = (m_pSettingChangeNotify != NULL);
	if(bRet && m_pSettingChangeNotify->GetSize() == 0)
	{
	// init everything
	//??空的ATL Dialog Template吗?
	_ATL_EMPTY_DLGTEMPLATE templ;
	//增加一个无模式对话框
	HWND hNtfWnd = ::CreateDialogIndirect(GetModuleInstance(), &templ, NULL, pfnDlgProc);
	ATLASSERT(::IsWindow(hNtfWnd));
	if(::IsWindow(hNtfWnd))
	{
	// need conditional code because types don't match in winuser.h
	#ifdef _WIN64
	::SetWindowLongPtr(hNtfWnd, GWLP_USERDATA, (LONG_PTR)this);
	#else
	::SetWindowLongPtr(hNtfWnd, GWLP_USERDATA, PtrToLong(this));
	#endif
	bRet = m_pSettingChangeNotify->Add(hNtfWnd);
	}
	else
	{
	bRet = FALSE;
	}
	}
	
	lock.Unlock();
	
	return bRet;
	}
	
	//清理消息
	void TermSettingChangeNotify()
	{
	CStaticDataInitCriticalSectionLock lock;
	if(FAILED(lock.Lock()))
	{
	ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::TermSettingChangeNotify./n"));
	ATLASSERT(FALSE);
	return;
	}
	
	if(m_pSettingChangeNotify != NULL && m_pSettingChangeNotify->GetSize() > 0)
	//销毁窗口
	::DestroyWindow((*m_pSettingChangeNotify)[0]);
	delete m_pSettingChangeNotify;
	m_pSettingChangeNotify = NULL;
	
	lock.Unlock();
	}
	
	BOOL AddSettingChangeNotify(HWND hWnd)
	{
	CStaticDataInitCriticalSectionLock lock;
	if(FAILED(lock.Lock()))
	{
	ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::AddSettingChangeNotify./n"));
	ATLASSERT(FALSE);
	return FALSE;
	}
	
	ATLASSERT(::IsWindow(hWnd));
	BOOL bRet = FALSE;
	if(InitSettingChangeNotify() != FALSE)
	bRet = m_pSettingChangeNotify->Add(hWnd);
	
	lock.Unlock();
	
	return bRet;
	}
	
	BOOL RemoveSettingChangeNotify(HWND hWnd)
	{
	CStaticDataInitCriticalSectionLock lock;
	if(FAILED(lock.Lock()))
	{
	ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::RemoveSettingChangeNotify./n"));
	ATLASSERT(FALSE);
	return FALSE;
	}
	
	BOOL bRet = FALSE;
	if(m_pSettingChangeNotify != NULL)
	bRet = m_pSettingChangeNotify->Remove(hWnd);
	
	lock.Unlock();
	
	return bRet;
	}
	
	现在回到刚才提到的Run()函数,里面最开始就定义了一个CmessageLoop循环对象,然后通过_Module对象的AddMessageLoop成员函数加入到循环队列里面,直到_Module调用了RemoveMessageLoop移除循环队列,程序才结束循环,返回到WinMain函数。
	在这里还有一个比较重要的类,那就是CMessageLoop,是他维持了系统的消息,维持了程序的生命周期。那么下面我们来看看这个类的定义和具体的实现方法。
	CmessageLoop包含了如下一些成员函数和成员变量
	成员变量
	//处理消息
	ATL::CSimpleArray<CMessageFilter*> m_aMsgFilter;
	//处理空闲句柄
	ATL::CSimpleArray<CIdleHandler*> m_aIdleHandler;
	//Win32API消息结构
	MSG m_msg;
	
	成员函数(用红色标记的函数是虚函数)
	AddMessageFilter 加入一条消息过滤
	RemoveMessageFilter 移除一条消息过滤
	AddIdleHandler 加入一个空闲句柄
	RemoveIdleHandler 移出一个空闲句柄
	AddUpdateUI 为了兼容老的ATL而设计的
	RemoveUpdateUI 为了兼容老的ATL而设计的
	IsIdleMessage 过滤一些比如WM_MOUSEMOVE之类的消息
	Run 消息循环。关键部分!!!
	PreTranslateMessage 消息过滤
	OnIdle 空闲处理
	
	再这里我不准备对每个函数都进行详细的分析,主要分析核心的函数Run,CmessageLoop由它来维持着系统的消息循环。
	函数如下:
	int Run()
	{
	//空闲?
	BOOL bDoIdle = TRUE;
	//空闲计数器
	int nIdleCount = 0;
	//返回标志
	BOOL bRet;
	
	//开始消息循环了哦!!!
	for(;;)
	{
	
	//当bDoIdle为TRUE,并且不能从消息队列里面取出消息了,那么开始空闲操作了!
	//PM_NOREMOVE:再PeekMessage函数处理后不将消息从队列里移除
	while(bDoIdle && !::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE))
	{
	if(!OnIdle(nIdleCount++))
	bDoIdle = FALSE;
	}
	
	//从当前线程获取一个消息
	//返回-1表示出现一个错误
	//返回 0表示提交了一个WM_QUIT,程序将要退出
	//成功获得一个消息,返回不等于0的值
	bRet = ::GetMessage(&m_msg, NULL, 0, 0);
	
	if(bRet == -1)
	{
	ATLTRACE2(atlTraceUI, 0, _T("::GetMessage returned -1 (error)/n"));
	continue; // error, don't process
	}
	else if(!bRet)
	{
	ATLTRACE2(atlTraceUI, 0, _T("CMessageLoop::Run - exiting/n"));
	break; // WM_QUIT, exit message loop
	}
	
	//如果熟悉使用c语言来写Win32的程序员会发现,原来WinMain中的哪个处理消息循环的语句放到这里来了!!!
	if(!PreTranslateMessage(&m_msg))
	{
	//translates virtual-key messages into character messages.
	::TranslateMessage(&m_msg);
	//dispatches a message to a window procedure
	::DispatchMessage(&m_msg);
	}
	
	//判断是否为空闲消息?
	//排除WM_MOUSEMOVE WM_NCMOUSEMOVE WM_SYSTIMER消息
	if(IsIdleMessage(&m_msg))
	{
	bDoIdle = TRUE;
	nIdleCount = 0;
	}
	}
	
	return (int)m_msg.wParam;
	}
	以上就是对ATLAPP.H中的几个比较重要的类的分析,还有其他几个类的分析我将放在以后的文章中
	(待续。。。)