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

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

首先还是让我们来看看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 - 1))
    RemoveThread(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的消息循环。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WTL 具有两面性,确实是这样的。它没有MFC的界面(GUI)类库那样功能强大,但是能够生成很小的可执行文件。如果你象我一样使用MFC进行界面编程,你会觉得MFC提供的界面控件封装使用起来非常舒服,更不用说MFC内置的消息处理机制。当然,如果你也象我一样不希望自己的程序仅仅因为使用了MFC的框架就增加几百K的大小的话,WTL就是你的选择。当然,我们还要克服一些障碍: ATL样式的模板类初看起来有点怪异 没有类向导的支持,所以要手工处理所有的消息映射。 MSDN没有正式的文档支持,你需要到处去收集有关的文档,甚至是查看WTL的源代码。 买不到参考书籍 没有微软的官方支持 ATL/WTL的窗口与MFC的窗口有很大的不同,你所了解的有关MFC的知识并不全部适用与WTL。 从另一方面讲,WTL也有它自身的优势: 不需要学习或掌握复杂的文档/视图框架。 具有MFC的基本的界面特色,比如DDX/DDV和命令状态的自动更新功能(译者加:比如菜单的Check标记和Enable标记)。 增强了一些MFC的特性(比如更加易用的分隔窗口)。 可生成比静态链接的MFC程序更小的可执行文件(译者加:WTL的所有源代码都是静态链接到你的程序中的)。 你可以修正自己使用的WTL中的错误(BUG)而不会影响其他的应用程序(相比之下,如果你修正了有BUG的MFC/CRT动态库就可能会引起其它应用程序的崩溃。 如果你仍然需要使用MFC,MFC的窗口和ATL/WTL的窗口可以“和平共处”。(例如我工作中的一个原型就使用了了MFC的CFrameWnd,并在其内包含了WTL的CSplitterWindow,在CSplitterWindow中又使用了MFC的CDialogs -- 我并不是为了炫耀什么,只是修改了MFC的代码使之能够使用WTL的分割窗口,它比MFC的分割窗口好的多)。 在这一系列文章中,我将首先介绍ATL的窗口类,毕竟WTL是构建与ATL之上的一系列附加类,所以需要很好的了解ATL的窗口类。介绍完ATL之后我将介绍WTL的特性以并展示它是如何使界面编程变得轻而易举。
目录 WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version PrologueWTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue WTL for MFC Programmers, Chinese Version Prologue ................................ ........................... 6 WTL for MFC Programmers, Part I - ATL GUI Classes ................................ ........................... 8 README.TXTREADME.TXT README.TXTREADME.TXT README.TXTREADME.TXT ................................ ................................ ................................ ......................... 8 对本系列文章的总体介绍 对本系列文章的总体介绍 对本系列文章的总体介绍 ................................ ................................ ................................ ....... 9 对第一章的简单介绍 对第一章的简单介绍 ................................ ................................ ................................ ............. 11 ATL ATL ATL 背景知识 ................................ ................................ ................................ ....................... 11 ATL 和 WTL 的发展历史 ................................ ................................ ......................... 12 ATL-style 模板 ................................ ................................ ................................ ............. 12 ATL ATL ATL 窗口类 ................................ ................................ ................................ ........................... 15 定义一个窗口的实现 定义一个窗口的实现 ................................ ................................ ................................ ............. 17 填写消息映射链 ................................ ................................ ................................ ............. 19 高级消息映射链和嵌 高级消息映射链和嵌 入类 ................................ ................................ ................................ ..... 21 ATLATL 程序的结构 ................................ ................................ ................................ .................... 24 ATLATL 中的对话框 ................................ ................................ ................................ .................... 27 WTL for MFC Programmers, Part II - WTL GUI Base Classes ................................ ............. 32 对第二部分的介绍 对第二部分的介绍 ................................ ................................ ................................ ................. 32 WTL WTL WTL WTL 总体印象 ................................ ................................ ................................ ...................... 32 开始写 WTLWTLWTL程序 ................................ ................................ ................................ .................. 33 WTL WTL WTL WTL 对消息映射的增强 ................................ ................................ ................................ ...... 36 从 WTLWTLWTL的应用程序生成向导能得到什么 的应用程序生成向导能得到什么 的应用程序生成向导能得到什么 ................................ ................................ .......... 42 使用向导的整个过程 ................................ ................................ ................................ ..... 42 查看生成的代码 ................................ ................................ ................................ ............. 44 CMessageLoop CMessageLoop CMessageLoop CMessageLoop CMessageLoop CMessageLoop CMessageLoop CMessageLoop 的内部实现 ................................ ................................ ................................ .. 47 CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl CFrameWindowImpl 的内部实现 ................................ ................................ ......................... 48 回到前面的时钟程序 回到前面的时钟程序 ................................ ................................ ................................ ............. 50 界面元素的自动更新 界面元素的自动更新 (UI Updating)(UI Updating)(UI Updating)(UI Updating) (UI Updating) (UI Updating)(UI Updating)(UI Updating)(UI Updating) (UI Updating) ................................ ................................ ...................... 51 添加控制时钟的新菜单项 ................................ ................................ ............................. 52 调用 UIEnable()................................ ................................ ................................ ............ 53 消息映射链中最后需要注意的地方 消息映射链中最后需要注意的地方 消息映射链中最后需要注意的地方 消息映射链中最后需要注意的地方 ................................ ................................

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值