VC|将win32封装成函数库:理解封装思想和提高代码通用性思路

VC|将win32封装成函数库:理解封装思想和提高代码通用性思路//Windows项目要使用Windows子系统, 而不是Console, 可以这样设置:// > > 选择“Link”属

大家好,欢迎来到IT知识分享网。

相同类别的程序总是有固定的结构(或框架),这些固定结构的代码基本都差不多,如果每次编写这样相同类别的程序,这些固定结构的代码都要重写一遍,这是程序员所不希望看到的。为此,将一些固定结构和使用频率较高的代码封装起来,这就是库(或框架)。

从封装库和使用库来看,实现封装库的程序员可以称为设计者(Implementer),使用库的可以称为使用者(Client).设计者一般将库写成两部分,接口部分(interface,写在.h文件中)和实现部分(implementation,写在.c文件中)。使用者只需引入接口,便可调用库中的函数或类,而在另外的文件中编写变动的业务逻辑代码。所以说,封装成库的代码,相当来说是比较固定的。

我们来看一个hello world的win32 API程序:

#include<windows.h> //Windows项目要使用Windows子系统, 而不是Console, 可以这样设置: //[Project] --> [Settings] --> 选择"Link"属性页, //在Project Options中将/subsystem:console改成/subsystem:windows // 窗口过程函数 LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); // 入口函数 int WINAPI WinMain(HINSTANCE hInstance, // 当前实例句柄 HINSTANCE hPreInstance,// 前一实例句柄 LPSTR lpCmdLine, // 指向命令行参数的指针 int nCmdShow) // 窗口的现实显示状态 { // 读取一个系统预设的光标 HCURSOR hCur=LoadCursorFromFile("C://WINDOWS//Cursors//dinosaur.ani"); // 定义一个窗口类 WNDCLASS wndclass; wndclass.style=CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc=WndProc; wndclass.cbClsExtra=0; wndclass.cbWndExtra=0; wndclass.hInstance=hInstance; wndclass.hIcon=NULL; wndclass.hCursor=hCur; wndclass.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH); wndclass.lpszMenuName=NULL; wndclass.lpszClassName="winDemo"; // 类名 // 注册窗口类 if(!RegisterClass(&wndclass)){ MessageBox(NULL,"fail to create winDemo","error",0); return 0; } // 创建这个窗口类的实例 HWND hwnd=CreateWindow("winDemo", "winDemo", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); // 显示和更新窗口 ShowWindow(hwnd,nCmdShow); UpdateWindow(hwnd); MSG msg; // 消息循环 while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); // 转换某些键盘消息 DispatchMessage(&msg); // 发送消息给窗口过程函数 } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) { // 处理消息 switch(message) { case WM_CREATE: MessageBox(NULL,"程序运行","消息",0); return 0; case WM_LBUTTONDOWN: MessageBox(NULL,"左键单击","消息",0); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd,message,wParam,lParam); }

我们发现,上述程序将相对固定的结构代码和业务逻辑代码(主要是一些消息响应函数)的代码是杂糅在一起的,模块化,结构性不强。

回调函数WinProc()中的case语句对应一段处理某个消息的代码可以抽离出来,封装成函数,然后在case语句中调用。这样模块性、结构性更强:

// 首先我们将这些switch-case语句中的代码转换成函数的形式。为了统一, // 我们使这些函数都具有一样的函数原型。即消息响应函数声明: LRESULT OnChar(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT OnLButtonDown (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT OnPaint (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT OnDestroy (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); // 转换后的部分代码如下: switch (uMsg) { case WM_CHAR: OnChar(hWnd, wMsg, wParam, lParam); break; case WM_LBUTTONDOWN: OnLButtonDown (hWnd, wMsg, wParam, lParam); break; case WM_PAINT: OnPaint (hWnd, wMsg, wParam, lParam); break; case WM_DESTROY: OnDestroy (hWnd, wMsg, wParam, lParam); break; default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } …… // 消息响应函数定义

转换后的代码就更加具有规律性了,如下表所示:

 消息(code) 消息响应函数(Fxn) WM_CHAR OnChar(hWnd, wMsg, wParam, lParam) WM_LBUTTONDOWN OnLButtonDown (hWnd, wMsg, wParam, lParam) WM_PAINT OnPaint (hWnd, wMsg, wParam, lParam) WM_DESTROY OnDestroy (hWnd, wMsg, wParam, lParam)

是否可以将上面的switch结构给固定下来?

可以的,用一个结构体封装消息、与消息响应函数指针,再定义一个结构体数组、在一个for循环中遍历这个结构体数组即可。

// 具有相同原型的一系列函数,可以使用typedef语句将他们定义成统一的函数指针形式 typedef LRESULT (*FXN)( HWND, UINT, WPARAM, LPARAM); // 再定义一个返回元素个数的宏,后面的代码会用到 #define dim(x) (sizeof(x) / sizeof(x[0])) // 然后定义一个结构,用于表示上述的表格中的2类数据 struct tagMESSAGEMAP { UINT Code; // 消息 FXN Fxn; // 响应函数 }; // 再用该结构类型定义一个消息映射数组MessageMaps,并赋初值 tagMESSAGEMAP MessageMaps [] = { WM_CHAR, OnChar, WM_LBUTTONDOWN, OnLButtonDown, WM_PAINT, OnPaint, WM_DESTROY, OnDestroy, }; // 将WinProc函数中的switch-case语句改造成for循环语句 LRESULT CALLBACK 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); }

