WarmGUI(1) 第一个类,用CBTHook构建CWindow (山寨版MFC::Cwnd)

WarmGUI(1) 第一个类,用CBTHook构建CWindow (山寨版MFC::Cwnd)

转载必须注明原文转自C++博客(cppblog),作者毕达哥拉斯半圆,谢谢合作。

上一篇序言得到了很多高人的帮助鼓励和意见,并且给出了一些框架做参考,我这几天拼命消化这些信息,比较了一些架构,最终决定以完整的应用框架为主,并不先开发完整的控件库,可以先采用Windows自带的控件,或者DX控件,@vczh 的Gac控件也是可以用的嘛,@fzy 提出了快速的迭代开发方法,对我是非常好的建议,但是个人水平问题速度很可能快不起来。^_^

Any Way, 千里之行始于足下,就从最简单的窗口应用开始,请各位朋友继续批评指正以及各种吐槽~,哈哈。

Windows是以消息循环为主体,面向过程的软件结构,这是汇编、C语言对OS开发的必然结果,所以开发框架的第一步就构建面向对象的体系结构,其核心使用了CBThook,下面逐步阐述。

先回顾一个典型的Windows App是这样的:
 1  int  APIENTRY _tWinMain(HINSTANCE hInstance,
 2                       HINSTANCE hPrevInstance,
 3                       LPTSTR    lpCmdLine,
 4                        int        nCmdShow)
 5  {
 6      UNREFERENCED_PARAMETER(hPrevInstance);
 7      UNREFERENCED_PARAMETER(lpCmdLine);
 8 
 9      MSG msg;
10      HACCEL hAccelTable;
11 
12       //  初始化全局字符串
13      LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
14      LoadString(hInstance, IDC_TESTWIN32, szWindowClass, MAX_LOADSTRING);
15     //注册窗口
16      MyRegisterClass(hInstance, szWindowClass, WndProc);
17 
18       //  执行应用程序初始化:
19       if  ( ! InitInstance (hInstance, nCmdShow))
20      {
21           return  FALSE;
22      }
23 
24       while  (GetMessage( & msg, NULL,  0 0 ))
25      {
26           if  ( ! TranslateAccelerator(msg.hwnd, hAccelTable,  & msg))
27          {
28              TranslateMessage( & msg);
29              DispatchMessage( & msg);
30          }
31      }
32 
33       return  ( int ) msg.wParam;
34  }
35

然后是很熟悉的WndProc
 1  LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 2  {
 3       int  wmId, wmEvent;
 4      PAINTSTRUCT ps;
 5      HDC hdc;
 6       switch  (message)
 7      {
 8       case  WM_PAINT:
 9           break ;
10 
11       // message
12       // message
13       // message
14 
15       case  WM_COMMAND:
16           break ;
17       case  WM_CREATE:
18           return   0 ;
19       case  WM_DESTROY:
20          PostQuitMessage( 0 );
21           break ;
22       default :
23           return  DefWindowProc(hWnd, message, wParam, lParam);
24      }
25       return   0 ;
26  }
27 
上面的MyRegisterClass调用WinApi RegisterClassEx 注册窗口,InitInstance调用CreateWindow创建窗口,而while循环做消息处理,直到应用退出。

作为WarmGUI的第一个类CWindow,应该像MFC::CWnd那样中封装RegisterClass和CreateWindow,这应该是很平凡的事情,照做就可以了,麻烦在于对WndProc封装时,WndProc必须是一个全局的函数,只能作为CWindow的静态函数,而static修饰的函数是没有this指针的,因此在消息循环中,我们只能得到HWND句柄,却不知道这个句柄是哪一个CWindow的实例。

MFC解决的办法是使用CBT钩子,HCBT_CREATEWND类型的CBT钩子可以在窗口创建时指定回调函数,在这个回调函数中,将HWND和CWnd做一个映射,CWnd::Attach函数完成这个功能,这样WndProc就可以用FromHandlePermanent函数从hwnd找到CWnd实例,然后可以在消息处理中调用CWnd::On-Message()什么的了。

