深入剖析WTL——如何封装Windows界面程序

转载 2007年10月08日 12:32:00
首先还是让我们来看看WTL是怎样封装应用程序线程的。

和ATL类似,WTL使用一个_Module全局变量来保存全局数据,并通过它来引用应用程序级的代码。在WTL中,该变量是CAppModule或CServerAppModule的实例。后者通常作为COM服务器的应用程序。

每个应用程序都有一个或多个界面线程组成。首先剖析一下WTL是怎样管理只有一个界面线程的(除了Mutli-SDI应用程序)。

单个界面线程的封装


先看应用程序的入口函数。

CAppModule _Module;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));    
//程序逻辑,调用全局函数Run()	
int nRet = Run(lpstrCmdLine, nCmdShow);     
//终止模块	_Module.Term();	
::CoUninitialize();	
return nRet;}


在上面的代码中,_Module是一个全局变量,这里是CAppModule的一个实例。而CAppModule类是对应用程序的封装。它封装了诸如初始化模块等功能。一个_Module还维持一个消息循环Map。

入口函数名为_tWinMain()。当使用UNICODE时,编译器会将它替换为wWinMain(),否则,为WinMain()。入口函数其实就是主线程(_Module)的起始点。

在该函数中,最关键的逻辑是调用了全局函数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;}


该函数创建了一个CMessageLoop实例,该实例包含了这个线程的消息循环。这些消息循环都放在模块的全局消息循环中,通过线程的ID来索引。这样,该线程的其它代码就能访问得到。

每一个应用程序维护一个消息循环队列Map,应用程序中的每个线程都通过"_Module.AddMessageLoop(&theLoop);",把该线程的消息循环加入到_Module的消息循环Map中。

消息循环对象包含了消息过滤和空闲处理。每个线程都可以加入空闲处理代码和消息过滤。

我们将在后面更加详细地探讨消息循环。这里仅需要知道,线程是通过创建一个消息循环对象CMessageLoop来包装消息循环的。

多个界面线程的封装


那么再看看,当一个应用程序有多个界面线程时,WTL是怎样对这些线程进行管理的。

通常一个Mutli-SDI应用程序包含多个界面线程。象IE浏览器就是这样的应用程序。每个主窗口都在一个单独的线程中运行,每个这样的线程都有一个消息处理。

WTL App Wizard通过为程序的主线程创建一个Thread Manager类的实例来实现的。

class CThreadManager 
{public:	
// thread init param	
struct _RunData	
{		
LPTSTR lpstrCmdLine;		
int nCmdShow;	};
static DWORD WINAPI RunThread(LPVOID lpData);
DWORD m_dwCount;		
//count of threads	
HANDLE m_arrThreadHandles[MAXIMUM_WAIT_OBJECTS - 1];	
CThreadManager() : m_dwCount(0) {}	
DWORD AddThread(LPTSTR lpstrCmdLine, int nCmdShow);	
void RemoveThread(DWORD dwIndex);	
int Run(LPTSTR lpstrCmdLine, int nCmdShow);};


该类中,m_arrThreadHandles[MAXIMUM_WAIT_OBJECTS-1]是用于存放运行的线程的句柄的数组。而m_dwCount是当前有多少线程的计数值。

AddThread()和RemoveThread()两个函数用于将线程从存放线程句柄的数组中增加或删除线程句柄。

RunThread()是线程的逻辑所在。它的主要工作是创建一个消息队列,并把它加入主线程(_Module)的消息队列Map中。它还创建该线程的主窗口。

该函数的逻辑与前面描述的全局函数Run()相似。其实,应该可以想到的。因为前面的_Module是应用程序的主线程。

下面是该函数的代码:

