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

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++作出这种改变的可能性不大。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作为Microsoft 32位平台的应用程序编程接口, Win32 API是从事Windows应用程序开发所必备的。 首先对Win32 API函数做完整的概述;然后收录五大函数: 窗口管理、图形设备接口、系统服务、国际特性以及网络服务; 在附录部分,讲解如何在Visual Basic和Delphi中对其调用。 本书是从事Windows应用程序开发的软件工程师的必备参考手册。 控件与消息函数 共91个函数 硬件与系统函数 共98个函数 设备场景函数 共73个函数 绘图函数 共105个函数 位图、图标和光栅运算函数 共39个函数 菜单函数 共37个函数 文本和字体函数 共41个函数 打印函数 共66个函数 文件处理函数 共118个函数 进程和线程函数 共40个函数 Windows消息函数 共11个函数 网络函数 共14个函数 目 录 第一章 Win32 API概论…………………………………………………………………………1 1.1 为什么使用Win32 API …………………………………………………………………1 1.2 Win32 API简介 …………………………………………………………………………1 1.3 综述………………………………………………………………………………………11 第二章 窗口管理函数(Windows Control Function) ……………………………………13 2.1 易用特性函数(Accessibility Features)…………………………………………13 2.2 按钮函数(Button)……………………………………………………………………20 2.3 插入标记(^)函数(Caret)…………………………………………………………21 2.4 组合框函数(Combo box) ……………………………………………………………24 2.5 通用对话框函数(Common Dialog Box) ……………………………………………25 2.6 标函数(Cursor)………………………………………………………………………36 2.7 对话框函数(Dialog Box)……………………………………………………………40 2.8 编辑控制函数(Edit Control)………………………………………………………54 2.9 图标函数(Icon)………………………………………………………………………54 2.10 键盘加速器函数(Keyboard Accelerator)……………………………………… 61 2.11 键盘输入函数(Keyboard InPut) …………………………………………………63 2.12 列表框函数(List box) ……………………………………………………………75 2.13 菜单函数(Menu) ……………………………………………………………………76 2.14 消息和消息队列函数(Message and Message Queue)……………………………90 2.15 鼠标输入函数(Mouse Input) ……………………………………………………100 2.16 多文档接口函数(Multiple Document Interface) ……………………………103 2.17 资源函数(Resource)………………………………………………………………105 2.18 滚动条函数(Scroll Bar)…………………………………………………………113 2.19 窗口函数(Window)…………………………………………………………………119 2.20 窗口函数(Window Class)………………………………………………………144 2.21 窗口过程函数(Window Procedure)………………………………………………150 2.22 窗口属性函数(Window Property) ………………………………………………152 第三章 图形设备接口函数(Graphic Device Interface Function) …………………155 3.1 位图函数(Bitmap) …………………………………………………………………155 3.2 笔刷函数(Brush)……………………………………………………………………171 3.3 剪切函数(Clipping) ………………………………………………………………176 3.4 颜色函数(Color)……………………………………………………………………179 3.5 坐标空间与变换函数(Coordinate Space Transformation)……………………186 3.6 设备环境函数(Device Context) …………………………………………………195 3.7 填充形态函数(Filled shape) ……………………………………………………211 3.8 字体和正文函数(Font and Text)…………………………………………………215 3.9 ICM 2.0函数 …………………………………………………………………………238 3.10 线段和曲线函数(Line and Curve)………………………………………………295 3.11 图元文件函数(Metafile)…………………………………………………………300 3.12 多显示器函数(Multiple Display Monitors) …………………………………311 3.13 绘图函数和画图函数(Painting and Drawing)…………………………………313 3.14 路径函数(Path)……………………………………………………………………328 3.15 画笔函数(Pen) ……………………………………………………………………332 3.16 打印及打印假脱机程序函数(Printing and Print Spooler)…………………334 3.17 矩形函数(Rectangle) ……………………………………………………………371 3.18 区域函数(Region)…………………………………………………………………374 第四章 系统服务函数(System Service Function) ……………………………………383 4.1 访问控制函数(Access Control) …………………………………………………383 4.2 原子函数(Atom) ……………………………………………………………………406 4.3 客户/服务器访问控制函数(Client/Server Access Control) ………………409 4.4 剪贴板函数(Clipboard)……………………………………………………………431 4.5 通信函数(Communication)…………………………………………………………436 4.6 控制台函数(Console)………………………………………………………………444 4.7 数据解压库函数(Data Decompression Library) ………………………………463 4.8 调试函数(Debugging)………………………………………………………………466 4.9 设备输入输出函数(Device Input and Output)…………………………………472 4.10 动态数据交换函数(Dynamic Data Exchange) …………………………………474 4.11 动态数据交换管理函数(Dynamic Data Exchange Management)………………476 4.12 动态链接库函数(Dynamic-Link Library)………………………………………489 4.13 错误函数(Error) …………………………………………………………………496 4.14 事件日志函数(Event Logging) …………………………………………………499 4.15 文件函数(File)……………………………………………………………………503 4.16 文件安装库函数(File Installation Library) ………………………………542 4.17 文件映射函数(File Mapping)……………………………………………………546 4.18 文件系统函数 File System)………………………………………………………551 4.19 句柄和对象函数(Handle and Object)………………………………………………556 4.20 挂钩函数(Hook)………………………………………………………………………560 4.21 ImageHlp函数…………………………………………………………………………572 4.22 大整数操作函数(Iarge Integer Operations)……………………………………594 4.23 低层访问控制函数(Low-Level Access Control)………………………………596 4.24 LSAPI函数 …………………………………………………………………………617 4.25 邮槽函数(Mailslot)………………………………………………………………622 4.26 内存管理函数(Memory Management) ……………………………………………623 4.27 管道函数(Pipe) …………………………………………………………………655 4.28 电源管理函数(Power Management) …………………………………………… 663 4.29 进程和线程函数(Process and Thread)…………………………………………666 4.30 注册表函数(Registry)……………………………………………………………700 4.31 字符串操作函数(String Manipulation)……………………………………… 724 4.32 结构化异常处理函数(Structured Exception Handling) ……………………742 4.33 同步函数(Synchronization) ……………………………………………………745 4.34 系统信息函数(System Information)……………………………………………766 4.35 系统消息函数(System Message)…………………………………………………780 4.36 系统关机函数(System Shutdown) ………………………………………………781 4.37 磁带备份函数(Tape Backup) ……………………………………………………783 4.38 时间函数(Time)……………………………………………………………………789 4.39 计时器函数(Timer) ………………………………………………………………795 4.40 工具帮助函数(Tool Help) ………………………………………………………796 4.41 窗口站和桌面函数(Window Station and Desktop)……………………………799 4.42 Windows NT 4.0访问控制函数(Window NT 4.0 Access-Control)……………808 4.43 WinTrust函数(WinTrust)…………………………………………………………814 第五章 国际特性函数(International Peatures Punction)时性…………………………815 5.1 输入方法编辑函数(Input Method Editor)…………………………………………815 5.2 国家语言支持函数(National Language Support)………………………………… 828 5.3 Unicode和字符集函数(Unicode and Character Set)……………………………… 843 第六章 网络服务函数(Networding Service Function)……………………………………849 6.1 数据链路控制函数(DLC)………………………………………………………………849 6.2 网络函数(Net)…………………………………………………………………………849 6.3 NetBIOS函数……………………………………………………………………………896 6.4 网络DDE函数(Networking DDE)……………………………………………………897 6.5 RAS服务器管理函数(RAS Server Administration)………………………………901 6.6 远程访问服务函数(Remote Access Administration)………………………………910 6.7 服务函数(Service)……………………………………………………………………929 6.8 Windows网络函数(Windows Networking)……………………………………………930 附录1 如何在VB中调用DLL API ……………………………………………………………945 1 DLL API的声明……………………………………………………………………………945 2 DLL API的调用……………………………………………………………………………947 附录2 在Delphi中直接调用Windows API…………………………………………………953

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值