WIN32 API —— 最简单的Windows窗口封装类[通俗易懂]

news2025/1/9 14:49:23

1 开发语言抉择

1.1 关于开发Win32 程序的语言选择 C还是C++

在决定抛弃MFC,而使用纯Win32 API 开发Window桌面程序之后,还存在一个语言的选择,这就是是否使用C++。C++作为C的超集,能实现所有C能实现的功能。其实反之亦然,C本身也能完成C++超出的那部分功能,只是可能需要更多行的代码。就本人理解而言,

  • 对于巨大型项目,还是使用纯C来架构更加稳妥;
  • 对于中小型项目来说,C++可能更方便快捷。由于目前做的是中小项目,所以决定把C++作为主要开发语言。

1.2 关于C++特性集合的选择

在决定使用C++之后,还有一个至关重要的抉择,那就是C++特性集合的选择。C++实在是太复杂了,除了支持它的老祖先C的所有开发模式,还支持基于对象开发(OB)、面向对象开发(OO)、模板技术。可以说,C++是个真正全能型语言,这同时也造成了C++的高度复杂性。使用不同的开发模式,就相当于使用不同的编程语言。就本人而言,对C++的模板编程也根本没有任何经验。综合过去的经验教训和本人对C++的掌握程度,决定:

  • 使用基于对象和面向对象两种开发模式,如果一个功能两种都可以实现,则优先选择基于对象。倾向于OB的技术观点来自对苹果Object-C开发经验。
  • 尽量避免多继承,此观点来自Java和.net开发经验。
  • 数据结构和容器,使用C++标准模板库(STL),模板编程本身复杂,但是使用STL却非常容易。

2 Windows窗口对象的封装类

对Windows桌面程序而言,Window和Message的概念是核心。首先需要封装的就是窗口,例如MFC就是用CWnd类封装了窗口对象。我们当初抛弃MFC的原因,就是因为它太复杂不容易理解,所以对基本窗口对象的封装一定要做到最简单化。

2.1 封装原则

首要的原则就是“简单”。能用一个Win32API直接实现的功能,绝不进行二次包装,如移动窗口可以使用 MoveWindow()一个函数实现,类中就不要出现同样功能的MoveWindow()函数。MFC里有很多这种重复的功能,其实只是可以少写一个hwnd参数而已,却多加了一层调用。我就是要让HWND句柄到处出现,绝不对其隐藏,因为这个概念对于Windows来说太重要了,开发者使用任何封装类都不应该对其视而不见。

其次,同样功能多种技术可以实现时,优先选择容易理解的技术,“可理解性”比“运行效率”更重要。

2.2 源码

头文件 XqWindow.h