static DWORD WINAPI RunThread(LPVOID lpData)	
{		
CMessageLoop theLoop;		
_Module.AddMessageLoop(&theLoop);		
_RunData* pData = (_RunData*)lpData;		
CMainFrame wndFrame;		
if(wndFrame.CreateEx() == NULL)		
{			
ATLTRACE(_T("Frame window creation failed!/n"));			
return 0;		}		
wndFrame.ShowWindow(pData->nCmdShow);		
::SetForegroundWindow(wndFrame);	// Win95 needs this		
delete pData;		
int nRet = theLoop.Run();		
_Module.RemoveMessageLoop();		
return nRet;	}


当使用AddThread()函数创建一个线程时,就是创建一个RunThread()的线程。

下面是AddThread()的代码。

DWORD AddThread(LPTSTR lpstrCmdLine, int nCmdShow)	
{		
if(m_dwCount == (MAXIMUM_WAIT_OBJECTS - 1))		
{::MessageBox(NULL, _T("ERROR: Cannot create ANY MORE threads!!!"), _T("test2"), MB_OK);
		return 0;		}		
_RunData* pData = new _RunData;		
pData->lpstrCmdLine = lpstrCmdLine;		
pData->nCmdShow = nCmdShow;		
DWORD dwThreadID;		
HANDLE hThread = ::CreateThread(NULL, 0, RunThread, pData, 0, &dwThreadID);		
if(hThread == NULL)		
{::MessageBox(NULL, _T("ERROR: Cannot create thread!!!"), _T("Application"), MB_OK);
return 0;		}		
m_arrThreadHandles[m_dwCount] = hThread;		
m_dwCount++;		
return dwThreadID;	}


在上述代码的语句:

HANDLE hThread = ::CreateThread(NULL, 0, RunThread, pData, 0, &dwThreadID);


中,RunThread作为参数传递给CreateThread()。

那么应用程序是怎样通过CThreadManager类来管理多个线程的呢?

先看一下现在应用程序的入口函数(主线程)的逻辑:

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 = 0;	
// BLOCK: Run application    
 // 注意该处和前面应用程序的差别。     
// 这里创建一个CThreadManager类的实例,然后调用该类的Run()函数。	
{		
CThreadManager mgr;		
nRet = mgr.Run(lpstrCmdLine, nCmdShow);	}	
_Module.Term();	
::CoUninitialize();	
return nRet;}


在这个入口函数(主线程),创建了一个CThreadManager类的实例。然后调用该类的Run()函数。

看一下Run()做了什么事情。

int Run(LPTSTR lpstrCmdLine, int nCmdShow)
{		MSG msg;		
// force message queue to be created		
::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);        
 // 创建一个界面线程。为该应用程序的第一个界面线程。该界面线程的主体
