Windows窗口程序详解

今天来给大家详细剖析一下Windows的消息机制

一、什么是消息机制

首先消息机制是Windows上面进程之间通信的一种方式,除此之外还包括共享内存,管道,socket等等进程之间的通信方式,当然socket还可以实现远程进程之间的通信,这里就不再赘述。

1、动作

动作就是指用户做出了什么样的行为,当然也可以是系统做出的,比如用户点击了鼠标左键,或者敲击了键盘上面的按键"k",这都是一些具体的动作,如果有基础的朋友应该知道不论是点击鼠标还是按下键盘都会触发一个外部中断,这将使得CPU变态,从用户态转为内核态,进入操作系统的内核区执行中断处理的代码,当然其中有一步操作就是记录下来用户所执行的操作。

2、消息

消息就是把动作有关的信息封装到一个结构体MSG当中,这个MSG就是消息。

官方定义:

The message structure contains message information from a threads' message queue.

 

消息结构体定义: 
	typedef struct tagMSG {		
	  HWND   hwnd;        //发送给哪个窗口
	  UINT   message; 	  //消息的类型(按下鼠标左键/按下键盘"k")
	  WPARAM wParam; 	  //对message的进一步说明
	  LPARAM lParam; 	  //对message的进一步说明
	  DWORD  time; 		  //事件被触发的时间
	  POINT  pt; 		  //动作发生的位置
	} MSG, *PMSG; 		
参数详解:

HWND   hwnd;          //发送给哪个窗口
UINT   message;       //消息的类型(按下鼠标左键/按下键盘"k")
WPARAM wParam;   //对message的进一步说明
LPARAM lParam;      //对message的进一步说明
DWORD  time;          //事件被触发的时间
POINT  pt;                 //动作发生的位置 

二、消息机制的原理

1、消息机制的完整过程:

通过点击鼠标左键,触发了一个中断,产生了一个消息 》 Windows将这个消息保存在系统队列当中  》 操作系统将系统队列当中的消息MSG挂到应用进程的消息队列(位于内核区) 》应用进程通过消息循环来取出消息,如果是自己关心的就去处理,如果不是,就交给操作系统来处理 

2、Windows窗口程序创建的全过程

1)第一步写出特定格式的窗口程序的主函数WinMain,其实就是C语言当中的main

int APIENTRY WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR     lpCmdLine,
	int       nCmdShow)
{
	// TODO: Place code here.
	hAppInstance = hInstance;

    return 0;

}

2)注册窗口类

因为Windows系统默认只认识系统定义好的类,比如按钮,对话框之类的,如果我们需要自定义窗口就需要有自定义的类,并通过RegisterClass注册类来让系统识别,这里需要注意:

①注册类的以下4个带注释的是必须要填写的属性

②希望读者自己去逆向分析一下,为什么这里写的是WNDCLASS wndclass={0};直接写WNDCLASS wndclass;可不可以?这两种写法有什么区别?

//窗口的类名
	PSTR className = (PCHAR)"My First Window";

	// 创建窗口类的对象 
	WNDCLASS wndclass = { 0 };						//一定要先将所有值赋值
	wndclass.hbrBackground = (HBRUSH)COLOR_MENU;	//窗口的背景色
	wndclass.hCursor = LoadCursor(NULL, IDC_APPSTARTING);
	wndclass.lpfnWndProc = WindowProc;				//窗口过程函数
	wndclass.lpszClassName = className;				//窗口类的名字	
	wndclass.hInstance = hInstance;					//定义窗口类的应用程序的实例句柄


	// 注册窗口类  
	// 参加MSDN文档RegisterClass->Parameters:
	// You must fill the structure with the appropriate class attributes 
	// before passing it to the function. 
	RegisterClass(&wndclass);

3)创建窗口

①这里第一个参数使用的是类名是统一规划好的,为了方便和系统自定义的窗口类相统一

②从这里我们也可以看出来一个进程不一定只有一个窗口,它可以有很多子窗口,说到底窗口这种东西就是进程的资源罢了,可以有很多的,就像给进程分配多少个设备是一回事

// 创建窗口  
	HWND hwnd = CreateWindow(
		className,				//类名
		"我的第一个窗口",		//窗口标题
		WS_OVERLAPPEDWINDOW,	//窗口外观样式  
		10,						//相对于父窗口的X坐标
		10,						//相对于父窗口的Y坐标
		600,					//窗口的宽度  
		300,					//窗口的高度  
		NULL,					//父窗口句柄,为NULL  
		NULL,					//菜单句柄,为NULL  
		hInstance,				//当前应用程序的句柄  
		NULL);					//附加数据一般为NULL

	if (hwnd == NULL)			//是否创建成功  
		return 0;

