一个简单的Win32程序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hhhuang1991/article/details/80111398

一、Windows消息机制

1、 消息是什么

Windows程序的运行是依靠外部的事件来驱动。换句话说,程序不断等待,等待任何可能的输入,然后做出判断,再做适当的处理。前面的“输入”是指操作系统发送给程序的消息。消息,其实就是系统内设的一种数据结构。

typedef struct MSG
{
     HWND hwnd;//hwnd 是窗口的句柄,这个参数将决定由哪个窗口过程函数对消息进行处理 
     UINT message;      //message是一个消息常量,用来表示消息的类型 
     WPARAM wParam;     //32 位的附加信息,具体表示什么内容,要视消息的类型而定 
     LPARAM lParam;     //32 位的附加信息,具体表示什么内容,要视消息的类型而定 
     DWORD time;       //time 是消息发送的时间 
     POINT pt;         //消息发送时鼠标所在的位置 
}

2、 消息类型的分类

消息类型可以分为两大类:系统定义消息和用户自定义消息。
从上面的消息定义可以看出,消息类型其实就是一个UINT类型的变量。系统定义消息值的范围是:0x0000-0x03ff,用户自定义消息值的范围是:0x0400-0x07ff,为了便于使用,系统定义了一个宏WM_USER来表示用户自定义消息的起始值,#define WM_USER 0x0400

系统定义消息
系统定义消息分为:窗口消息、命令消息、控件通知消息。

  • 窗口消息:即与窗口的内部运作有关的消息,如创建窗口,绘制窗口,销毁窗口等
    可以是一般的窗口,也可以是MainFrame,Dialog,控件等。 如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL等
  • 命令消息:当用户从菜单选中一个命令项目、按下一个快捷键、点击工具栏上的一个按钮或者点击控件都将发送WM_COMMAND命令消息。通过消息结构中的wParam和lParam成员就能清楚得知道消息的来源。
    LOWORD(wParam):代表菜单ID、或控件ID,或快捷键ID;
    HIWORD(wParam):表示通知码,当消息是从菜单发出时,则这个值为0,当消息是从快捷键发出时,这个值为1,当消息是从控件发出时,这个值为通知码,比如按钮的通知码:BN_CLICKED, BN_DBLCLK等;
    lParam:当消息从菜单和快捷键发出时,这个值为0,当从控件发出时,为控件的句柄。
    这里写图片描述
  • 通知消息:随着控件的种类越来越多,越来越复杂(如列表控件、树控件等),仅仅将wParam,lParam将视为一个32位无符号整数,已经装不下太多信息了。 为了给父窗口发送更多的信息,微软定义了一个新的WM_NOTIFY消息来扩展WM_COMMAND消息。 WM_NOTIFY消息仍然使用MSG消息结构,只是此时wParam为控件ID,lParam为一个NMHDR指针,不同的控件可以按照规则对NMHDR进行扩充,因此WM_NOTIFY消息传送的信息量可以相当的大。

3、 队列消息和非队列消息

前文有提起,应用程序会不断等待操作系统消息的输入。其实就是,程序中有一个获取消息的循环代码,会不断的从操作系统中获取消息。

  • 队列消息
    一般,程序都是从消息队列中获取消息。消息会先保存在消息队列中,消息循环会从此队列中取出消息并分发到各窗口处理 如:WM_PAINT,WM_TIMER,WM_CREATE,WM_QUIT,以及鼠标,键盘消息等。其中,WM_PAINT,WM_TIMER只有在队列中没有其他消息的时候才会被处理,WM_PAINT消息还会被合并以提高效率。其他所有消息以先进先出(FIFO)的方式被处理。
    系统中维护着一个全局的系统消息队列,还会为每一个UI线程维护一个UI线程消息队列。当系统消息队列中存在消息时,系统会根据消息所属的UI线程,分发到应用程序对应的UI线程消息队列中去。
  • 非队列消息
    但是还有一部分消息会绕过消息队列,直接发送到窗口过程进行处理 。如WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR,WM_WINDOWPOSCHANGED
    这里写图片描述

二、简单的Win32程序实现

Windows程序设计中,消息机制是最重要的部分,至于窗口的产生和显示,有专门的Windows API负责,比较简单。

#include <windows.h>
#include "resource.h"