// 逻辑RunThread()是创建主窗口,并创建消息循环对象。		
AddThread(lpstrCmdLine, nCmdShow);		
int nRet = m_dwCount;		
DWORD dwRet;		
while(m_dwCount > 0)		
{		
dwRet = ::MsgWaitForMultipleObjects(m_dwCount, m_arrThreadHandles, FALSE, INFINITE, QS_ALLINPUT);
if(dwRet == 0xFFFFFFFF)				
::MessageBox(NULL, _T("ERROR: Wait for multiple objects failed!!!"), _T("test2"), MB_OK);
else if(dwRet >= WAIT_OBJECT_0 && dwRet <= (WAIT_OBJECT_0 + m_dwCount - 1RemoveThread(dwRet - WAIT_OBJECT_0);	
else if(dwRet == (WAIT_OBJECT_0 + m_dwCount))			
{				
::GetMessage(&msg, NULL, 0, 0);				
if(msg.message == WM_USER)					
AddThread("", SW_SHOWNORMAL);				
else					
::MessageBeep((UINT)-1);			}			
else				
::MessageBeep((UINT)-1);		}		
return nRet;	}
 


该函数首先创建一个界面线程,这是这个应用程序的第一个界面线程。通过调用AddThread()创建界面线程,指定RunThread()为线程的主体逻辑。它的主要任务是创建一个框架窗口,然后创建一个消息循环对象,并把该对象加入到主线程_Module的消息循环Map中。

随后,该函数调用MsgWaitForMultipleObjects(),进入等待状态(以INFINITE为时间参数)。有三种情况可以使该函数返回。

· 一种是“Wait for multiple objects failed”,是出错情况。

· 一种是某一个查询的线程结束。此时调用RemoveThread()将该线程句柄删除。

· 最后一种是某一线程接收到WM_USER消息。此时,调用AddThread()创建另一个界面线程。

上面的逻辑一直运行,直到所有的界面线程都结束为止。

现在,您是否对如何封装Windows界面程序有一定的了解了呢?如果是,我们接下来就将讨论WTL的消息循环。
 

深入剖析WTL——如何封装Windows界面程序

首先还是让我们来看看WTL是怎样封装应用程序线程的。 和ATL类似,WTL使用一个_Module全局变量来保存全局数据,并通过它来引用应用程序级的代码。在WTL中,该变量是CAppModule...
  • zcxin
  • zcxin
  • 2013年10月23日 01:02
  • 847

跟我一起学Windows界面封装(一) 之 基础篇:Win32 API

一、 初篇 Win32应用程序可以分成两大类:控制台程序和Windows窗口界面程序。其中控制台程序的入口是main(或_tmain),而窗口界面程序的入口函数是WinMain(或_tWinMain...
  • cheneywong
  • cheneywong
  • 2013年04月25日 09:15
  • 1488

解救臃肿的代码,代码封装利器自定义注解深入剖析

点击上面蓝字进行关注的都是靓仔和仙女部分自定义注解内容元注解:  元注解的作用就是负责注解其他注解。    Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 an...
  • TVwR8OfV0P
  • TVwR8OfV0P
  • 2017年12月12日 00:00
  • 209

WTL界面自绘系列-预备

本文不会阐述技术上的细节,旨在提供宏观的思路。 wi界面自绘
  • u011076878
  • u011076878
  • 2014年05月24日 15:38
  • 697

深入编译链接

一、准备知识 1. 指令与数据 指令是指挥计算机工作的命令,在计算机取指阶段从内存中读出的指令流,由控制器处理;数据是各种字母,数字,符号和图像等,在计算机执行阶段从内存中读出的数据流,由CP...
  • HappyToEat
  • HappyToEat
  • 2016年11月14日 19:52
  • 294

《深入剖析tomcat》读书笔记2

《深入剖析tomcat》读书笔记准备写四篇,这篇是第二篇,分析默认连接器,对应书籍的第四章。第三篇分析容器,第四篇来个纵向总结,顺便回答第一篇开头提出的问题。 第四章  Tomcat默认连接器 ...
  • randyjiawenjie
  • randyjiawenjie
  • 2013年03月23日 17:11
  • 1881

深入剖析WTL—WTL框架窗口分析

WTL的基础是ATL。WTL的框架窗口是ATL窗口类的继承。因此,先介绍一下ATL对Windows窗口的封装。 由第一部分介绍的Windows应用程序可以知道创建窗口和窗口工作的逻辑是: ...
  • zcxin
  • zcxin
  • 2013年10月23日 01:42
  • 1458

深入剖析Tomcat-第一章

--开始学习Tomcat服务器,学习代码,自己注释一下。 import java.io.File; import java.io.IOException; import java.io.Input...
  • u011345136
  • u011345136
  • 2015年04月24日 20:17
  • 735

深入剖析WTL—技巧10则

深入剖析WTL—技巧10则 (1)   发布时间:2002.11.14 11:15     来源:赛迪网    作者:Ed Gadziemski著 李阳编译 ...
  • zhangjinqing1234
  • zhangjinqing1234
  • 2014年01月17日 15:25
  • 393

深入剖析WTL——Win32模型

WTL 是Windows Template Library的缩写。最初,WTL是由微软的ATL(Active Template Library)小组成员开发的一个SDK例子。主要是基于ATL的对Win...
  • zcxin
  • zcxin
  • 2013年10月23日 00:28
  • 1138
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章: 深入剖析WTL——如何封装Windows界面程序
举报原因:
原因补充:

(最多只允许输入30个字)