《语法篇》HANDLE句柄

发布时间 2023-07-27 15:01:49作者: Fusio

HANDLE是什么

简单理解:HANDLE是一个void指针,作为资源对象的标识号,为什么要用标识号不用地址?因为操作系统不想让我们知道资源对象的地址

详细介绍看下面。

参考链接:https://blog.csdn.net/maowei117/article/details/55254855

最近在接触windows编程,在多线程编程中遇到了这样的语句:

HANDLE mMutex;
mMutex = CreateMutex(NULL, FASLE, NULL);

 这里的HANDLE被翻译为句柄,句柄这个词太抽象了,那么到底什么是一个句柄呢?在MSDN中我们可以看到HANDLE的解释类似于一种能够访问线程、文件、图片等系统资源的指针。但是HANDLE和我们平常的指针又有什么区别呢,为什么又要特意定义一个HANDLE呢?本篇主要讲HANDLE是什么。

HANDEL的定义

我们可以在windows.h中找到HANDLE的定义

typedef void *HANDLE;

可以看到HANDLE的定义很简单,就是一个void * ,但是我们知道void *可以通过强转为任意类型的指针,所以在使用HANDLE的时候,可以用HANDLE指向所有的数据结构(基本数据类型、结构体、类),其实这就是HANDLE使用得很多的原因,因为任何的数据结构都可以用HANDLE来表示。

如果不使用HANDLE

在这里我借用stackoverflow上面的一个例子来说明这个问题。首先我自己需要定义一个结构体Widget,这个结构体中包含了一个id和一个name来存储信息。

struct Widget {
	int id;
	char *name;
};

其他的程序员需要获得其中的id和name信息。那么就需要一个GetWidget方法,来获得某个特定的Widget,这样就能得到其中的id和name信息了。如果没有HANDLE,我们很可能这样写:

Widget * GetWidget (std::string name)
{
	Widget *w;

	w = findWidget(name);

	return w;
}

别人调用我们的方法如下:

Widget *mWidget;
mWidget = GetWidget("first");
mWidget.setId(10);
mWidget.setName("second");

乍看起来好像这样调用没有什么问题,但是实际上存在着很多的隐患。

隐患1

 注意到这个Widget是我们自己定义的一个结构体,比如我们将上面的定义写在Widget.h中,那么用户在使用我们的程序的时候就需要include”Widget.h”,用户需要得到我们定义Widget的头文件才能使用。而且这只是其中的一个结构体,如果我们定义了很多的结构体,分别在不同的头文件中,那么我们不得不把所有的头文件都打个包让用户能下载。很多情况下我们并不希望把自己的设计暴露给用户,可是现在我们不得不这样。

隐患2

 但是如果我们将Widget的头文件暴露给了用户,就意味着用户知道了我的Widget是如何定义的,他们知道在Widget这个结构体中,前4个字节代表id,然后接下来是一个指向name的char *。这种情况下很可能有些不听话的用户会想:“为什么我要按照你给我的函数来得到我需要的数据呢?我现在都知道我要什么数据了,我要这么干:”

Widget *mWidget;
mWidget = GetWidget("first");
mWidget.id = 10;
mWidget.name = "mw";

 在这种情况下,用户直接就绕开了我们为其准备的setId和setName,同样达到了他的目的。但是他却不知道我们的setId和setName除了修改了id,还做了一些别的事情(比如将原来的id和name存储在某个队列中)。这样我们便无法控制用户的操作,很容易导致程序乱套。

隐患3

 因为我们将Widget的定义暴露给了用户,那我们就无法保证他们不会对我们的头文件动手动脚。如果用户不小心将Widget的定义进行了修改,那么程序基本上很难按照我们想象中去运行了。

隐患4

 有一天我们想到了一个更好的方法去定义这个Widget,并且通过这种方式能让软件的性能提升20%!没有程序员能够拒绝这样的诱惑,所以我们当然铆足干劲去重构,去让代码变得更完美,于是我们的Widget变成了这样:

struct newWidget {
	...
};

