什么是Windows句柄?


153

在Windows中讨论资源时,“句柄”是什么?它们如何运作?

Answers:


167

它是对资源(通常是内存,打开的文件或管道)的抽象引用值。

正确地,在Windows中(通常在计算中),句柄是一种抽象,它向API用户隐藏了真实的内存地址,从而允许系统对程序透明地重组物理内存。将句柄解析为指针会锁定内存,释放该句柄会使指针无效。在这种情况下,可以将其视为指向指针表的索引...您可以将索引用于系统API调用,系统可以随意更改表中的指针。

另外,当API编写者希望使API用户与返回的地址所指向的内容隔离时,可以使用实指针作为句柄。在这种情况下,必须考虑到句柄指向的内容可能随时发生变化(从API版本到版本,甚至从返回句柄的API的调用到调用),因此,该句柄应被视为简单的不透明值仅对 API 有意义。

我应该补充一点,在任何现代操作系统中,即使所谓的“真实指针”仍然是进程虚拟内存空间中的不透明句柄,这使O / S能够在不使进程内的指针无效的情况下管理和重新排列内存。


4
我非常感谢您的迅速反应。不幸的是,我认为我还是一个新手,仍然不能完全理解它:-(
Al C

4
我的扩展答案是否有任何启示?
劳伦斯·多尔

100

A HANDLE是特定于上下文的唯一标识符。特定于上下文的意思是,从一个上下文获得的句柄不一定可以在也适用于HANDLEs的任何其他框架上下文中使用。

例如,GetModuleHandle将唯一标识符返回到当前加载的模块。返回的句柄可以在接受模块句柄的其他函数中使用。不能将其提供给需要其他类型的句柄的函数。例如,您无法提供从返回的句柄GetModuleHandleHeapDestroy并期望它做一些明智的事情。

HANDLE本身只是一个整数类型。通常,但不是必须的,它是指向某些基础类型或内存位置的指针。例如,HANDLE返回的GetModuleHandle实际上是指向模块基本虚拟内存地址的指针。但是没有规则说明句柄必须是指针。句柄也可以只是一个简单的整数(某些Win32 API可能将其用作数组的索引)。

HANDLEs是故意不透明的表示形式,可提供来自内部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的代码的可移植性。


5
不管您是否有意,都是对的-这个概念肯定是不透明的(至少对我而言:-)
Al C

5
我用一些具体示例扩展了我的原始答案。希望这会使概念更透明。
丹·莫丁

2
非常有用的扩展...谢谢!
Al C

4
对于我一段时间以来遇到的任何问题,这必须是最干净,直接,最书面的答复之一。衷心感谢您抽出宝贵的时间来编写它!
安德鲁

@DanMoulding:因此,使用handle代替的主要原因void *不鼓励用户代码尝试准确地找出void *指向的内容。我对么?
狮Lion

37

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的状态。

有关更多信息,请参见句柄和数据类型


通过HANDLEADT 引用的对象由内核管理。HWND另一方面,您命名的其他句柄类型(等)是USER对象。这些不是由Windows内核管理的。
IInspectable

1
@IInspectable猜测那些是由User32.dll东西管理的?
the_endian '16

8

句柄是Windows管理的对象的唯一标识符。它就像一个指针,但不是一个指针,因为它不是用户代码可以取消引用以访问某些数据的地址。取而代之的是将句柄传递给一组函数,这些函数可以对该句柄标识的对象执行操作。


5

因此,在最基本的层次上,任何形式的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
这是有缺陷的推理;C内存分配子系统不能只是使指针无效。否则,任何C或C ++程序都无法证明是正确的。更糟糕的是,任何具有足够复杂性的程序从定义上都将证明是错误的。此外,如果直接指向的内存在程序下方移动,则双重间接寻址也无济于事,除非指针本身实际上是对实际内存的抽象-这将使其成为句柄
劳伦斯·多尔

1
Macintosh操作系统(版本最高为9或8)完成了上述操作。如果分配了一些系统对象,则通常会得到它的句柄,从而使OS可以自由移动对象。由于首批Mac的内存有限,这一点非常重要。
Rhialto支持Monica 2015年

5

句柄就像数据库中记录的主键值。

编辑1:好吧,为什么downvote,主键唯一地标识数据库记录,而Windows系统中的句柄唯一地标识窗口,打开的文件等,这就是我的意思。


1
我无法想象您可以断言该句柄是唯一的。对于每个用户的Windows Station,它可能是唯一的,但如果有多个用户同时访问同一系统,则不能保证它是唯一的。也就是说,多个用户可以获取一个数字上相同的句柄值,但是在用户的Windows Station上下文中,它们映射到不同的事物……
Nick

2
@nick在给定的上下文中是唯一的。主键在不同的表之间也不会唯一...
Benny Mackney

2

可以将Windows中的窗口视为描述它的结构。该结构是Windows的内部组成部分,您无需了解其详细信息。相反,Windows为该结构的结构体指针提供了typedef。那就是您可以抓住窗户的“把手”。


没错,但始终值得记住的是,句柄通常不是内存地址,并且一个用户代码不应取消引用它。
锐齿2009年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.