经过上面的改造之后,我们以后再要添加新的消息和消息响应函数,就只需要声明消息响应函数,在数组MessageMaps[]中添加相应的消息代码,实现相应的消息响应函数就可以了。这样就将我们的精力真正转移到了我们所关心的业务上面来了,而再也不用去关心程序的结构了。

改造后的代码如下:

// 将win32程序封装成库 // 工程→设置→连接→工程选项:将 /subsystem:console改成windows /* 库为了方便以后的开发,将大量固定的、重复的、有规律的代码包装起来(成为框架), 供以后开发时直接调用,而不用再去关心和重写这部分千篇一律的程序结构的代码了。 实现程序的模块化,并实现接口与实现的分离。 将业务逻辑代码(主要是消息响应函数)抽离出来,程序员只需在这一部分增减代码。 第1次改写:消息回调函数中的switch结构,将case分支的具体代码抽象成函数,case分支 中直接调用函数,改写后逻辑更清晰了。但增加消息响应函数时还需要在这一 部分中去做修改,需要考虑将这一部分固定下来。 第2次改写:将switch结构改成动态的for循环(能固定下来),先定义消息映射结构和 消息映射数组,在for循环中循环这个数组即可。这样改写后,只需在下面 备注的①、②、③中分别声明消息响应函数、在消息映射数组中增加消息映射、 然后定义消息响应函数即可。(其它部分固定下来了) */ #include <windows.h> #include <stdio.h> #define dim(x)(sizeof(x) / sizeof(x[0])) // 返回数组元素的个数 typedef LRESULT(*FXN)(HWND, UINT, WPARAM, LPARAM); // 定义函数指针 struct tagMESSAGEMAP // 消息映射结构 { UINT Code; // 消息 FXN Fxn; // 响应函数指针 }; LRESULT CALLBACK WinProc(HWND, UINT, WPARAM, LPARAM);// 主窗口回调函数声明 // 声明消息响应函数声明------------------------------ ① LRESULT OnChar(HWND, UINT, WPARAM, LPARAM); LRESULT OnLButtonDown(HWND, UINT, WPARAM, LPARAM); LRESULT OnPaint(HWND, UINT, WPARAM, LPARAM); LRESULT OnDestroy(HWND, UINT, WPARAM, LPARAM); LRESULT OnTimer(HWND, UINT, WPARAM, LPARAM); // 消息映射数组-------------------------------------- ② tagMESSAGEMAP MessageMaps[] = { WM_CHAR, OnChar, WM_LBUTTONDOWN, OnLButtonDown, WM_PAINT, OnPaint, WM_DESTROY, OnDestroy, WM_TIMER, OnTimer, }; // 入口函数 int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow) // show state { 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 = hInstance; wndcls.lpfnWndProc = WinProc; wndcls.lpszClassName = TEXT("ItJob2010"); wndcls.lpszMenuName = NULL; wndcls.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wndcls); HWND hWnd; hWnd = ::CreateWindow(wndcls.lpszClassName, TEXT("将win32封装成库"), WS_OVERLAPPEDWINDOW, 0, 0, 600, 400, NULL, NULL, hInstance, NULL); ShowWindow(hWnd, SW_SHOWNORMAL); UpdateWindow(hWnd); ::SetTimer(hWnd, 123, 1000, NULL); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } // 主窗口回调函数 LRESULT CALLBACK 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 OnChar(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { char szChar[20]; sprintf(szChar, "char is %c", (char)wParam); MessageBox(hWnd,LPTSTR(szChar), TEXT("OnChar"), 0); return 0; } // 鼠标左键按下 LRESULT OnLButtonDown(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { HDC hdc; hdc = GetDC(hWnd); TextOut(hdc, 10, 50, TEXT("将win32封装成库"), strlen("将win32封装成库")); ReleaseDC(hWnd, hdc); return 0; } // 重绘窗口 LRESULT 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, TEXT("试试左键按下"), strlen("试试左键按下")); EndPaint(hWnd, &ps); return 0; } // 销毁窗口 LRESULT OnDestroy(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { PostQuitMessage(0); return 0; } // 定时器 LRESULT OnTimer(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { RECT rc; ::GetClientRect(hWnd, &rc); ::InvalidateRect(hWnd, &rc, TRUE); return 0; }

上面只是模块性、结构性相对强了一些。还可以将相同固定的代码分配到.h和.c文件中。而业务逻辑代码可以写在另外的.c文件中。

另外,还可以用C++将上述的代码封装成类,程序员只需继承或重写相关类,然后写消息响应函数即可。详情请见后续文章:《VC|将win32封装成类库:理解封装思想和提高代码通用性思路》

ref:

https://www.bilibili.com/video/BV1LK4y1v7o1

https://blog.csdn.net/shimazhuge/article/details/

-End-

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/83488.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信