龙空技术网

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

小智雅汇 155

前言:

此时同学们对“vc system函数”大致比较着重,咱们都想要分析一些“vc system函数”的相关知识。那么小编在网上网罗了一些关于“vc system函数””的相关内容,希望各位老铁们能喜欢,姐妹们快快来了解一下吧!

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

从封装库和使用库来看,实现封装库的程序员可以称为设计者(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:

-End-

标签: #vc system函数 #net源码封装