4)显示窗口

调用Windows API ::ShowWindow来把窗口画出来

// 显示窗口  
	ShowWindow(hwnd, SW_SHOW);

	// 更新窗口  
	UpdateWindow(hwnd);

5)消息处理

!!!这一部分是窗口程序的核心,我们的最终目的还是处理消息 

①通过消息循环来循环查询系统队列当中有没有新的消息产生,如果有,就会把消息保存在out类型的参数MSG当中,然后翻译完消息之后再次DispatchMessage传给操作系统,交给操作系统来处理这消息。

②!!!这里一定要注意,我们的消息处理函数WindowProc不是应用程序自己调用的,而是操作系统帮我们调用的,其实很好理解,你前面这一句:wndclass.lpfnWndProc = WindowProc;不就是把函数地址扔给OS了吗?它直接调用不就行了,,有的童鞋说,那万一我写个死循环不把OS给搞死了吗?其实你想多了,你可以把OS理解成一个大进程,它只是单起了一个内核级线程来调用你的消息处理函数,卡死了,等你的时间片用完,就把你挂到阻塞队列里面,如果你出bug了,不还是会触发中断吗?触发中断谁来接管?还是OS啊!所以啊,童鞋,别想太多辣!😂😂😂

③这里再多一下嘴(Tandy还是那么爱你们,我哭死) ,有的同学不知道什么是回调函数,其实通过这个例子就很好理解啊,我自己定义自己调用的函数就是普通函数,但是如果我自己定义,由别的进程来调用就是你回调函数呗🙂🙂

// 消息循环  
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

6)消息处理函数

这里还是强调上面那一点: 

我们自定义的消息处理函数WindowProc不是应用程序自己调用的,而是操作系统帮我们调用的,当然了消息处理函数要遵循微软的规范,他的格式如下所示:

LRESULT CALLBACK WindowProc(
	IN  HWND hwnd,
	IN  UINT uMsg,
	IN  WPARAM wParam,
	IN  LPARAM lParam
)
{
	switch (uMsg)
	{
		//窗口消息
	case WM_CREATE:
	{
		DbgPrintf("WM_CREATE %d %d\n", wParam, lParam);
		CREATESTRUCT* createst = (CREATESTRUCT*)lParam;
		DbgPrintf("CREATESTRUCT %s\n", createst->lpszClass);
		break;
	}
	case WM_MOVE:
	{
		DbgPrintf("WM_MOVE %d %d\n", wParam, lParam);
		POINTS points = MAKEPOINTS(lParam);
		DbgPrintf("X Y %d %d\n", points.x, points.y);
		break;
	}
	case WM_SIZE:
	{
		DbgPrintf("WM_SIZE %d %d\n", wParam, lParam);
		int newWidth = (int)(short)LOWORD(lParam);
		int newHeight = (int)(short)HIWORD(lParam);
		DbgPrintf("WM_SIZE %d %d\n", newWidth, newHeight);
		break;
	}
	case WM_DESTROY:
	{
		DbgPrintf("WM_DESTROY %d %d\n", wParam, lParam);
		PostQuitMessage(0);
		return 0;
		break;
	}
	//键盘消息
	case WM_KEYUP:
	{
		DbgPrintf("WM_KEYUP %d %d\n", wParam, lParam);
		break;
	}
	case WM_KEYDOWN:
	{
		DbgPrintf("WM_KEYDOWN %d %d\n", wParam, lParam);
		break;
	}
	//鼠标消息
	case WM_LBUTTONDOWN:
	{
		DbgPrintf("WM_LBUTTONDOWN %d %d\n", wParam, lParam);
		POINTS points = MAKEPOINTS(lParam);
		DbgPrintf("WM_LBUTTONDOWN %d %d\n", points.x, points.y);
		break;
	}
	default:
		return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}
	return 0;
}

哦,对了,再补充一点,最后一定要写一个return 0;因为操作系统调用你这个函数的位置需要一个返回值,为0就代表这个消息已经处理完毕了,否则会陷入混乱,你可以仔细体会一下。、

最后我还想讲一下我们的程序为什么能够被CPU执行,如果你对上面哪里有疑问,说不定这一句话可以给你一点帮助呢:

