Answers:
它是对资源(通常是内存,打开的文件或管道)的抽象引用值。
正确地,在Windows中(通常在计算中),句柄是一种抽象,它向API用户隐藏了真实的内存地址,从而允许系统对程序透明地重组物理内存。将句柄解析为指针会锁定内存,释放该句柄会使指针无效。在这种情况下,可以将其视为指向指针表的索引...您可以将索引用于系统API调用,系统可以随意更改表中的指针。
另外,当API编写者希望使API用户与返回的地址所指向的内容隔离时,可以使用实指针作为句柄。在这种情况下,必须考虑到句柄指向的内容可能随时发生变化(从API版本到版本,甚至从返回句柄的API的调用到调用),因此,该句柄应被视为简单的不透明值仅对 API 有意义。
我应该补充一点,在任何现代操作系统中,即使所谓的“真实指针”仍然是进程虚拟内存空间中的不透明句柄,这使O / S能够在不使进程内的指针无效的情况下管理和重新排列内存。
A HANDLE
是特定于上下文的唯一标识符。特定于上下文的意思是,从一个上下文获得的句柄不一定可以在也适用于HANDLE
s的任何其他框架上下文中使用。
例如,GetModuleHandle
将唯一标识符返回到当前加载的模块。返回的句柄可以在接受模块句柄的其他函数中使用。不能将其提供给需要其他类型的句柄的函数。例如,您无法提供从返回的句柄GetModuleHandle
,HeapDestroy
并期望它做一些明智的事情。
在HANDLE
本身只是一个整数类型。通常,但不是必须的,它是指向某些基础类型或内存位置的指针。例如,HANDLE
返回的GetModuleHandle
实际上是指向模块基本虚拟内存地址的指针。但是没有规则说明句柄必须是指针。句柄也可以只是一个简单的整数(某些Win32 API可能将其用作数组的索引)。
HANDLE
s是故意不透明的表示形式,可提供来自内部Win32资源的封装和抽象。这样,Win32 API可能会更改HANDLE背后的基础类型,而不会以任何方式影响用户代码(至少是这样)。
考虑一下我刚刚编写的Win32 API的这三种不同的内部实现,并假定它Widget
是一个struct
。
Widget * GetWidget (std::string name)
{
Widget *w;
w = findWidget(name);
return w;
}
void * GetWidget (std::string name)
{
Widget *w;
w = findWidget(name);
return reinterpret_cast<void *>(w);
}
typedef void * HANDLE;
HANDLE GetWidget (std::string name)
{
Widget *w;
w = findWidget(name);
return reinterpret_cast<HANDLE>(w);
}
第一个示例公开了有关API的内部细节:它允许用户代码知道GetWidget
返回指向的指针struct Widget
。这有两个后果:
Widget
结构的头文件Widget
结构的内部部分这两种后果都是不希望的。
第二个示例通过返回just,从用户代码中隐藏了此内部细节void *
。用户代码不需要访问定义该Widget
结构的标头。
第三个示例与第二个示例完全相同,但是我们仅调用void *
a HANDLE
。也许这会阻止用户代码尝试确切void *
指出要指向的内容。
为什么要经历这个麻烦?考虑这个相同API的更新版本的第四个示例:
typedef void * HANDLE;
HANDLE GetWidget (std::string name)
{
NewImprovedWidget *w;
w = findImprovedWidget(name);
return reinterpret_cast<HANDLE>(w);
}
请注意,该函数的接口与上面的第三个示例相同。这意味着,即使“幕后”实现已改为使用NewImprovedWidget
结构,用户代码也可以继续使用此新版本的API,而无需进行任何更改。
这些示例中的句柄实际上只是一个新的,大概更友好的名称void *
,它恰好HANDLE
是Win32 API中的名称(在MSDN上查找)。它在用户代码和Win32库的内部表示之间提供了不透明的墙,从而增加了Windows版本之间使用Win32 API的代码的可移植性。
handle
代替的主要原因void *
是不鼓励用户代码尝试准确地找出void *指向的内容。我对么?
Win32编程中的HANDLE是一个令牌,表示由Windows内核管理的资源。句柄可以是窗口,文件等。
句柄只是使用Win32 API标识要使用的特定资源的一种方式。
因此,例如,如果要创建一个Window并将其显示在屏幕上,可以执行以下操作:
// Create the window
HWND hwnd = CreateWindow(...);
if (!hwnd)
return; // hwnd not created
// Show the window.
ShowWindow(hwnd, SW_SHOW);
在上面的示例中,HWND表示“窗口的句柄”。
如果您习惯了面向对象的语言,则可以将HANDLE视为没有任何方法的类的实例,其状态只能由其他函数修改。在这种情况下,ShowWindow函数会修改Window HANDLE的状态。
有关更多信息,请参见句柄和数据类型。
HANDLE
ADT 引用的对象由内核管理。HWND
另一方面,您命名的其他句柄类型(等)是USER对象。这些不是由Windows内核管理的。
因此,在最基本的层次上,任何形式的HANDLE都是指向指针的指针或
#define HANDLE void **
现在,为什么要使用它
让我们进行设置:
class Object{
int Value;
}
class LargeObj{
char * val;
LargeObj()
{
val = malloc(2048 * 1000);
}
}
void foo(Object bar){
LargeObj lo = new LargeObj();
bar.Value++;
}
void main()
{
Object obj = new Object();
obj.val = 1;
foo(obj);
printf("%d", obj.val);
}
因此,因为obj是通过将值(将其复制并提供给函数)传递给foo的,所以printf将输出原始值1。
现在,如果我们将foo更新为:
void foo(Object * bar)
{
LargeObj lo = new LargeObj();
bar->val++;
}
printf可能会打印更新后的值2。但是foo也可能会导致某种形式的内存损坏或异常。
原因是因为当您现在使用指针将obj传递给您还要分配2 Meg内存的函数时,这可能会导致OS在更新obj的位置周围移动内存。由于已按值传递了指针,因此如果obj被移动,则OS会更新指针,但不会更新函数中的副本,并且可能会导致问题。
foo的最终更新:
void foo(Object **bar){
LargeObj lo = LargeObj();
Object * b = &bar;
b->val++;
}
这将始终打印更新的值。
请参见,当编译器为指针分配内存时,它将它们标记为不可移动,因此,由分配的大对象引起的任何内存重新转换都会传递给函数,该值将指向正确的地址,以找出内存中的最终位置。更新。
任何特定类型的HANDLE(hWnd,FILE等)都是特定于域的,它们指向某种类型的结构以防止内存损坏。
句柄就像数据库中记录的主键值。
编辑1:好吧,为什么downvote,主键唯一地标识数据库记录,而Windows系统中的句柄唯一地标识窗口,打开的文件等,这就是我的意思。