重温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

#pragma once
#include <vector>

class XqWindow
{
public:
	XqWindow(HINSTANCE hInst);
	~XqWindow();
private:
	HWND hWnd;    // 对外只读,确保安全
	HINSTANCE hInstance;
public:
	// 返回窗口对象句柄
	HWND GetHandle();
	// 消息处理。需要后续默认处理则需要返回0;停止该消息后续处理,则返回1
	virtual int HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
private:
	// 原始窗口过程
	static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
private:
	// 已注册过的类集合
	static std::vector<void*> registeredClassArray;
public:
	// 创建窗口
	void Create();
};

实现文件 XqWindow.cpp

#include "stdafx.h"
#include "XqWindow.h"

std::vector<void*> XqWindow::registeredClassArray;

// 创建窗口
void XqWindow::Create()
{
	wchar_t szClassName[32];
	wchar_t szTitle[128];
	void* _vPtr = *((void**)this);
	::wsprintf(szClassName, L"%p", _vPtr);

	std::vector<void*>::iterator it;
	for (it = registeredClassArray.begin(); it != registeredClassArray.end(); it++)  // 判断对象的类是否注册过
	{
		if ((*it) == _vPtr)
			break;
	}
	if (it == registeredClassArray.end())                // 如果没注册过,则进行注册
	{
		//注册窗口类
		WNDCLASSEX wcex;
		wcex.cbSize = sizeof(WNDCLASSEX);
		wcex.style = CS_HREDRAW | CS_VREDRAW;
		wcex.lpfnWndProc = XqWindow::WndProc;
		wcex.cbClsExtra = 0;
		wcex.cbWndExtra = 0;
		wcex.hInstance = this->hInstance;
		wcex.hIcon = NULL;
		wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW);
		wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
		wcex.lpszMenuName = NULL;
		wcex.lpszClassName = szClassName;
		wcex.hIconSm = NULL;
		if (0 != ::RegisterClassEx(&wcex))     // 把注册成功的类加入链表
		{
			registeredClassArray.push_back(_vPtr);
		}
	}

	// 创建窗口
	if (this->hWnd == NULL)
	{
		::wsprintf(szTitle, L"窗口类名(C++类虚表指针):%p", _vPtr);
		HWND hwnd = ::CreateWindow(szClassName,
			szTitle,
			WS_OVERLAPPEDWINDOW,
			0, 0, 800, 600,
			NULL,
			NULL,
			hInstance,
			(LPVOID)this
			);
		if (hwnd == NULL)
		{
			this->hWnd = NULL;
			wchar_t msg[100];
			::wsprintf(msg, L"CreateWindow()失败:%ld", ::GetLastError());
			::MessageBox(NULL, msg, L"错误", MB_OK);
			return;
		}
	}
}
XqWindow::XqWindow(HINSTANCE hInst)
{
	this->hWnd = NULL;
	this->hInstance = hInst;
}

XqWindow::~XqWindow()
{
	if ( this->hWnd!=NULL && ::IsWindow(this->hWnd) ) // C++对象被销毁之前,销毁窗口对象
	{
		::DestroyWindow(this->hWnd);  // Tell system to destroy hWnd and Send WM_DESTROY to wndproc
	}
}

HWND XqWindow::GetHandle()
{
	return this->hWnd;
}
// 消息处理。需要后续默认处理则需要返回0;停止该消息后续处理,则返回1
int XqWindow::HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	return 0;
}

// 原始窗口过程
LRESULT CALLBACK XqWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	XqWindow* pObj = NULL;
	if (message == WM_CREATE)	// 在此消息收到时,把窗口对象句柄赋给C++对象成员,同时把C++对象地址赋给窗口对象成员
	{
		pObj = (XqWindow*)(((LPCREATESTRUCT)lParam)->lpCreateParams);
		pObj->hWnd = hWnd;	                                         // 在此处获取HWND,此时CreateWindow()尚未返回。
		::SetWindowLong(hWnd, GWL_USERDATA, (LONG)pObj);             // 通过USERDATA把HWND和C++对象关联起来
	}
	pObj = (XqWindow*)::GetWindowLong(hWnd, GWL_USERDATA);

	switch (message)
	{
	case WM_CREATE:
		pObj->HandleMessage(hWnd, message, wParam, lParam);
		break;
	case WM_DESTROY:
		if (pObj != NULL)  // 此时,窗口对象已经销毁,通过设置hWnd=NULL,来通知C++对象
		{
			pObj->hWnd = NULL;
		}
		break;
	default:
		pObj = (XqWindow*)::GetWindowLong(hWnd, GWL_USERDATA);
		if (pObj != NULL)
		{
			if (pObj->HandleMessage(hWnd, message, wParam, lParam) == 0) // 调用子类的消息处理虚函数
			{
				return DefWindowProc(hWnd, message, wParam, lParam);
			}
		}
		else
		{
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;
	}
	return 0;
}

2.3 使用举例

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

TestWindow.h

#pragma once
#include "XqWindow.h"
class TestWindow :
 public XqWindow
{
public:
 TestWindow(HINSTANCE);
 ~TestWindow();
protected:
 int HandleMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
private:
 // 业务数据部分
 int rectWidth; 
 int rectHeight;
};