WARMGUI::CWindow将采用CBT的方法,下面是山寨过程,应该有更好的实现方法,请各位大佬多指教 ^_^
首先类CWindowManager继承std::map,定义一个HWND-CWindow map,
 1  // the CWindowManager is a map of HWND-CWindow
 2  class  CWindow;
 3  // define the HWND-CWindow map
 4  typedef map  < HWND, CWindow *>  CWindowMap;
 5  typedef pair < HWND, CWindow *>  WindowPair;
 6  typedef map  < HWND, CWindow *> ::iterator WndIterator;
 7  typedef pair < WndIterator,  bool >  IterBool;
 8 
 9  class  CWindowManager :  private  CWindowMap
10  {
11  private :
12      CWindowManager( void );
13       ~ CWindowManager( void );
14  public :
15       bool  Add(CWindow *  pwnd);   // add a window to map
16       bool  Remove(HWND hwnd);      // remove a window by hwnd
17       void  Clear();                // remove all items
18      CMyWindow *  Find(HWND hwnd);  // find the window by hwnd
19 
20  public :
21       // get CWindowManager instance as singleton pattern
22       static  CWindowManager  *  GetInstance();
23  };
24 
其中的代码是很平凡的,调用std::map的函数而已,不做举例了。
用一个全局变量
1  CWindow *  gpInitWnd;
记住当前正在创建的CWindow*,并用互斥锁保证不会对其发生读写冲突。事实上只要保证当前线程内不发生读写冲突就可以了,因为CBT是以线程为单位创建的,不过第一个版本的CWindow暂时不考虑,以后再说。
这是CBT-hook代码:
 1  static  HHOOK ghook  =   0 ;
 2 
 3  // Create a WH_CBT Hook
 4  static   bool  HookCrate()
 5  {
 6     HANDLE hThread  =  GetCurrentThread();
 7     DWORD dwThreadId  =  GetThreadId(hThread);
 8      if  (hThread) {
 9          ghook  =  SetWindowsHookEx(
10              WH_CBT,
11              MyCBTProc,     // set the CBT proc
12               0 ,
13              dwThreadId);
14           if  ( ! ghook)
15               return   false ;
16     }
17 
18      return  ( 0 );
19  }
20 
21  // Destroy WH_CBT Hook
22  static   void  HookDestroy()
23  {
24      if  (ghook) {
25          UnhookWindowsHookEx(ghook);
26          ghook  =   0 ;
27     }
28  }
29 
他的回调函数如下:
 1  static  LRESULT CALLBACK MyCBTProc( int  nCode, WPARAM wParam, LPARAM lParam)
 2  {
 3       if  (nCode  ==  HCBT_CREATEWND) {
 4           // GetInitPwnd() return gpInitWnd, the window is creating
 5          CWindow  *  pwnd  =  GetInitPwnd();
 6           if  (pwnd) {
 7              HWND hwnd  =  pwnd ->GetSafeHwnd(); //return pwnd->_hwnd
 8               if  ( ! hwnd) {
 9                   // first time call this proc, the CWindow have no HWND yet,
10                   // Attach() will attach the wParam to pwnd , eg. pwnd->_hwnd = wParam
11                  pwnd -> Attach((HWND)wParam);
12                   // call the PreCreateWindow before WM_CREATE
13                   if  ( ! (pwnd -> PreCreateWindow((LPCREATESTRUCT)lParam)))
14                       return  ( 1 );
15                   return  ( 0 );
16              }  else  {
17                   // second time call this proc, i donw know why for now.
18                   // but this is second chance to decide what is the style
19                   // of the window, or the window should will be created or not,
20                   // if you want create it, return 0, else return non-zero.
21                   return  ( 0 );
22              }
23          }  else  {
24               return  ( 1 );
25          }
26      }  else
27           return  CallNextHookEx(ghook, nCode, wParam, lParam);
28  }
29 
注释中我解释了每个步骤的含义, MyCBTProc返回0则窗口被创建,返回非零则窗口被销毁。尤其要注意的是这个回调会被调用两次,我还不知道为什么,暂时也没时间搞了,请教一下高人指点。