首先我们打开计算机的电源键 》CPU从固定的ROM区开始执行操作系统加载程序 》操作系统启动之后开始帮助计算机管理软硬件资源的分配和使用,然后开始创建用户进程 》再为用户进程创建多个线程(核心级/用户级) 》然后操作系统就要主动让出CPU了,直到中断/异常的出现才会重新执行操作系统的代码 》当一个核心级线程阻塞之后(时间片用完/等待某个事件),会通过中断/异常进入OS,来重新调度新的内核级线程 》切换到另外一个线程去执行。

三、完整源码

#include<iostream>
using namespace std;
#include<Windows.h>
#pragma warning(disable:4996)

void __cdecl OutputDebugStringF(const char* format, ...)
{
	va_list vlArgs;
	char* strBuffer = (char*)GlobalAlloc(GPTR, 4096);

	va_start(vlArgs, format);
	_vsnprintf(strBuffer, 4096 - 1, format, vlArgs);
	va_end(vlArgs);
	strcat(strBuffer, "\n");
	OutputDebugStringA(strBuffer);
	GlobalFree(strBuffer);
	return;
}

#define DbgPrintf   OutputDebugStringF  

HINSTANCE hAppInstance;

LRESULT CALLBACK WindowProc(
	IN  HWND hwnd,
	IN  UINT uMsg,
	IN  WPARAM wParam,
	IN  LPARAM lParam
);


int APIENTRY WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR     lpCmdLine,
	int       nCmdShow)
{
	// TODO: Place code here.
	hAppInstance = hInstance;

	//窗口的类名
	PSTR className = (PCHAR)"My First Window";

	// 创建窗口类的对象 
	WNDCLASS wndclass = { 0 };						//一定要先将所有值赋值
	wndclass.hbrBackground = (HBRUSH)COLOR_MENU;	//窗口的背景色
	wndclass.hCursor = LoadCursor(NULL, IDC_APPSTARTING);
	wndclass.lpfnWndProc = WindowProc;				//窗口过程函数
	wndclass.lpszClassName = className;				//窗口类的名字	
	wndclass.hInstance = hInstance;					//定义窗口类的应用程序的实例句柄


	// 注册窗口类  
	// 参加MSDN文档RegisterClass->Parameters:
	// You must fill the structure with the appropriate class attributes 
	// before passing it to the function. 
	RegisterClass(&wndclass);

	// 创建窗口  
	HWND hwnd = CreateWindow(
		className,				//类名
		"我的第一个窗口",		//窗口标题
		WS_OVERLAPPEDWINDOW,	//窗口外观样式  
		10,						//相对于父窗口的X坐标
		10,						//相对于父窗口的Y坐标
		600,					//窗口的宽度  
		300,					//窗口的高度  
		NULL,					//父窗口句柄,为NULL  
		NULL,					//菜单句柄,为NULL  
		hInstance,				//当前应用程序的句柄  
		NULL);					//附加数据一般为NULL

	if (hwnd == NULL)			//是否创建成功  
		return 0;

	// 显示窗口  
	ShowWindow(hwnd, SW_SHOW);

	// 更新窗口  
	UpdateWindow(hwnd);

	// 消息循环  
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return 0;
}


LRESULT CALLBACK WindowProc(
	IN  HWND hwnd,
	IN  UINT uMsg,
	IN  WPARAM wParam,
	IN  LPARAM lParam
)
{
	switch (uMsg)
	{
		//窗口消息
	case WM_CREATE:
	{
		DbgPrintf("WM_CREATE %d %d\n", wParam, lParam);
		CREATESTRUCT* createst = (CREATESTRUCT*)lParam;
		DbgPrintf("CREATESTRUCT %s\n", createst->lpszClass);
		break;
	}
	case WM_MOVE:
	{
		DbgPrintf("WM_MOVE %d %d\n", wParam, lParam);
		POINTS points = MAKEPOINTS(lParam);
		DbgPrintf("X Y %d %d\n", points.x, points.y);
		break;
	}
	case WM_SIZE:
	{
		DbgPrintf("WM_SIZE %d %d\n", wParam, lParam);
		int newWidth = (int)(short)LOWORD(lParam);
		int newHeight = (int)(short)HIWORD(lParam);
		DbgPrintf("WM_SIZE %d %d\n", newWidth, newHeight);
		break;
	}
	case WM_DESTROY:
	{
		DbgPrintf("WM_DESTROY %d %d\n", wParam, lParam);
		PostQuitMessage(0);
		return 0;
		break;
	}
	//键盘消息
	case WM_KEYUP:
	{
		DbgPrintf("WM_KEYUP %d %d\n", wParam, lParam);
		break;
	}
	case WM_KEYDOWN:
	{
		DbgPrintf("WM_KEYDOWN %d %d\n", wParam, lParam);
		break;
	}
	//鼠标消息
	case WM_LBUTTONDOWN:
	{
		DbgPrintf("WM_LBUTTONDOWN %d %d\n", wParam, lParam);
		POINTS points = MAKEPOINTS(lParam);
		DbgPrintf("WM_LBUTTONDOWN %d %d\n", points.x, points.y);
		break;
	}
	default:
		return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}
	return 0;
}

