首页 IP地址查询 | Alexa排名查询 | 手机归属地查询
设为首页 收藏本站
  • 网络编程网络编程
  • 软件编程软件编程
  • 数据库技术数据库技术
  • 编程学院
  • 业界资讯 业界资讯
  • 源码中心源码中心
  • 会员中心会员中心
  • 页面导航: 首页C++WTL教程 → 深入剖析WTL——如何封装Windows界面程序

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

    发布:百度空间 发布日期:2009-10-17 字体:[增加 减小] 类型:转载
    首先还是让我们来看看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的消息循环。

     


    Tags: WTL 窗口
    为配合网络严查,文章评论将关闭敬请谅解.
    同 类 文 章
    最 近 更 新
    热 点 排 行