CWindow的Create函数是这样的:
 1  bool  CWindow::Create(TCHAR  *   szClassName,     // NULL for default class name
 2                              TCHAR  *  szWindowName,
 3                                        DWORD dwStyle,
 4                                              RECT &  rect,
 5                              CWindow  *  pParentWnd,
 6                                        LPVOID lpParam)
 7  {
 8       if  ( ! pParentWnd)
 9           return   false ;
10 
11      TCHAR  *  szcn  =  (szClassName)  ?  szClassName : _gszClassName;
12 
13      _hInst  =  pParentWnd -> GetInstance();
14       if  ( ! MyRegisterClass(_hInst, szcn)) {
15          DWORD dwErr  =  GetLastError();
16           if  (dwErr  !=  ERROR_CLASS_ALREADY_EXISTS)  // 0x00000582
17               return   false ;
18      }
19 
20      SetInitWnd( this );    // gpInitWnd = this
21      HookCrate();
22      
23      HWND hWnd  =  CreateWindow(szcn,
24                          szWindowName,
25                          dwStyle,
26                          rect.left, rect.top, rect.right  -  rect.left, rect.bottom  -  rect.top,
27                          pParentWnd -> GetSafeHwnd(),
28                          NULL,     // menu
29                          _hInst,
30                          lpParam);
31      HookDestroy();
32      UnsetInitWnd();     // gpInitWnd = 0;
33 
34       if  ( ! hWnd)
35           return  FALSE;
36 
37      _pParent  =  pParentWnd;
38 
39      ShowWindow(hWnd, SW_SHOW);
40      UpdateWindow(hWnd);
41 
42       return  TRUE;
43  }
过程如下:首先注册窗口类,然后设定gpInitWnd=自己,创建CBTHook,创建窗口,销毁CBTHook,设定gpInitWnd=0,最后显示窗口。

CWindow的WndProc如下:
 1  LRESULT CALLBACK CWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 2  {
 3       // FromHandlePermanent get CWindow* from CWindowManager(a HWND-CWindow* map)
 4       // it call CWindowManager::Find(HWND hwnd) function and return CWindow*
 5       // it is MFC::CWnd Function
 6      CWindow  *  pwnd  =  FromHandlePermanent(hWnd);
 7       if  ( ! pwnd)  return  FirstDefaultWndProc(hWnd, message, wParam, lParam);
 8 
 9       // Message Message and Message
10      LRESULT r  =   0 ;
11       switch  (message)
12      {
13           case  WM_CREATE                      :
14              r  =  pwnd -> OnCreate((LPCREATESTRUCT)lParam);
15               if  (r)
16                   return  (r);
17               break ;
18           case  WM_DESTROY                     :
19              pwnd -> OnDestroy();
20               return  ( 0 );
21           case  WM_PAINT:
22              pwnd -> WindowPaint();
23               return  ( 0 );
24           case  WM_SIZE                        :
25              pwnd -> OnSize((UINT)wParam, LOWORD(lParam), HIWORD(lParam));
26               return  ( 0 );
27           case  WM_DISPLAYCHANGE               :
28              pwnd -> WindowPaint();
29               return  ( 0 );
30           // message
31           // message
32           // and message
33           default :
34               return  DefWindowProc(hWnd, message, wParam, lParam);
35      }
36       return  DefWindowProc(hWnd, message, wParam, lParam);
37  }
现在可以对各种消息机制做一个比较好的封装,比如对WM_PAINT调用WindowPaint函数:
 1  void  CWindow::WindowPaint()
 2  {
 3      PAINTSTRUCT ps;
 4      HDC hdc  =  BeginPaint(_hwnd,  & ps);;
 5 
 6      OnPaint(hdc,  & ps);
 7 
 8      EndPaint(_hwnd,  & ps);
 9  }
在这个函数内部实现BeginPaint和EndPaint对,保证我们不会因为忙乱而忘记这对冤家(常见错误之一呀),CWindow和他的子类只要重写OnPaint就可以了。
现在可以把消息循环封装进来了:
 1  void  CWindow::RunMessageLoop(HACCEL hAccelTable)
 2  {
 3      MSG msg;
 4 
 5       while  (GetMessage( & msg, NULL,  0 0 ))
 6      {
 7           if  (hAccelTable) {
 8               if  ( ! TranslateAccelerator(msg.hwnd, hAccelTable,  & msg))
 9              {
10                  TranslateMessage( & msg);
11                  DispatchMessage( & msg);
12              }
13          }  else  {
14              TranslateMessage( & msg);
15              DispatchMessage( & msg);
16          }
17      }
18  }