运行结果截图:

 

 

以上就是本节的全部的内容了,喜欢的话记得一键三连支持一下哦亲~💗💗💗🤞🤞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/275986.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JS变量和函数提升

JS变量和函数提升 JS变量提升编译阶段执行阶段相同变量或函数 变量提升带来的问题变量容易不被察觉的遭覆盖本应销毁的变量未被销毁 如何解决变量提升带来的问题 JS变量提升 sayHi()console.log(myname)var myname yyfunction sayHi() {console.log(Hi) }// 执行结果: // Hi …

我的2023年,平淡中寻找乐趣

文章目录 两个满意我学会了自由泳。学习英语 一个较满意写博客 2024的期望 2023年&#xff0c;我有两个满意&#xff0c;一个较满意。 两个满意 我学会了自由泳。 开始练习自由泳是从2023年3月份&#xff0c;我并没有请教练&#xff0c;而是自己摸索。在抖音上看自由泳的视频…

【一分钟】ThinkPHP v6.0 (poc-yaml-thinkphp-v6-file-write)环境复现及poc解析

写在前面 一分钟表示是非常短的文章&#xff0c;只会做简单的描述。旨在用较短的时间获取有用的信息 环境下载 官方环境下载器&#xff1a;https://getcomposer.org/Composer-Setup.exe 下载文档时可以设置代理&#xff0c;不然下载不上&#xff0c;你懂的 下载成功 cmd cd…

JavaWeb——前端之HTMLCSS

学习视频链接&#xff1a;https://www.bilibili.com/video/BV1m84y1w7Tb/?spm_id_from333.999.0.0 一、Web开发 1. 概述 能通过浏览器访问的网站 2. Web网站的开发模式——主流是前后端分离 二、前端Web开发 1. 初识 前端编写的代码通过浏览器进行解析和渲染得到我们看到…

Java多线程常见的成员方法(线程优先级,守护线程,礼让/插入线程)

目录 1.多线程常见的成员方法2.优先级相关的方法3.守护线程&#xff08;备胎线程&#xff09;4.其他线程 1.多线程常见的成员方法 ①如果没有给线程设置名字&#xff0c;线程是有默认名字 的&#xff1a;Thread-X(X序号&#xff0c;从0开始) ②如果要给线程设置名字&#xff0c…

【SAM系列】Auto-Prompting SAM for Mobile Friendly 3D Medical Image Segmentation

论文链接&#xff1a;https://arxiv.org/pdf/2308.14936.pdf 核心&#xff1a; finetune SAM,为了不依赖外部prompt&#xff0c;通过将深层的特征经过一个编-解码器来得到prompt embedding&#xff1b;finetune完之后做蒸馏

苯酚,市场预计将以5%左右的复合年增长率

苯酚是一种重要的化合物&#xff0c;用于广泛的工业应用&#xff0c;包括塑料、树脂和合成纤维的生产。在建筑、汽车和电子行业不断增长的需求推动下&#xff0c;苯酚市场在过去十年中经历了稳步增长。全球苯酚市场分析&#xff1a; 在 2021-2026 年的预测期内&#xff0c;全球…

Java并发编程(三)

并发编程的三个特性 并发编程的三个重要特性是原子性、可见性和有序性。 原子性&#xff1a;原子性指的是一个操作是不可中断的&#xff0c;要么全部执行成功&#xff0c;要么全部不执行&#xff0c;是不可再分割的最小操作单位。保证原子性可以避免多个线程同时对共享数据进行…

《深入理解计算机系统》学习笔记 - 第七课 - 机器级别的程序三