TestWindow.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;
}

调用部分:

	pTest = new TestWindow(theApp.m_hInstance);
	pTest->Create();
	::ShowWindow(pTest->GetHandle(), SW_SHOW);
	::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++作出这种改变的可能性不大。

3 一点感受

XqWindow类的源码一共不到150行,却花了我2天的业余时间来完成。这里涉及到对C++对象内存布局,窗口创建、销毁、消息处理过程的深入理解。写一个小小类就如此不易,写一个健壮的类库真是难上加难,想想MFC也真的挺不容易的。

关于这个类,大家有什么好的想法,欢迎交流探讨。

时间: 2024-10-16 17:29:13

重温WIN32 API ------ 最简单的Windows窗口封装类的相关文章

C语言调用WIN32 API教程之1创建窗口

本学习笔记基于VC++6.0开发环境,通过c语言编程语言,调用win32 API进行windows系统应用程序开发. 1,打开VC++6.0,点击 文件->新建->工程->Win32 Application 工程名填写example1,点击确定,选择 一个空工程,点击完成. 2,点击"新建文件" 按钮,新建一个空白文件,点击 文件->另存为 输入文件名example1.c 选择工作空间对应的文件夹,保存. 3,点击FileView,右击Source File,点

C# Windows API应用之FlashWindowEx ——实现窗口闪烁的方法

Windows API Windows 这个多作业系统除了协调应用程序的执行.分配内存.管理资源-之外, 它同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程式达到开启视窗.描绘图形.使用周边设备等目的,由于这些函数服务的对象是应用程序(Application), 所以便称之为 Application Programming Interface,简称 API 函数.WIN32 API也就是Microsoft Windows 32位平台的应用程序编程接口

从.NET平台调用Win32 API

小序        Win32 API可以直接控制Microsoft Windows的核心,因为API(Application Programming Interface)本来就是微软留给我们直接控制Windows的接口.想玩儿吗?呵呵,太难了.        C#使用非常简单,写程序就像打拱猪,Sorry  -_-! ,搭积木一样简单.想玩儿吗?呵呵,没办法直接控制Windows的核心.        难道就没有两全其美的办法吗?当然不是!要不微软的产品早就没人买了.其实从C#(或者说.NET

C#调用Win32 api学习总结

从.NET平台调用Win32 API Win32 API可以直接控制Microsoft Windows的核心,因为API(Application Programming Interface)本来就是微软留给我们直接控制Windows的接口. 一.    基础知识 Win32 API是C语言(注意,不是C++语言,尽管C语言是C++语言的子集)函数集. 1. Win32 API函数放在哪里? Win32 API函数是Windows的核心,比如我们看到的窗体.按钮.对话框什么的,都是依靠Win32函

第一个Windows窗口应用程序

学习目的 熟悉开发工具Visual C++ 6.0和MSDN 2001的使用. 应用Windows API函数, 手工编写具有最基本构成的Windows窗口应用程序(包含WinMain入口函数, 消息循环, 窗口函数), 并调试成功. 1.熟悉开发工具 熟悉开发工具visual studio的使用: 在visual studio中新建win32空项目 ? 2.熟悉MSDN帮助的使用 练习使用MSDN查询windows相关函数信息 ? 3. 应用Windows API函数, 手工编写具有最基本构成

MFC_1——采用windows API函数来生成一个窗口显示helloword

//采用windows API函数来生成一个窗口显示helloword: #include <windows.h> LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam); //进入WinMain函数 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)

设置windows窗口ICON 【windows 编程】【API】【原创】

1. ICON介绍 最近开始接触windows 编程,因此将自己所接触的一些零散的知识进行整理并记录.本文主要介绍了如何更改windows对话框窗口的ICON图标.这里首先介绍一下windows ICON定义.在我们使用的windows APP上,一般都存在两个ICON.一个是打开APP后显示与左上角的小的图标文件(SMALL ICON)以及按下Alt+Tab切换窗口时显示的一个大的图标文件(BIG ICON).以windows自带的记事本程序为例,小的图标文件如下图1所示: 图1 记事本程序的

[MFC]_在vs2019中使用MFC快速构建简单windows窗口程序

微软基础类库(英语: Classes,简称MFC)是微软公司提供的一个类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量.其中包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类. vs 2019 最新版,在设计上又有了很大的变化,并且其所有的服务,模块都是自定义搭建的,所以在一开始安装的时候,没有勾选mfc模块的话,是无法快速构成mfc应用的. vs2019下MFC模块的安装:

API、Win32 SDK、Win32项目、MFC、Windows窗体应用程序的区别

[原]API.Win32 SDK.Win32项目.MFC.Windows窗体应用程序的区别 首先来看一下每一个术语的定义: API:Application Programming Interface.Windows操作系统提供给应用程序编程的接口, 简称 为API函数. Win32 SDK:SDK(Software Development Kit)中文是软件开发包.则Win32 SDK是Windows 32位平台下的软件开发包,包括了API函数.帮助文档.微软 提供的一些辅助开发工具. Win3