转载必须注明原文转自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