Windows消息机制
通过VS创建一个Win32的项目
// Lesson07_Win32Demo.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "Lesson07_Win32Demo.h"
#define MAX_LOADSTRING 100
// 全局变量:
HINSTANCE hInst; // 当前实例
WCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此放置代码。
// 初始化全局字符串
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_LESSON07_WIN32DEMO, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_LESSON07_WIN32DEMO));
MSG msg;
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
//
// 函数: MyRegisterClass()
//
// 目的: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_LESSON07_WIN32DEMO));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_LESSON07_WIN32DEMO);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目的: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 将实例句柄存储在全局变量中
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目的: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 在此处添加使用 hdc 的任何绘图代码...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}HINSTANCE
HINSTANCE是"实例内核对象", 是指的进程.WinMain函数
该函数为Win32程序的入口函数.
和main函数一样, 该函数是被动调用的.
在VS生成的Win32项目中, 会有三种 WinMain 函数:
- tWinMain
- WinMain
- wWinMain
显然, tWinMain是一个可变版本, WinMain是ANSI版本, wWinMain是Unicode版本
它们接受的参数可以看出区别.
当前我们的工程是UNICODE编码.
WinMain函数为什么要传递hInstance
因为进程是由操作系统创建的, 所以要传递过来
- main函数是C语言的入口函数
- C语言出现的时候还没操作系统, 所以在设计上没有进行传递hInstance
- 可以通过
GetInstanceAPI来获取hInstance
WinMain中的hPrevInstance
hPrevInstance指的是 启动我的进程是谁
WinMain中的其他参数
- 后两个参数, 一个是附加参数, 一个是显示方式
- 可以通过
快捷方式的方式来传递 - 可以通过cmd来传递
调用约定
我们可以看到, 在 wWinMain 之前, 有一个 APIENTRY 关键字.
它就是我们的调用约定
具体定义如下:
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall- 我们知道, 函数的调用, 在传递参数的时候, 是通过
栈来传递的. - 当我们函数调用完成之后, 被调用的函数的
栈帧会被清理. - 但是我们调用函数之前, 压入的参数和下一步执行地址, 怎么进行清理呢?
- 调用约定就是来规定栈清理的问题, 我们称之为
平栈操作 __stdcall- 是Pascal程序的缺省调用方式
- 参数采用从右到左的压栈方式
- 由
调用方完成压栈操作 - 由
被调用方自身在返回前清空堆栈来清理 - Win32 API默认调用约定就是 __stdcall
- 返回通过EAX.
__cdecl- C/C++的缺省调用方式
- 参数采用从右到左的压栈方式
- 由
调用方完成压栈操作 传送参数的内存栈由调用者维护, 也就是调用方负责平栈操作- _cedcl约定的函数只能被C/C++调用
- 每一个调用它的函数都包含清空堆栈的代码
- 像printf这样变参数的函数必须用这种规则
- 返回通过EAX.
__fastcall- 特点就是快
是寄存器传参
- 实际上, 它用ECX和EDX传送前两个双字(DWORD)或更小的参数
- 剩下的参数仍旧
自右向左压栈传送 被调用的函数在返回前清理传送参数的内存栈
通过寄存器传送的两个参数是从左向右的
- 即第一个参数进ECX, 第2个进EDX
- 其他参数是从右向左的入stack
- 返回通过EAX.
__pascal- 从左向右传递参数
- 通过EAX返回
- 堆栈由被调用者清除
__thiscall- 仅仅应用于"C++"成员函数
- this指针存放于CX寄存器
- 参数从右到左压栈
- thiscall不是关键词, 因此不能被程序员指定
窗口创建过程
一个窗口被创建, 需要以下几个步骤
- 注册窗口类
- 通过CreateWindow来进行窗口的实例创建
注册窗口类
- 通过
WNDCLASSEX结构体来指定窗口类的样式 - WNDCLASSEX 是一个窗口风格式样的设置结构体
通过
RegisterClassExAPI来注册窗口风格- 相当于指定一个窗口模板
Windows中的结构体, 如果出现了 size, 一定要多加注意.
cbSize
- 该参数为 sizeof(WNDCLASSEX)
- 一定要确保在调用 GetClassInfoEx 前设置该值
style
- 窗口类的样式
- 它是
Class Styles中的取值, 可以组合
*lpfnWndProc
- 窗口处理过程的函数指针
- 必须使用
CallWindowProc方法去调用窗口过程 - 更多详情请看
WindowProc
cbClsExtra
- 默认为 0
- 附加的窗口类数据
cbWndExtra
- 默认为 0
- 窗口实例之后要分配的额外字节数
- 如果应用程序使用WNDCLASSEX注册在资源文件中使用CLASS指令创建的对话框, 则必须将此成员设置为DLGWINDOWEXTRA
hInstance
- 包含该类的窗口过程的实例的句柄
hIcon
- 窗口类图标的句柄
- 该成员必须是图标资源的句柄
- 如果设置为NULL, 系统将提供默认图标
hCursor
- 窗口类光标的句柄
- 该成员必须是光标资源的句柄
- 如果为NULL, 则只要鼠标移动到应用程序的窗口中, 应用程序就必须显式设置光标形状
hbrBackground
- 窗口类背景画笔的句柄
- 该成员可以是用于画刷背景的画笔的句柄
也可以是一个颜色值
- 颜色值必须是以下标准系统颜色之一
- 如果给出了颜色值,则必须将其转换为以下HBRUSH类型之一
- COLOR_ACTIVEBORDER
- COLOR_ACTIVECAPTION
- COLOR_APPWORKSPACE
- COLOR_BACKGROUND
- COLOR_BTNFACE
- COLOR_BTNSHADOW
- COLOR_BTNTEXT
- COLOR_CAPTIONTEXT
- COLOR_GRAYTEXT
- COLOR_HIGHLIGHT
- COLOR_HIGHLIGHTTEXT
- COLOR_INACTIVEBORDER
- COLOR_INACTIVECAPTION
- COLOR_MENU
- COLOR_MENUTEXT
- COLOR_SCROLLBAR
- COLOR_WINDOW
- COLOR_WINDOWFRAME
- COLOR_WINDOWTEXT
- 当使用
UnregisterClass取消注册类时, 系统会自动删除类背景画笔. - 应用程序不应该删除这些画笔
当为NULL时, 应用程序必须自己绘制自己的背景
- 要确定是否必须绘制背景, 应用程序可以处理WM_ERASEBKGND消息
- 或测试由BeginPaint函数填充的PAINTSTRUCT结构的fErase成员
lpszMenuName
- 指向一个以 NULL 结束的字符串, 指定类菜单的资源名称, 名称显示在资源文件中
- 如果使用整数来标识菜单, 请使用MAKEINTRESOURCE宏
- 如果此成员为NULL, 属于此类的Windows没有默认菜单
lpszClassName
- 指向 NULL 终止字符串的指针, 或着是 ATOM(原子)
- 如果此参数是一个 ATOM(原子), 它必须是由先前调用RegisterClass或RegisterClassEx函数创建的类原子
- ATOM(原子)必须在lpszClassName的低位字中; 高阶字必须为零
- 如果lpszClassName是一个字符串, 它将指定窗口类名称
- 类名可以是使用RegisterClass或RegisterClassEx注册的任何名称, 也可以是任何预定义的控制类名称
- lpszClassName的最大长度为256.
- 如果lpszClassName大于最大长度, 则RegisterClassEx函数将失败
hIconSm
- 与窗口类相关联的小图标的句柄
- 如果为NULL, 系统将搜索由hIcon成员指定的图标资源, 以获取适当大小的图标作为小图标
CreateWindow
真正的进行窗口创建.
函数详情请看 MSDN.aspx)
Windows消息机制
消息响应机制是在
Win32子系统中实现的.- 为什么不在内核中实现?
- 因为内核越简单越好
- 如果在内核中实现, 则会使内核占用大量资源
- 可能会引起不稳定
- 硬件在有了动作的时候, 比如点击鼠标, 会写入到一个
公共缓冲区中 - Win32子系统中不断的扫描这个区域
- 当看到有一个
鼠标动作的时候, 就会翻译成消息. Win32会帮每个应用程序建立一个消息队列- 在翻译消息完成后, 就会进行
派发, 放到应用程序的消息队列里 - 派发的消息一定是该应用程序的消息
MSG结构体
hwnd
- 窗口句柄, 只取该窗口的消息
message
- 消息值, UINT类型
- 我们称之为
消息数值化
- wParam
lParam
- WPARAM LPARAM 作为附加参数,
经常传递指针 - 它形成了一套扩展机制
- 比如, 同样的WM_KEYDOWN消息, 可以通过这两个参数判定组合键
- WPARAM LPARAM 作为附加参数,
time
- 时间, 消息发生时间
pt
- 消息产生时,
鼠标指针所在的位置
- 消息产生时,
通过
GetMessageAPI来取当前应用程序的消息队列的消息
通过DispatchMessageAPI来触发WndProc回调
窗口过程(WndProc)
在WndProc中处理窗口的各种消息
消息分类:
- 窗口消息
- 命令消息
- 控件通知消息
例子可见最上方的 wM_COMMAND 消息处理.
MFC与Win32
- 由于Win32的消息太多, 微软为了方便我们使用, 封装了MFC框架.
- MFC其实就是将
消息和方法封装成了类
在MFC中, 调用的所有函数都是MFC的函数
如果想调用Win32的函数, 需要加 :: 来进行全局范围的访问.
未完待续...
如有错误,请提出指正!谢谢.
本文由 花心胡萝卜 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: 2017-05-11 at 03:12 am