差不多已经做完了,我们看看CWindow的声明:
 1 #define msgfun virtual
 2  namespace  WARMGUI {
 3       class  WARMGUI_API CWindow
 4      {
 5       public :
 6          CWindow( void );
 7           virtual   ~ CWindow( void );
 8 
 9          HWND GetSafeHwnd();
10          HINSTANCE GetInstance();
11 
12           void  Attach(HWND hwnd);
13           void  Dettach();
14           virtual  BOOL InitInstance(  HINSTANCE hInstance,
15                                      HINSTANCE hPrevInstance,
16                                      LPTSTR lpCmdLine,
17                                       int  nCmdShow,
18                                      TCHAR  *  szClassName,
19                                      LPTSTR szTitle);
20 
21           virtual   void   RunMessageLoop(HACCEL hAccelTable);
22           virtual  BOOL PreCreateWindow(LPCREATESTRUCT cs);
23 
24           bool  Create(TCHAR  *   szClassName,
25                      TCHAR  *  szWindowName,
26                             DWORD dwStyle,
27                                RECT &  rect,
28                      CWindow  *  pParentWnd,
29                        LPVOID lpParam  =   0 );
30 
32           void  WindowPaint();
33 
34       protected :
35          HWND      _hwnd;
36          HINSTANCE _hInst;
37          CWindow  *  _pParent;
38 
39       protected :
40           /// OnCreate must return 0 to continue the creation of the CWnd object. If the application returns –1, the window will be destroyed. 
41          msgfun  int   OnCreate                    (LPCREATESTRUCT cs);
42          msgfun  void  OnDestroy                   ();
43          msgfun  void  OnSize                      (UINT nType,  int  cx,  int  cy);
44           // bla bla bla hao duo message
45           // bla bla and bla hai you hao duo message
46 
47       protected :
48           static  CWindow  *  FromHandlePermanent(HWND hWnd);
49           static  LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
50 
51       private :
52           virtual  ATOM MyRegisterClass(HINSTANCE hInstance, TCHAR  *  szClassName);
53      };
54  }
现在可以从CWindow继承,并重载他的各种消息函数,并在WinMain中调用,一个最简单的应用框架已经有了,显然,他必须是Hello, World:
 1  using   namespace  WARMGUI;
 2 
 3  class  CMyWindow :  public  CWindow {
 4  public :
 5      CMyWindow();
 6       ~ CMyWindow();
 7      
 8      msgfun  void  OnPaint(LPPAINTSTRUCE ps);x
 9  };
10 
11  void  CMyWindow::OnPaint(HDC hdc, LPPAINTSTRUCT  /* ps */ )
12  {
13      HBRUSH brush  =  CreateSolidBrush(RGB( 0 0 255 ));
14      
15      TCHAR hello[]  =  L " Hello, World! " ;
16      RECT rect;
17      rect.left  =   100 , rect.top  =   100 , rect.right  =   200 , rect.bottom  =   200 ;
18      COLORREF oldBkg  =  SetBkColor(hdc, RGB( 255 255 0 ));
19      COLORREF oldClr  =  SetTextColor(hdc, RGB( 0 0 255 ));
20      DrawText(hdc, hello, _tcslen(hello),  & rect, DT_CENTER);
21      SetTextColor(hdc, oldClr);
22      SetBkColor(hdc, oldBkg);
23      DeleteObject(brush);
24  }
25 
26  int  APIENTRY _tWinMain(HINSTANCE hInstance,
27                       HINSTANCE hPrevInstance,
28                       LPTSTR    lpCmdLine,
29                        int        nCmdShow)
30  {
31      CMyWindow mainwnd;
32 
33       if  (mainwnd.InitInstance(hInstance, hPrevInstance, lpCmdLine, nCmdShow, szClassName, szTitle)) {
34          mainwnd.RunMessageLoop( 0 );
35      }  else  {
36           return  ( - 1 );
37      }
38 
39       return  ( 0 );
40  }
41 

你可能感兴趣的:(WarmGUI(1) 第一个类,用CBTHook构建CWindow (山寨版MFC::Cwnd))