类是对数据与函数的封装,所以将一些重复性高的结构代码封装成类,然后通过继承和改写可以进一步提高代码的重用性。
前文的工程都还是全局函数的形式,现在我们来将Win32Lib封装成class。
将前文的Win32Lib工程中复制一份,将目录改名为miniMFC,我们将在此基础上封装我们的类。
Win32Lib工程中,我们仅仅是改造了WinProc()这个窗口处理函数,对WinMain()主函数并没有改造。仔细分析WinMain()函数,发现其本质就做了2件事情:一是创建和显示窗口,二是进行消息循环以响应窗口消息。
我们可以封装一个CMyWnd类,专门负责和窗口创建相关的工作,而在入口函数WinMain()中实例化CMyWnd,并调用其成员函数。
进一步的,可以封装一个CMyApp类,在CMyApp类中定义成员函数InitInstance(),将WinMain()中的代码移到InitInstance()中来,专门负责初始化,定义成员函数run(),来负责程序的运行(消息循环)等应用性的工作。
Win32Lib.h
// 将win32程序封装成类库1// 工程→设置→连接→工程选项:将 /subsystem:console改成windows // Win32Lib.cpp文件#include #include #include "miniMFC.h" CMyApp theApp; //入口函数int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow) // show state{ return 0;} CMyWnd::CMyWnd(){ m_hWnd = NULL; m_hInstance = NULL;} BOOL CMyWnd::Create() // 定义、注册、创建窗口{ WNDCLASS wndcls; wndcls.cbClsExtra = 0; wndcls.cbWndExtra = 0; wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndcls.hCursor = LoadCursor(NULL, IDC_ARROW); wndcls.hIcon = LoadIcon(NULL, IDI_ERROR); wndcls.hInstance = m_hInstance; wndcls.lpfnWndProc = WinProc; wndcls.lpszClassName = (LPTSTR)"ItJob2010"; wndcls.lpszMenuName = NULL; wndcls.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wndcls); m_hWnd = ::CreateWindow(wndcls.lpszClassName, (LPTSTR)"培训中心", WS_OVERLAPPEDWINDOW, 0, 0, 600, 400, NULL, NULL, m_hInstance, NULL); ::SetTimer(m_hWnd, 123, 1000, NULL); if (m_hWnd == NULL) return FALSE; else return TRUE;} BOOL CMyWnd::ShowWindow(){ return ::ShowWindow(m_hWnd, SW_SHOWNORMAL);} BOOL CMyWnd::UpdateWindow(){ return ::UpdateWindow(m_hWnd);} // 主窗口回调函数LRESULT CALLBACK CMyWnd::WinProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ // 如果当前消息是我们关心的、定义在数组中的消息,则处理之 for (int i = 0; i < dim(MessageMaps); i++) { if (wMsg == MessageMaps[i].Code) { FXN iFxn = MessageMaps[i].Fxn; LRESULT lResult = iFxn(hWnd, wMsg, wParam, lParam); if (lResult == 0) return 0; } } // 否则,将消息交给系统去处理 return DefWindowProc(hWnd, wMsg, wParam, lParam);}// 消息响应函数实现// 字符按下LRESULT CMyWnd::OnChar(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ char szChar[20]; sprintf(szChar, "char is %c", (char)wParam); MessageBox(hWnd, (LPTSTR)szChar, (LPTSTR)"OnChar", 0); return 0;} // 鼠标左键按下LRESULT CMyWnd::OnLButtonDown(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ HDC hdc; hdc = GetDC(hWnd); TextOut(hdc, 10, 50, (LPTSTR)"win32封装成类库", strlen("win32封装成类库")); ReleaseDC(hWnd, hdc); return 0;} // 重绘窗口LRESULT CMyWnd::OnPaint(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ //画一个圆 RECT rc; GetClientRect(hWnd, &rc); int iR = min(rc.right - rc.left, rc.bottom - rc.top) / 2; iR = iR * 4 / 5; POINT pt; pt.x = (rc.right + rc.left) / 2; pt.y = (rc.bottom + rc.top) / 2; HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hWnd, &ps); ::Ellipse(hdc, pt.x - iR, pt.y - iR, pt.x + iR, pt.y + iR); MoveToEx(hdc, pt.x, pt.y,(LPPOINT)NULL); LineTo(hdc, pt.x + iR, pt.y); // 显示时间 static char stime[] = "23:59:59"; SYSTEMTIME tm; ::GetLocalTime(&tm); sprintf(stime, "%.2d:%.2d:%.2d", tm.wHour, tm.wMinute, tm.wSecond); ::TextOut(hdc, 10, 10,(LPTSTR)stime, strlen(stime)); TextOut(hdc, 10, 50, (LPTSTR)"请按下左键试一试!", strlen("请按下左键试一试!")); EndPaint(hWnd, &ps); return 0;} // 销毁窗口LRESULT CMyWnd::OnDestroy(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ PostQuitMessage(0); return 0;} // 定时器LRESULT CMyWnd::OnTimer(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ RECT rc; ::GetClientRect(hWnd, &rc); ::InvalidateRect(hWnd, &rc, TRUE); return 0;} CMyApp::CMyApp(){ m_pMainWnd = NULL; if (InitInstance()) Run();} BOOL CMyApp::Run(){ MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return TRUE;} BOOL CMyApp::InitInstance(){ m_pMainWnd = new CMyWnd(); m_pMainWnd->Create(); m_pMainWnd->ShowWindow(); return m_pMainWnd->UpdateWindow();}
Win32Lib.cpp
// 将win32程序封装成类库1// 工程→设置→连接→工程选项:将 /subsystem:console改成windows // Win32Lib.cpp文件#include #include #include "miniMFC.h" CMyApp theApp; //入口函数int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow) // show state{ return 0;} CMyWnd::CMyWnd(){ m_hWnd = NULL; m_hInstance = NULL;} BOOL CMyWnd::Create() // 定义、注册、创建窗口{ WNDCLASS wndcls; wndcls.cbClsExtra = 0; wndcls.cbWndExtra = 0; wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndcls.hCursor = LoadCursor(NULL, IDC_ARROW); wndcls.hIcon = LoadIcon(NULL, IDI_ERROR); wndcls.hInstance = m_hInstance; wndcls.lpfnWndProc = WinProc; wndcls.lpszClassName = (LPTSTR)"ItJob2010"; wndcls.lpszMenuName = NULL; wndcls.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wndcls); m_hWnd = ::CreateWindow(wndcls.lpszClassName, (LPTSTR)"培训中心", WS_OVERLAPPEDWINDOW, 0, 0, 600, 400, NULL, NULL, m_hInstance, NULL); ::SetTimer(m_hWnd, 123, 1000, NULL); if (m_hWnd == NULL) return FALSE; else return TRUE;} BOOL CMyWnd::ShowWindow(){ return ::ShowWindow(m_hWnd, SW_SHOWNORMAL);} BOOL CMyWnd::UpdateWindow(){ return ::UpdateWindow(m_hWnd);} // 主窗口回调函数LRESULT CALLBACK CMyWnd::WinProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ // 如果当前消息是我们关心的、定义在数组中的消息,则处理之 for (int i = 0; i < dim(MessageMaps); i++) { if (wMsg == MessageMaps[i].Code) { FXN iFxn = MessageMaps[i].Fxn; LRESULT lResult = iFxn(hWnd, wMsg, wParam, lParam); if (lResult == 0) return 0; } } // 否则,将消息交给系统去处理 return DefWindowProc(hWnd, wMsg, wParam, lParam);}// 消息响应函数实现// 字符按下LRESULT CMyWnd::OnChar(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ char szChar[20]; sprintf(szChar, "char is %c", (char)wParam); MessageBox(hWnd, (LPTSTR)szChar, (LPTSTR)"OnChar", 0); return 0;} // 鼠标左键按下LRESULT CMyWnd::OnLButtonDown(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ HDC hdc; hdc = GetDC(hWnd); TextOut(hdc, 10, 50, (LPTSTR)"win32封装成类库", strlen("win32封装成类库")); ReleaseDC(hWnd, hdc); return 0;} // 重绘窗口LRESULT CMyWnd::OnPaint(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ //画一个圆 RECT rc; GetClientRect(hWnd, &rc); int iR = min(rc.right - rc.left, rc.bottom - rc.top) / 2; iR = iR * 4 / 5; POINT pt; pt.x = (rc.right + rc.left) / 2; pt.y = (rc.bottom + rc.top) / 2; HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hWnd, &ps); ::Ellipse(hdc, pt.x - iR, pt.y - iR, pt.x + iR, pt.y + iR); MoveToEx(hdc, pt.x, pt.y,(LPPOINT)NULL); LineTo(hdc, pt.x + iR, pt.y); // 显示时间 static char stime[] = "23:59:59"; SYSTEMTIME tm; ::GetLocalTime(&tm); sprintf(stime, "%.2d:%.2d:%.2d", tm.wHour, tm.wMinute, tm.wSecond); ::TextOut(hdc, 10, 10,(LPTSTR)stime, strlen(stime)); TextOut(hdc, 10, 50, (LPTSTR)"请按下左键试一试!", strlen("请按下左键试一试!")); EndPaint(hWnd, &ps); return 0;} // 销毁窗口LRESULT CMyWnd::OnDestroy(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ PostQuitMessage(0); return 0;} // 定时器LRESULT CMyWnd::OnTimer(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ RECT rc; ::GetClientRect(hWnd, &rc); ::InvalidateRect(hWnd, &rc, TRUE); return 0;} CMyApp::CMyApp(){ m_pMainWnd = NULL; if (InitInstance()) Run();} BOOL CMyApp::Run(){ MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return TRUE;} BOOL CMyApp::InitInstance(){ m_pMainWnd = new CMyWnd(); m_pMainWnd->Create(); m_pMainWnd->ShowWindow(); return m_pMainWnd->UpdateWindow();}
以上代码虽有实现将函数封装到类,但需要做为interface的.h文件保持稳定性,库使用者尽量只在.cpp文件中写业务逻辑(主要是消息响应函数)。
也就是将不常用的代码移到.h文件中,将常用的代码移到.cpp文件中去。
Win32Lib.h
// Win32Lib.h: interface for the CMyWnd class.// 相对固定不变的代码#ifndef AFX_MYWIN32LIB_H_#define AFX_MYWIN32LIB_H_ #if _MSC_VER > 1000#pragma once#endif // _MSC_VER > 1000 // 返回元素的个数#define dim(x)(sizeof(x) / sizeof(x[0])) // 定义函数指针typedef LRESULT(*FXN)(HWND, UINT, WPARAM, LPARAM); // 消息映射结构struct tagMESSAGEMAP{ UINT Code; // 消息 FXN Fxn; // 响应函数}; class CMyWnd{public: HINSTANCE m_hInstance; HWND m_hWnd; public: BOOL Create(); BOOL ShowWindow(); BOOL UpdateWindow(); CMyWnd(); virtual ~CMyWnd(){}; // 主窗口回调函数 static LRESULT CALLBACK WinProc(HWND, UINT, WPARAM, LPARAM); // 声明消息响应函数 MY_MESSAGE_DECLARE}; // 消息映射数组宏MY_MESSAGE_MAP class CMyApp {public: CMyWnd* m_pMainWnd; BOOL InitInstance(); BOOL Run(); CMyApp(); virtual ~CMyApp(){};}; // 入口函数int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow) // show state{ return 0;} CMyWnd::CMyWnd(){ m_hWnd = NULL; m_hInstance = NULL;} BOOL CMyWnd::Create(){ WNDCLASS wndcls; wndcls.cbClsExtra = 0; wndcls.cbWndExtra = 0; wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndcls.hCursor = LoadCursor(NULL, IDC_ARROW); wndcls.hIcon = LoadIcon(NULL, IDI_ERROR); wndcls.hInstance = m_hInstance; wndcls.lpfnWndProc = WinProc; wndcls.lpszClassName = (LPTSTR)"ItJob2010"; wndcls.lpszMenuName = NULL; wndcls.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wndcls); m_hWnd = ::CreateWindow(wndcls.lpszClassName, (LPTSTR)"培训中心", WS_OVERLAPPEDWINDOW, 0, 0, 600, 400, NULL, NULL, m_hInstance, NULL); ::SetTimer(m_hWnd, 123, 1000, NULL); if (m_hWnd == NULL) return FALSE; else return TRUE;} BOOL CMyWnd::ShowWindow(){ return ::ShowWindow(m_hWnd, SW_SHOWNORMAL);} BOOL CMyWnd::UpdateWindow(){ return ::UpdateWindow(m_hWnd);} // 主窗口回调函数LRESULT CALLBACK CMyWnd::WinProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ // 如果当前消息是我们关心的、定义在数组中的消息,则处理之 for (int i = 0; i < dim(MessageMaps); i++) { if (wMsg == MessageMaps[i].Code) { FXN iFxn = MessageMaps[i].Fxn; LRESULT lResult = iFxn(hWnd, wMsg, wParam, lParam); if (lResult == 0) return 0; } } // 否则,将消息交给系统去处理 return DefWindowProc(hWnd, wMsg, wParam, lParam);} CMyApp::CMyApp(){ m_pMainWnd = NULL; if (InitInstance()) Run();} BOOL CMyApp::Run(){ MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return TRUE;} BOOL CMyApp::InitInstance(){ m_pMainWnd = new CMyWnd(); m_pMainWnd->Create(); m_pMainWnd->ShowWindow(); return m_pMainWnd->UpdateWindow();} #endif // defined(AFX_MYWIN32LIB_H_)
Win32Lib.cpp
// 将win32程序封装成类库2// 工程→设置→连接→工程选项:将 /subsystem:console改成windows #include #include // 声明消息响应函数声明宏#define MY_MESSAGE_DECLARE static LRESULT OnChar(HWND, UINT, WPARAM, LPARAM); static LRESULT OnLButtonDown(HWND, UINT, WPARAM, LPARAM); static LRESULT OnPaint(HWND, UINT, WPARAM, LPARAM); static LRESULT OnDestroy(HWND, UINT, WPARAM, LPARAM); static LRESULT OnTimer(HWND, UINT, WPARAM, LPARAM); // 消息映射数组定义宏#define MY_MESSAGE_MAP tagMESSAGEMAP MessageMaps[] = { WM_CHAR, CMyWnd::OnChar, WM_LBUTTONDOWN, CMyWnd::OnLButtonDown, WM_PAINT, CMyWnd::OnPaint, WM_DESTROY, CMyWnd::OnDestroy, WM_TIMER, CMyWnd::OnTimer, }; #include "miniMFC.h"CMyApp theApp;// 定义消息响应函数 // 字符按下LRESULT CMyWnd::OnChar(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ char szChar[20]; sprintf(szChar, "char is %c", (char)wParam); MessageBox(hWnd, (LPTSTR)szChar, (LPTSTR)"OnChar", 0); return 0;} // 鼠标左键按下LRESULT CMyWnd::OnLButtonDown(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ HDC hdc; hdc = GetDC(hWnd); TextOut(hdc, 10, 50, (LPTSTR)"封装win32为类库", strlen("封装win32为类库")); ReleaseDC(hWnd, hdc); return 0;} // 重绘窗口LRESULT CMyWnd::OnPaint(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ // 画一个圆 RECT rc; GetClientRect(hWnd, &rc); int iR = min(rc.right - rc.left, rc.bottom - rc.top) / 2; iR = iR * 4 / 5; POINT pt; pt.x = (rc.right + rc.left) / 2; pt.y = (rc.bottom + rc.top) / 2; HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hWnd, &ps); ::Ellipse(hdc, pt.x - iR, pt.y - iR, pt.x + iR, pt.y + iR); MoveToEx(hdc, pt.x, pt.y,(LPPOINT)NULL); LineTo(hdc, pt.x + iR, pt.y); // 显示时间 static char stime[] = "23:59:59"; SYSTEMTIME tm; ::GetLocalTime(&tm); sprintf(stime, "%.2d:%.2d:%.2d", tm.wHour, tm.wMinute, tm.wSecond); ::TextOut(hdc, 10, 10,(LPTSTR)stime, strlen(stime)); TextOut(hdc, 10, 50, (LPTSTR)"请点击左键试试!", strlen("请点击左键试试!")); EndPaint(hWnd, &ps); return 0;} // 销毁窗口LRESULT CMyWnd::OnDestroy(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ PostQuitMessage(0); return 0;} // 定时器LRESULT CMyWnd::OnTimer(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){ RECT rc; ::GetClientRect(hWnd, &rc); ::InvalidateRect(hWnd, &rc, TRUE); return 0;}
应当说,Win32Lib.h是可以进一步将其中的实现函数放到一个新的.cpp文件中去的,这样真正实现interface与implementation的分享。
上面代码其中使用MY_MESSAGE_DECLARE和MY_MESSAGE_MAP这2个宏将原本在Win32Class.h文件中的声明消息响应函数和定义消息响应函数数组的代码也移到了Win32MFC.cpp文件中。这样,.h文件中都是一些结构性和固定模式的不需要经常改动的内容了,而.cpp文件中的内容就全是需要经常改动、与应用相关的内容,如要再添加新的消息及其响应代码,只需要照葫芦画瓢即可,真正实现了将主要精力放在业务上而不是程序的框架上的目标。
我们知道,MFC的复杂之处在于,既有C又有C++,既有win32API,又有MFC对win32API的封装,再加上没有通过虚函数的多态(主要是考虑到使用虚函数会造成过大的系统开销),而是通过宏进行的消息映射,所以MFC复杂晦涩。其实,上面2个宏的实现思想就是MFC中消息映射的核心思想,通过实现我们自己的类库的过程,我们对于代码封装即库界面与实现的分工便会有更深刻的理解了。
我们看C:Program FilesMicrosoft Visual StudioVC98MFCIncludeAFXWIN.H内的源代码:
1 DECLARE_MESSAGE_MAP()宏
#define DECLARE_MESSAGE_MAP() private: static const AFX_MSGMAP_ENTRY _messageEntries[]; protected: static AFX_DATA const AFX_MSGMAP messageMap; static const AFX_MSGMAP* PASCAL _GetBaseMessageMap(); virtual const AFX_MSGMAP* GetMessageMap() const;
_messageEntries[]的类型是结构体AFX_MSGMAP_ENTRY:
struct AFX_MSGMAP_ENTRY // {UINT nMessage; // windows messageUINT nCode; // control code or WM_NOTIFY codeUINT nID; // control ID (or 0 for windows messages)UINT nLastID; // used for entries specifying a range of control id'sUINT nSig; // signature type (action) or pointer to message #AFX_PMSG pfn; // routine to call (or special value)};
相对于上面自定义消息映射结构体只定义了2个成员,这里有6个成员。
2 BEGIN_MESSAGE_MAP和END_MESSAGE_MAP定义
作用是向类中添加消息映射必要的结构体和函数声明
BEGIN_MESSAGE_MAP(theClass, baseClass)//{{AFX_MSG_MAP(theClass)ON_BN_CLICKED(id, memberFxn)//}}AFX_MSG_MAPEND_MESSAGE_MAP()
对应的宏:
END_MESSAGE_MAP和BEGIN_MESSAGE_MAP是成对出现的首先看定义#define BEGIN_MESSAGE_MAP(theClass, baseClass) / const AFX_MSGMAP* theClass::GetMessageMap() const / { return &theClass::messageMap; } / AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = / { &baseClass::messageMap, &theClass::_messageEntries[0] }; / AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = / { /#define END_MESSAGE_MAP() / {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } / }; /
DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP、END_MESSAGE_MAP以及ON_COMMAND、ON_MESSAGE等宏最终作用的结果是在你的类中生成了一个名为_messageEntries[]的数组,该数组中将填入每个你关心的消息和此消息对应的处理函数。应用程序框架生成的WindowProc接收到一个消息后,会按照一定的原则轮询个各类(CView、CDocument、CFrameWnd、CWinApp)的_messageEntries[]数组,检查该数组中有没有对应的消息,如果有,就调用相应的响应函数;如果没有,就换下一个类继续检查。当所有的有关的类都被检查完后仍未发现响应函数时,便将此消息丢给DefWindowProc处理。
MFC把窗口函数一致设为AfxWndProc()。
MFC2.5 的CWinApp::Run 调用PumpMessage,后者又调用::DispatchMessage,把消息源源推往AfxWndProc,最后流向pWnd->WindowProc 去。
事实上,MFC 4.x 利用hook,把看似无关的动作全牵联起来了。所谓hook,是Windows程序设计中的一种高阶技术。通常消息都是停留在消息队列中等待被所隶属之窗口抓取,如果你设立hook,就可以更早一步抓取消息,并且可以抓取不属于你的消息,送往你设定的一个所谓「滤网函数(filter)」。
ref
https://blog.csdn.net/shimazhuge/article/details/8279936
https://www.bilibili.com/video/BV1LK4y1v7o1?p=2
-End-