#define LDS_MAXLENGHT 100
HINSTANCE hInst;    //应用程序实例
WCHAR lpszTitle[LDS_MAXLENGHT];   //窗口标题
WCHAR lpszWndClass[LDS_MAXLENGHT];  //窗口类名称
BOOL InitApplication(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK About(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


int APIENTRY WinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPreInstance,
    _In_ LPSTR lpCmdLine,
    _In_ int nCmdShow)
{

    LoadString(hInstance, IDS_APPTITLE, lpszTitle, LDS_MAXLENGHT);
    LoadString(hInstance, IDS_WNDCLASS, lpszWndClass, LDS_MAXLENGHT);

    InitApplication(hInstance);

    if (!InitInstance(hInstance, nCmdShow))
        return FALSE;

    MSG msg;

    HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDA_WINHELLO));

    while (GetMessage(&msg, nullptr, 0, 0))
    {
        /**
        * TranslateAccelerator函数
        */
        if (!TranslateAccelerator(msg.hwnd, hAccel, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (msg.wParam);  //传回PostQuitMessage的参数
}

/**
* @brief 注册窗口类
*/
BOOL InitApplication(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;

    wcex.cbSize = sizeof WNDCLASSEX;
    wcex.style = CS_VREDRAW | CS_HREDRAW; //CS_HREDRAW当窗口水平方向的宽度变化时重绘整个窗口.CS_VREDRAW 当窗口垂直方向的宽度变化时重绘整个窗口.
    wcex.hInstance = hInstance;  //应用程序实例
    wcex.cbClsExtra = 0; 
    wcex.cbWndExtra = 0; 
    wcex.lpfnWndProc = WndProc;   //窗口过程函数
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINHELLO)); //图标
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);  //光标,LoadCursor第一个参数为NULL,表示采用系统默认光标类型
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);  //背景颜色
    wcex.lpszMenuName = MAKEINTRESOURCE(IDR_WINHELLO);  //菜单名称
    wcex.lpszClassName = lpszWndClass;  //窗口类名称
    wcex.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINHELLO));  

    return (RegisterClassEx(&wcex));
}

/**
* @brief 创建窗口
*/
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance;

    HWND hWnd = CreateWindow(
        lpszWndClass, lpszTitle, 
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        nullptr, 
        nullptr, 
        hInstance, 
        nullptr
    );

    if (!hWnd)
        return FALSE;

    ShowWindow(hWnd, nCmdShow);

    UpdateWindow(hWnd);

    return TRUE;
}
/**
* @brief 主窗口函数
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
    {
        //LOWORD(wParam)表示ID
        WORD wId = LOWORD(wParam);
        WORD wHi = HIWORD(wParam);
        switch (wId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUT), 
                hWnd, About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        }
        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0; 
}
/**
* @brief 对话框函数,类似窗口函数
*/
INT_PTR CALLBACK About(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;   //返回TRUE表示消息已处理
    case WM_COMMAND:
        {
            if (LOWORD(wParam) == IDOK ||
                LOWORD(wParam) == IDCANCEL)
            {
                EndDialog(hWnd, (INT_PTR)TRUE);
                return (INT_PTR)TRUE;
            }
            break;
        }
    }
    return (INT_PTR)FALSE;  //返回FALSE表示消息未处理
}

解读:
1. 上面的代码,是一个完整的、最简单的Windows应用程序。其中涉及到的一些资源,菜单、字符串、图标、对话框、加速键等都是采用VS2015中的资源编辑器编辑,如下图所示,
这里写图片描述
2. 程序的起始和结束

a. 在注册窗口类后,程序调用CreateWindow,为程序建立了一个窗口,作为程序的主体界面。CreateWindow产生窗口之后,会送出WM_CREATE直接给窗口函数,后者于是可以在此时做些初始化操作;
b. 在程序运行的过程中,不断以GetMessage从消息队列中抓取消息。如果这个消息是WM_QUIT,GetMessage会传回0而结束while循环,进而结束整个程序;
c. DispatchMessage 通过Windows系统,把消息分派至窗口函数。消息将在该出被判别并处理;
d. 程序不断的进行b和c的动作;
e. 当使用者按下系统菜单中的Close命令项时,系统送出WM_CLOSE。通常程序的窗口函数不会拦截此消息,于是DefWindowProc处理它;
f. DefWindowProc收到WM_CLOSE后,调用DestroyWindow把窗口清除。DestroyWindow本身又会送出WM_DESTROY消息;
g. 程序对WM_DESTROY的标准反应是调用PostQuitMessage;
h. PostQuitMessage 没有什么其他操作,就只送出WM_QUIT消息,准备让消息循环中的GetMessage取得,如步骤b,结束消息循环。

如果在窗口函数中拦截WM_DESTROY,但是不调用PostQuitMessage(0),在选择Close后,就会出现窗口消失了,但是应用程序本身并没有结束,因为消息循环没有结束。

3.GetMessage和PeekMessage的区别?

  • GetMessage的主要功能是从消息队列中“取出”消息,消息被取出以后,就从消息队列中将其删除;而PeekMessage的主要功能是“窥视”消息,如果有消息,就返回true,否则返回false。也可以使用PeekMessage从消息队列中取出消息,这要用到它的一个参数(UINT wRemoveMsg),如果设置为PM_REMOVE,消息则被取出并从消息队列中删除;如果设置为PM_NOREMOVE,消息就不会从消息队列中取出。
  • 如果GetMessage从消息队列中取不到消息,则线程就会被操作系统挂起,等到OS重新调度该线程时,两者的性质不同:使用GetMessage线程仍会被挂起,使用PeekMessage线程会得到CPU的控制权,运行一段时间。
  • GetMessage每次都会等待消息,直到取到消息才返回;而PeekMessage只是查询消息队列,没有消息就立即返回,从返回值判断是否取到了消息。

猜你喜欢

转载自blog.csdn.net/hhhuang1991/article/details/80111398