Lecture 07 Machine Level Programming III Procedures 机器级别的程序三 文章目录 Lecture 07 Machine Level Programming III Procedures 机器级别的程序三概述程序机制 栈结构栈说明栈定义推入数据弹出数据 调用控制代码示例程序控制流程%rip 传递数据ABI 标准示例 管理局部…

WPF Button使用漂亮 控件模板ControlTemplate 按钮使用控制模板实例及源代码 设计一个具有圆角边框、鼠标悬停时颜色变化的按钮模板

续前两篇模板文章 模板介绍1 模板介绍2 WPF中的Button控件默认样式简洁&#xff0c;但可以通过设置模板来实现更丰富的视觉效果和交互体验。按钮模板主要包括背景、边框、内容&#xff08;通常为文本或图像&#xff09;等元素。通过自定义模板&#xff0c;我们可以改…

JVM篇:JVM的简介

JVM简介 JVM全称为Java Virtual Machine&#xff0c;翻译过来就是java虚拟机&#xff0c;Java程序&#xff08;Java二进制字节码&#xff09;的运行环境 JVM的优点&#xff1a; Java最大的一个优点是&#xff0c;一次编写&#xff0c;到处运行。之所以能够实现这个功能就是依…

Docker自建私人云盘系统

Docker自建私人云盘系统。 有个人云盘需求的人&#xff0c;主要需求有这几类&#xff1a; 文件同步、分享需要。 照片、视频同步需要&#xff0c;尤其是全家人都是用的同步。 影视观看需要&#xff08;分为家庭内部、家庭外部&#xff09; 搭建个人网站/博客 云端OFFICE需…

TiDB在WMS物流系统的应用与实践 (转)

业务背景 北京科捷物流有限公司于2003年在北京正式成立,是ISO质量管理体系认证企业、国家AAAAA级物流企业、海关AEO高级认证企业,注册资金1亿元,是中国领先的大数据科技公司——神州控股的全资子公司。科捷物流融合B2B和B2C的客户需求,基于遍布全国的物流网络与自主知识产…

电话号码的字母组合[中等]

一、题目 给定一个仅包含数字2-9的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意1不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "23" 输出&am…

【MySQL学习笔记008】多表查询及案例实战

1、多表关系 概述&#xff1a;项目开发中&#xff0c;在进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;由于业务之间相互关联&#xff0c;所以各个表结构之间也存在着各种联系&#xff0c;基本上可分为三种&a…

什么是 NLP (自然语言处理)

NLP&#xff08;自然语言处理&#xff09;到底是做什么&#xff1f; NLP 的全称是 Natural Language Processing&#xff0c;翻译成中文称作&#xff1a;自然语言处理。它是计算机和人工智能的一个重要领域。顾名思义&#xff0c;该领域研究如何处理自然语言。 自然语言就是我…

小狐狸ChatGPT付费创作系统小程序端开发工具提示打开显示无法打开页面解决办法

最新版2.6.7版下载&#xff1a;https://download.csdn.net/download/mo3408/88656497 很多会员在上传小程序前端时经常出现首页无法打开的情况&#xff0c;错误提示无法打开该页面&#xff0c;不支持打开&#xff0c;这种问题其实就是权限问题&#xff0c;页面是通过调用web-v…

实习知识整理13:在购物车界面点击提交订单进入订单信息界面

在这块主要就是对前端传到后端的数据的处理&#xff0c;然后由后端再返还到新的前端界面 首先点击下单按钮后&#xff0c; 提交购物车中所选中的信息 因为前端是将name定义为 cartList[0].cartId &#xff0c;cartList[1].cartId 形式的 所以后端需要重新定义一个类来进行封装…

C语言中宏定义的一种妙用

1.前言 最近分析了一个宏定义的妙用方法&#xff0c;利用宏定义来构建一个枚举类型&#xff0c;通过自己代码测试验证&#xff0c;方法可行&#xff0c;分享给大家。 2.源码 实验源码如下所示&#xff1a; head1.h DEF_TEST(name1) DEF_TEST(name2) DEF_TEST(name3) #unde…

Redis哨兵sentinel

是什么&#xff1f; 哨兵巡查监控后台master主机是否故障&#xff0c;如果故障根据投票数自动将某一个slave库变为master&#xff0c;就行对外服务&#xff0c;称为无人值守运维 能干嘛&#xff1f; 主从监控&#xff1a;监控主从redis库是否正常工作 消息通知&#xff1a;…