[cpp

  1. #pragma once
  2. #include <vector>
  3. class XqWindow
  4. {
  5. public:
  6. XqWindow(HINSTANCE hInst);
  7. ~XqWindow();
  8. private:
  9. HWND hWnd; // 对外只读,确保安全
  10. HINSTANCE hInstance;
  11. public:
  12. // 返回窗口对象句柄
  13. HWND GetHandle();
  14. // 消息处理。需要后续默认处理则需要返回0;停止该消息后续处理,则返回1
  15. virtual int HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
  16. private:
  17. // 原始窗口过程
  18. static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
  19. private:
  20. // 已注册过的类集合
  21. static std::vector<void*> registeredClassArray;
  22. public:
  23. // 创建窗口
  24. void Create();
  25. };

实现文件 XqWindow.cpp

[cpp

  1. #include “stdafx.h”
  2. #include “XqWindow.h”
  3. std::vector<void*> XqWindow::registeredClassArray;
  4. // 创建窗口
  5. void XqWindow::Create()
  6. {
  7. wchar_t szClassName[32];
  8. wchar_t szTitle[128];
  9. void* _vPtr = *((void**)this);
  10. ::wsprintf(szClassName, L“%p”, _vPtr);
  11. std::vector<void*>::iterator it;
  12. for (it = registeredClassArray.begin(); it != registeredClassArray.end(); it++) // 判断对象的类是否注册过
  13. {
  14. if ((*it) == _vPtr)
  15. break;
  16. }
  17. if (it == registeredClassArray.end()) // 如果没注册过,则进行注册
  18. {
  19. //注册窗口类
  20. WNDCLASSEX wcex;
  21. wcex.cbSize = sizeof(WNDCLASSEX);
  22. wcex.style = CS_HREDRAW | CS_VREDRAW;
  23. wcex.lpfnWndProc = XqWindow::WndProc;
  24. wcex.cbClsExtra = 0;
  25. wcex.cbWndExtra = 0;
  26. wcex.hInstance = this->hInstance;
  27. wcex.hIcon = NULL;
  28. wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW);
  29. wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  30. wcex.lpszMenuName = NULL;
  31. wcex.lpszClassName = szClassName;
  32. wcex.hIconSm = NULL;
  33. if (0 != ::RegisterClassEx(&wcex)) // 把注册成功的类加入链表
  34. {
  35. registeredClassArray.push_back(_vPtr);
  36. }
  37. }
  38. // 创建窗口
  39. if (this->hWnd == NULL)
  40. {
  41. ::wsprintf(szTitle, L“窗口类名(C++类虚表指针):%p”, _vPtr);
  42. HWND hwnd = ::CreateWindow(szClassName,
  43. szTitle,
  44. WS_OVERLAPPEDWINDOW,
  45. 0, 0, 800, 600,
  46. NULL,
  47. NULL,
  48. hInstance,
  49. (LPVOID)this
  50. );
  51. if (hwnd == NULL)
  52. {
  53. this->hWnd = NULL;
  54. wchar_t msg[100];
  55. ::wsprintf(msg, L“CreateWindow()失败:%ld”, ::GetLastError());
  56. ::MessageBox(NULL, msg, L“错误”, MB_OK);
  57. return;
  58. }
  59. }
  60. }
  61. XqWindow::XqWindow(HINSTANCE hInst)
  62. {
  63. this->hWnd = NULL;
  64. this->hInstance = hInst;
  65. }
  66. XqWindow::~XqWindow()
  67. {
  68. if ( this->hWnd!=NULL && ::IsWindow(this->hWnd) ) // C++对象被销毁之前,销毁窗口对象
  69. {
  70. ::DestroyWindow(this->hWnd); // Tell system to destroy hWnd and Send WM_DESTROY to wndproc
  71. }
  72. }
  73. HWND XqWindow::GetHandle()
  74. {
  75. return this->hWnd;
  76. }
  77. // 消息处理。需要后续默认处理则需要返回0;停止该消息后续处理,则返回1
  78. int XqWindow::HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  79. {
  80. return 0;
  81. }
  82. // 原始窗口过程
  83. LRESULT CALLBACK XqWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  84. {
  85. XqWindow* pObj = NULL;
  86. if (message == WM_CREATE) // 在此消息收到时,把窗口对象句柄赋给C++对象成员,同时把C++对象地址赋给窗口对象成员
  87. {
  88. pObj = (XqWindow*)(((LPCREATESTRUCT)lParam)->lpCreateParams);
  89. pObj->hWnd = hWnd; // 在此处获取HWND,此时CreateWindow()尚未返回。
  90. ::SetWindowLong(hWnd, GWL_USERDATA, (LONG)pObj); // 通过USERDATA把HWND和C++对象关联起来
  91. }
  92. pObj = (XqWindow*)::GetWindowLong(hWnd, GWL_USERDATA);
  93. switch (message)
  94. {
  95. case WM_CREATE:
  96. pObj->HandleMessage(hWnd, message, wParam, lParam);
  97. break;
  98. case WM_DESTROY:
  99. if (pObj != NULL) // 此时,窗口对象已经销毁,通过设置hWnd=NULL,来通知C++对象
  100. {
  101. pObj->hWnd = NULL;
  102. }
  103. break;
  104. default:
  105. pObj = (XqWindow*)::GetWindowLong(hWnd, GWL_USERDATA);
  106. if (pObj != NULL)
  107. {
  108. if (pObj->HandleMessage(hWnd, message, wParam, lParam) == 0) // 调用子类的消息处理虚函数
  109. {
  110. return DefWindowProc(hWnd, message, wParam, lParam);
  111. }
  112. }
  113. else
  114. {
  115. return DefWindowProc(hWnd, message, wParam, lParam);
  116. }
  117. break;
  118. }
  119. return 0;
  120. }

2.3 使用举例

基本用法为,创建一个TestWindow类,继承自XqWindow,然后重新虚函数 HandleMessage()。所有业务处理代码都要在HandleMessage()里调用,由于该函数是成员函数,所有里面可以直接使用this来引用TestWindow类对象的成员。一个例子代码如下:

TestWindow.h

[cpp

  1. #pragma once
  2. #include “XqWindow.h”
  3. class TestWindow :
  4. public XqWindow
  5. {
  6. public:
  7. TestWindow(HINSTANCE);
  8. ~TestWindow();
  9. protected:
  10. int HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
  11. private:
  12. // 业务数据部分
  13. int rectWidth;
  14. int rectHeight;
  15. };

TestWindow.cpp

[cpp

#include "stdafx.h" 
#include "TestWindow.h"

TestWindow::TestWindow(HINSTANCE hInst) :XqWindow(hInst) 
{  
	rectWidth = 300;  rectHeight = 200; 
}

TestWindow::~TestWindow() 
{

} 

int TestWindow::HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 
{  
	PAINTSTRUCT ps;  
	HDC hdc;  
	switch (message)  
	{  
		case WM_PAINT:   
			 hdc = ::BeginPaint(hWnd, &ps);   
			 ::Rectangle(hdc, 0, 0, this->rectWidth, this->rectHeight);   
			 ::EndPaint(hWnd, &ps);   
			 return 1;  
		default:   
		break;  
	}  
	return 0; 
}

调用部分:

[cpp

  1. pTest = new TestWindow(theApp.m_hInstance);
  2. pTest->Create();
  3. ::ShowWindow(pTest->GetHandle(), SW_SHOW);
  4. ::UpdateWindow(pTest->GetHandle());

运行效果:

2.4 技术要点

这个XqWindow类对窗口对象做了最小的封装,主要实现了消息处理函数和C++对象的关联。内存布局如下:

需要说明的几点:

(1)C++类和窗口类的一一对应。由于VC++默认不启用RTTI,同时考虑到代码兼容性和运行效率,也不提倡启用RTTI,在没有RTTI支持的情况下,如何才能在运行时把同一个类的所有实例与其他类的实例进行区分呢?这里我们采用了C++的虚表指针,每一个有虚函数的类都拥有自己独立的虚表,而这个虚表指针又在每个实例中存储。同一个类的不同实例共享一个虚表,所以这给了我们区分对象所属C++类的机会。当然这种技术只能用到有虚函数的类中,对于没有虚函数的类的对象,不存在虚表。对于我们的情况,XqWindow类有一个HandleMessage虚函数,从而其他所有继承此类的子类孙类也就都有自己的虚表了。

在RegisterClass()之前,首先判断当前C++对象所属类的虚表指针是否存在vptrAraay链表中。如果没有,则注册窗口类,并把虚表指针存放到vptrArray链表中;如果存在,则直接使用该虚表指针对应的窗口类。

需要注意的是,获取对象虚表指针值的操作不能在XqWindow::XqWindow()构造函数里进行,因为在执行此函数时,C++对象的虚表指针成员尚未被设置到指向派生类的虚表地址(因为尚未调用子类的构造函数)。所以必须在对象构造完成之后才能获取虚表指针值,这也是为什么Create()不能在XqWindow()构造函数里调用的原因。(我曾经为了简化调用把Create()放到XqWindow()里,导致了所有对象的虚表指针都相同的后果!)

(2)C++对象与窗口对象的关系。C++对象创建以后,调用Create()是唯一可以和窗口对象绑定到一起的途径。在旧窗口销毁之前,C++对象不能再创建新窗口,调用Create()多次也没用。

C++对象生存寿命也大于对应的窗口寿命,否则窗口过程中使用C++对象就会出现非法访问内存问题。这两种对象的生命序列为: C++ 对象出生 — 调用Create()产生窗口对象–某种原因窗口对象销毁–C++对象销毁。

为防止C++对象在窗口对象之前销毁,在XqWindow类的析构函数中,先通过DestroyWindow()销毁窗口对象。窗口对象销毁时,也会设置C++对象的hWnd为NULL,来通知C++对象窗口的销毁。

形象一点的说法:C++对象和窗口对象则是一夫一妻制、且只能丧偶不能离异条件下的夫妻关系,而且C++对象是寿命长的一方,窗口对象则是寿命短的一方。只有一个窗口对象死掉后,C++对象才能重新生成新窗口。而且C++对象死掉之前,需要先把窗口对象杀死陪葬。

(3)C++对象和窗口对象的彼此引用。C++对象通过成员变量hWnd引用窗口对象,窗口对象则通过GWL_USERDATA附加数据块指向C++对象。另外为了及时捕获WM_CRATE消息并在HandleMessage里处理,C++成员hWnd的赋值并没有在CreateWindow()之后,而是在原始窗口过程函数处理WM_CREAT消息时。这主要与CreateWindow()原理有关。

CreateWindow()

{

HWND hwnd = malloc(..);

初始化窗口对象;

WndProc(hwnd, WM_CRATE, ..); // 此时已经创建了窗口

其他操作;

return hwnd;

}

同理,DestroyWindow()的原理为.

DestroyWindow(hwnd)

{

窗口对象清理工作;

WndProc(hwnd, WM_DESTROY, ..); // 此时窗口已经不可见了

其他操作;

free(hwnd);

}

2.5 存在问题

虽然XqWindow类可以很好的工作,但也存在一些问题:

(1)由于Window对象靠USERDATA引用C++对象,所以如果其他代码通过SetWindowLong(hwnd, GWL_USERDATA, xxx)修改了这个数据块,那么程序将会崩溃。如何防止这种破坏,需要进一步研究。

(2)使用C++对象的虚表指针,而这个指针的具体内存布局并没有明确的规范标准,一旦将来VC++编译器修改虚表指针的存放位置,程序将会出问题。不过由于考虑到二进制的兼容性,VC++作出这种改变的可能性不大。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习 | matplotlib超详细教程

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

Git的安装和环境变量的配置

目录 前言一、下载Git二、安装Git三、检查是否安装成功四、 配置用户名和邮箱五、环境变量配置1. 获取git的安装路径2. 设置环境变量 前言 当我们第一次在新电脑上使用git命令的时候&#xff0c;会报错【git 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件】…

Typora+PicGo+阿里云OSS搭建博客图床

&#x1f3e0;个人主页&#xff1a;shark-Gao &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是shark-Gao&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f389;目前状况&#xff1a;23届毕业生&#xff0c;目前在某公司实习&#x1f…

中国民营快递:战事永不休

【潮汐商业评论/原创】 在中国&#xff0c;没有一个任何行业的“战争”在时间和烈度上可以与快递业相比。这是一场持续长达20年&#xff0c;融合了规模战、价格战、资本战等你能想象到的所有形态的立体化“战争”。 更令人难以置信的是&#xff0c;眼下这场以人力、技术为武器…

你还不会写系统?超详细驾校科考系统教程,手把手教学(内涵源码,Qt实现界面,有接口可供二次开发刷题使用)

目录 一、创建项目 二、登录界面 三、验证邮箱地址 四、验证账号密码 五、考试时间 六、初始化题库 七、布局按钮 八、提交试题 九、窗口交互 一、创建项目 新建Qt桌面应用程序&#xff0c;项目名&#xff1a;ExamSys。 类信息&#xff1a;类名LoginDialog继承自QDi…

Windows 7出现两个IP地址,导致联网问题

Qt源码解析 索引 Windows7 出现两个IP介绍 问题描述 win7电脑连接网线后出现两个IP地址&#xff0c;导致网络连接出现问题。 可能的现象有 连接网络出现黄色感叹号 局域网即时通信&#xff08;例如飞秋软件收发失败&#xff09; 修改IP地址不生效 服务软件启动报错&#…

12吋全自动划片机有哪些功能?

1、大面积工作盘&#xff1a;可容纳多个工件&#xff0c;并自动对位。 2、轴光/环光&#xff1a;采用合适的光源照射&#xff0c;显示影像更能呈现工作物表面特征。 3、双倍率显微镜头&#xff1a;视野更大&#xff0c;精准快速进行对准校正工作。 4、非接触测高&#xff1a…

Text2Video-Zero:Text-to-Image Diffusion Models are Zero-Shot Video Generators

【AIGC-AI视频生成系列-文章1】Text2Video-Zero - 知乎一句话亮点&#xff1a;当文本-视频生成也不需要额外数据训练&#xff0c;只需要基于现有的diffusion-model 如Stable Diffusion能力调整即可实现&#xff0c;解决生成视频帧间不一致问题&#xff0c;是不是很心动。 文章链…

腾讯云轻量2核2G4M带宽月流量/CPU/地域选择及限制说明

腾讯云轻量应用服务器2核2G4M活动上线了&#xff0c;这款轻量服务器4M公网带宽&#xff0c;每月300G免费流量&#xff0c;系统盘为50GB SSD盘&#xff0c;腾讯云百科来详细说下轻量应用服务器配置、租用费用、地域选择、CPU型号等详细说明&#xff1a; 目录 腾讯云轻量2核2G4…

2023年上半年软件设计师中级学习总结(超详细)

目录 前言 一、背景1.1上次考试感受&#xff1a;1.2这次考试感受&#xff1a;1.3方法&#xff1a; 二、 过程2.1计算机网络概论计算机组成数据表示相关知识校验码相关知识计算机体系结构网络体系结构OSI/RM和TCP/IP计算机安全性可靠性性能评价 2.2 程序设计语言基础知识编译和解…

前端开发如何更好的避免样式冲突?级联层(CSS@layer)

作者&#xff1a;vivo 互联网前端团队 - Zhang Jiqi 本文主要讲述了CSS中的级联层&#xff08;CSSlayer&#xff09;&#xff0c;讨论了级联以及级联层的创建、嵌套、排序和浏览器支持情况。级联层可以用于避免样式冲突&#xff0c;提高代码可读性和可维护性。 一、什么是级联…

选择交换机主要看哪些参数指标

交换机有几个性能指标您一定要知道哦&#xff0c;和海翎光电的小编一起温故而知新。 网络构成方式&#xff1a;接入层交换机、汇聚层交换机、核心层交换机 OST模型&#xff1a;第二层交换机、第三层交换机、第四层交换机……第七层交换机 交换机的可管理性&#xff1a;可管理…

电商分析必懂的指标体系,引流、转化、留存,一文带你搞懂

今天&#xff0c;我就来讲讲电商到底该重点关注哪些指标&#xff0c;又该拿这些指标来进行怎么样的分析。 一般来说&#xff0c;在运营模块&#xff0c;需要重点关注的是新用户的引流和转化&#xff0c;以及老用户的活跃、留存、回购、流失。 01 引流 简单来说&#xff0c;引…

【现实生活实际应用】【公交车换乘算法——算法用在公交车】【单源最短路-如何根据图意构建图,最重要】最优乘车【sstream输入】

问题出现了 其实我们构图的时候&#xff0c; 我们已经知道1 - 3 - 5 那么我们直接让1 连到 3 和 5 表示直达 如果出现另一路 4 7 3 6 则3最终连接的是 5 和 6 从1开始&#xff0c;先走到了 3 和 5 然后bfs宽搜到3&#xff08;此时就表示换乘了&#xff0c;因为1直达的已经都遍历…

JavaEE进阶5/25(Spring 注解)

1.五大类注解详解&#xff08;重点 1.Controller 控制器&#xff0c;用于业务逻辑层&#xff0c;来控制用户的行为。它用来检查用户参数的有效性。 当用户的参数有效的话会继续分发到服务层。controller可以理解为程序的安保系统 2.Service 服务层。归属服务层&#xff0c;调用…

【JavaSE】Java基础语法(二十九):Map集合

文章目录 1. Map集合概述和特点2. Map集合的基本功能3. Map集合的获取功能【应用】4. Map集合的遍历(方式1)5. Map集合的遍历(方式2)6. map四种遍历方式 1. Map集合概述和特点 Map集合概述 interface Map<K,V> K&#xff1a;键的类型&#xff1b;V&#xff1a;值的类型…

MySQL — 视图、存储过程、触发器

文章目录 视图/存储过程/存储函数/触发器一、视图1.1 语法1.1.1 创建视图1.1.2 查询1.1.3 修改1.1.4 删除1.1.5 对数据的操作 1.2 检查选项1.2.1 cascaded1.2.2 local 1.3 视图的更新1.4 视图的作用1.5 案例1.5.1 案例11.5.2 案例2 二、存储过程2.1 介绍2.2 基本语法2.3 变量2.…

【保姆级】拯救“爆红”C盘,轻松清出30G

目录 第一步 ​第二步 第三步 第四步 ​第五步&#xff08;可选&#xff09; 打开一个程序&#xff0c;发现等它启动好了咖啡都凉叻。。 访问个文件&#xff0c;都要转个圈圈。。不、转好几个圈圈⭕️⭕️⭕️⭕️⭕️⭕️⭕️ 更新、安装新软件时&#xff0c;提示“安装…

分布式网络通信框架(十二)——zookeeper简介与安装

简介 zookeeper&#xff08;后文简称zk&#xff09;提供功能有&#xff1a;服务注册中心、全局服务命名、以及分布式锁。 RPC服务会被发布到不同的服务器上&#xff0c;本项目zk的作用就是找到某一个RPC服务在哪一个服务器上&#xff0c;提供服务注册发现功能。 zk的数据如何…

Linux-0.11 入口函数main.c详解

Linux-0.11 入口函数main.c详解 模块简介 main.c大部分代码主要是对内核进行初始化&#xff0c;而main.c开始&#xff0c;就都是c语言编写的内核了。 函数详解 time_init static void time_init(void)该函数读取CMOS时钟信息作为系统的开机时间。 struct tm time;do {time…