当然随着我们这个Widget的修改,对于Widget的操作也进行了修改,所以我们有了新的GetWidget:

newWidget GetWidget (std::string name)
{
	newWidget *w;

	w = findWidget(name);

	return w;
}

我们看着自己的重构,看着20%效率的提升,夸着自己是个天才。想到用户要是用上这个版本的程序,会是怎样一副惊讶的表情…等等,用户?用户那里保存的是前一个版本的Widget,但是现在我已经把它改成了newWidget了,那岂不是所有的用户都必须要重新下载新的头文件?明明我所有的接口函数的名字都没有修改,但是因为我修改了我背后的实现,就需要用户重新去下载新的头文件,那这样岂不是每次修改都要全部用户重新下载?想到这里,你只能默默把版本倒回去,所有的修改都泡了汤。

使用了HANDLE

好在有HANDLE这样一个东西。这个时候我定义这个GetWidget就可以这样定义了:

typedef void *HANDLE
HANDLE GetWidget (std::string name)
{
	Widget *w;

	w = findWidget(name);

	return reinterpret_cast<HANDLE>(w);
}

发现了么,不管我们定义了多少结构体,我们都统一使用HANDLE来访问它们。现在我们不需要再为用户提供Widget的定义,而只需要提供HANDLE的定义。Widget的实现被隐藏在内部,当然用户没有办法修改Widget的定义。而且因为HANDLE的定义只是一个void ,用户也不知道这个void 到底指向了怎样的一块区域,也就不敢轻易直接对指向的存储进行改动。
 更加重要的一点是,当我们想修改widget的定义的时候:

typedef void * HANDLE;

HANDLE GetWidget (std::string name)
{
	NewImprovedWidget *w;

	w = findImprovedWidget(name);

	return reinterpret_cast<HANDLE>(w);
}

由于隐藏了实现细节,我们可以根据自己的需要对自己定义的结构体做出改动了。

总结

1、HANDLE提供了一种统一的方式去获得系统资源,并对其进行操作。
2、HANDLE使得程序设计的细节得以被隐藏,从而能够更加方便地对细节的实现进行修改。
3、由于不知道HANDLE所指向的具体的数据结构,所以我们必须根据源码提供者的API使用HANDLE,而且不同种类的HANDLE是无法混用的。例如:GetModuleHandle返回的HANDLE和GetFileHandle返回的HANDLE是不同的,只能在他们对应的函数中使用。

HANDLE的理解

参考链接:https://blog.csdn.net/weixin_45758146/article/details/107050622

HANDLE:句柄,是WINDOWS用来表示对象的,是一个通用句柄表示。
在WINDOWS程序中,有各种各样的资源(窗口、图标、光标等),系统在创建这些资源时为他们分配内存,并返回标示这些资源的标示号,即句柄。
但是如果这些资源的位置变了呢?
HANDLE是固定的,不会变,但是对象的地址会变,当对象在内存中的位置发生改变后,我们不能通过之前的对象指针找到对象。HANDLE能用来记录对象的最新地址。
也就是说,HANDLE像是中间商,联络着WINDOWS API和看不见的对象,所以可以通过HANDLE让对象做事。(不能让我们知道对象的内存地址是因为操作系统怕受到不利操作)。

各种HANDLE的定义,如HDC,HPEN,HINSTANCE等等,你会发现有这样一个声明:

DECLARE_HANDLE(HDC);

再把DECLARE_HANDLE这个宏展开:

#define DECLARE_HANDLE(name) struct name##__ { int unused; }; 
typedef struct name##__ *name

用HDC替换上面的name:

struct HDC__

{

int unused;

};

typedef struct HDC__ *HDC

所以句柄就是一个指向某一结构的指针,这个结构体只有一个成员,它是一个整数。
HANDLE的定义,在winnt.h头文件中:

typedef PVOID HANDEL;

PVOID是什么?

typedef void *PVOID;

HANDLE不过是一个指向void型,即无类型的指针,嗯,目前的指针是32位的吧.其实也不能说HANDLE是一种指针,它只充当一种索引的作用。