• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            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, 00))
            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, 00))
             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(00255));
            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(2552550));
            19     COLORREF oldClr = SetTextColor(hdc, RGB(00255));
            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 

            posted on 2012-09-04 00:25 畢達哥拉斯半圓 閱讀(2101) 評論(14)  編輯 收藏 引用

            評論

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 09:21 萬連文

            1.return路徑太多,不容易排錯
            2.CMyWindow* Find(HWND hwnd);我想是應該加一個const
            3.Add和Remove方法的參數不對稱,你確信是這樣子嗎?
            4.CWindow * pwnd指針前后都有空格是排版導致的嗎?
            5.WndIterator為何不用CWindowMap::iterator去定義,名字上有那么大差別,它們表達的含義真的不是一個事物嗎?
            6.RECT rect = { 100, 100, 200, 200 };不好嗎?

            因為你說了“代碼簡潔漂亮”,吸引了我,結果有點失望。Any Way, 千里之行始于足下。  回復  更多評論   

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 09:22 Richard Wei

            對于如何關聯HWND和CWnd常用有3種方法:
            (1)MFC的Map,就是博主上面用的
            (2)把CWnd指針放在CreateWindowEx的最后一個參數里,在回調WM_NCCREATE里從CREATESTRUCT里取出來,再保存CWnd*到SetWindowLong(GWL_USERDATA)中,后面其他消息就直接取出CWnd*調用
            (3)ATL的Thunk機制

            第一種方法容易理解,但是低效
            第二種方法把CreateParams和UserData用掉了,別人沒法再用了,通用性不是很好
            第三種方法簡單高效,但是會涉及到機器碼  回復  更多評論   

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 09:34 溪流

            class CWindowManager : private CWindowMap
            對這個有異議,把CwindowMap作為CWindowManager的一個成員貌似也可以滿足需求?  回復  更多評論   

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 09:35 畢達哥拉斯半圓

            @萬連文
            多謝幫助啊多謝!按你說的修改修改!  回復  更多評論   

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 09:37 畢達哥拉斯半圓

            @Richard Wei
            多謝指教!第三個我還真不會呢,能甩個鏈接學習一下嗎?  回復  更多評論   

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 09:41 畢達哥拉斯半圓

            @溪流
            當然可以了,我考慮WindowManager這個類除了做map沒有其他任何工作,所以就用繼承了,如果他還有其他方面的工作,我就把map作為成員組合進來,不知道會不會更好?  回復  更多評論   

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 10:09 Richard Wei

            @畢達哥拉斯半圓
            貌似上面的朋友 溪流 寫過,http://www.shnenglu.com/Streamlet/archive/2010/10/24/131064.aspx  回復  更多評論   

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 10:13 Richard Wei

            高級點的可以參考下這個http://www.shnenglu.com/cexer/archive/2008/08/06/58169.html  回復  更多評論   

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 10:15 畢達哥拉斯半圓

            @萬連文
            按您說的改了改,發現一個問題,
            1. return路徑太多,不容易排錯
            這個是從MFC的習慣而來的,當OnMESSAGE函數調用時,如果返回非0值,就從消息循環中返回這個值,如果是0值,則有兩種情況,一種是直接返回,另一種是繼續執行DefWndProc. 如果保持與MFC的習慣一致,不知道有沒有更好的辦法,請教一下。

            我把宣傳中的簡潔漂亮什么的去掉了,在高人眼中,我這還是太業余啦!哈哈  回復  更多評論   

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 10:24 畢達哥拉斯半圓

            @Richard Wei
            @溪流
            剛看到這個,相見恨晚相見恨晚! 我向你們好好學習學習.  回復  更多評論   

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 10:50 溪流

            @畢達哥拉斯半圓
            感覺沒事不用繼承的好……如此,C++中的private繼承就想不到什么應用場景了,困惑中……  回復  更多評論   

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 10:50 溪流

            @畢達哥拉斯半圓
            互相學習,不同的方案各有各的優缺點唄~  回復  更多評論   

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 11:42 畢達哥拉斯半圓

            @溪流
            額,也不是吧,畢竟WindowManager就是個map,或者改名叫CWndMap就理順了?  回復  更多評論   

            # re: WarmGUI(1) 第一個類,用CBTHook構建CWindow (山寨版MFC::Cwnd) 2012-09-04 11:50 溪流

            @畢達哥拉斯半圓
            也不順,我就是覺得private繼承一點也沒有存在的必要……感覺你的場景就是一個典型的組合場景……  回復  更多評論   

            <2012年9月>
            2627282930311
            2345678
            9101112131415
            16171819202122
            23242526272829
            30123456

            導航

            統計

            常用鏈接

            留言簿(3)

            隨筆檔案

            相冊

            contact

            搜索

            最新評論

            閱讀排行榜

            評論排行榜

            久久受www免费人成_看片中文| 久久国产精品久久| 久久99精品久久久久久水蜜桃 | 欧美一区二区精品久久| 亚洲国产精品高清久久久| 青青草原综合久久大伊人| 性做久久久久久久久久久| 久久久久国产日韩精品网站| 99久久精品国产一区二区蜜芽| 丰满少妇人妻久久久久久4| 99热都是精品久久久久久| 激情久久久久久久久久| 精品国产一区二区三区久久蜜臀| 久久久精品久久久久特色影视| 久久久这里有精品中文字幕| 午夜精品久久影院蜜桃| 精品久久久无码21p发布| 亚洲精品乱码久久久久久中文字幕| 国产精品成人久久久| 久久久久亚洲AV片无码下载蜜桃 | 精品国产91久久久久久久a| 久久综合精品国产一区二区三区 | 久久久久亚洲AV综合波多野结衣| 欧美日韩成人精品久久久免费看| 狠狠色婷婷久久综合频道日韩| 日日躁夜夜躁狠狠久久AV| 日本久久久久久中文字幕| 日本WV一本一道久久香蕉| 久久婷婷综合中文字幕| 亚洲伊人久久综合影院| 精品久久久久久无码专区 | 伊人久久大香线蕉av不卡| 好久久免费视频高清| 性做久久久久久免费观看| 精品久久久久久成人AV| 日本精品久久久久影院日本| 日韩精品久久无码人妻中文字幕| 国产精品美女久久久免费| 无码人妻少妇久久中文字幕蜜桃| 久久93精品国产91久久综合| 亚洲国产精品久久久天堂|