什么是 Windows 句柄?

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

162139 次浏览

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

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

或者,当 API 编写者希望 API 的用户与返回的地址所指向的具体内容隔离时,可以给出一个实际的指针作为句柄; 在这种情况下,必须考虑到句柄所指向的内容可能随时发生变化(从 API 版本到版本,甚至从返回句柄的 API 的调用到调用)——因此,句柄应该被视为一个对 API 有意义的不透明值 只有

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

可以将 Windows 中的窗口视为描述它的结构。这个结构是 Windows 的一个内部部分,您不需要知道它的详细信息。相反,Windows 为该结构体的结构指针提供了 typedef。这就是你可以抓住窗户的“把手”,

Win32编程中的 HANDLE 是表示由 Windows 内核管理的资源的令牌。句柄可以是窗口、文件等。

句柄只是用来标识要使用 Win32API 处理的特定资源的一种方法。

例如,如果你想创建一个窗口,并在屏幕上显示它,你可以这样做:

// Create the window
HWND hwnd = CreateWindow(...);
if (!hwnd)
return; // hwnd not created


// Show the window.
ShowWindow(hwnd, SW_SHOW);

在上面的例子中,HWND 表示“窗口的句柄”。

如果您习惯于使用面向对象的语言,您可以将 HANDLE 看作是没有方法的类的实例,其状态只能由其他函数修改。在这种情况下,演出窗口函数修改 WindowHANDLE 的状态。

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

HANDLE是一个特定于上下文的唯一标识符。通过上下文特定,我的意思是从一个上下文获得的句柄不一定可以用于任何其他同样适用于 HANDLE的上下文。

例如,GetModuleHandle向当前加载的模块返回一个唯一标识符。返回的句柄可用于接受模块句柄的其他函数。它不能给需要其他类型句柄的函数。例如,您不能给出一个从 GetModuleHandle返回到 HeapDestroy的句柄,并期望它做一些合理的事情。

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

HANDLE是有意识地不透明的表示,它提供来自内部 Win32资源的封装和抽象。这样,Win32 API 可以潜在地改变 HANDLE 背后的基础类型,而不会以任何方式影响用户代码(至少这是想法)。

考虑我刚刚编写的 Win32API 的这三个不同的内部实现,并假设 Widgetstruct

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结构的内部部分

这两种后果都可能是不可取的。

第二个示例只返回 void *,从而对用户代码隐藏了这个内部细节。用户代码不需要访问定义 Widget结构的标头。

第三个示例与第二个示例完全相同,但我们只是将 void *称为 HANDLE。也许这会阻止用户代码试图找出 void *到底指向什么。

为什么要这么麻烦呢? 考虑一下这个 API 的新版本的第四个例子:

typedef void * HANDLE;


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


w = findImprovedWidget(name);


return reinterpret_cast<HANDLE>(w);
}

注意,该函数的接口与上面的第三个示例相同。这意味着用户代码可以继续使用这个新版本的 API,而不需要做任何更改,即使“幕后”实现已经更改为使用 NewImprovedWidget结构。

这些例子中的句柄实际上只是 void *的一个新的、可能更友好的名称,这正是 Win32API 中的 HANDLE(查找 在 MSDN)。它在用户代码和 Win32库的内部表示之间提供了一道不透明的墙,从而增加了使用 Win32 API 的代码在 Windows 版本之间的可移植性。

因此,在最基本的级别上,任何类型的句柄都是指向指针或

#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兆内存的函数时,这可能会导致操作系统在更新 obj 位置时移动内存。由于已经通过值传递了指针,因此如果 obj 被移动,那么操作系统将更新指针,但不更新函数中的副本,并可能导致问题。

对 foo 的最后更新:

void foo(Object **bar){
LargeObj lo = LargeObj();
Object * b = &bar;
b->val++;
}

这将始终打印更新后的值。

看,当编译器为指针分配内存时,它会将指针标记为不可移动的,因此,任何由被分配给函数的大型对象引起的内存重新洗牌,都会指向正确的地址,从而找到内存中要更新的最终位置。

任何特定类型的句柄(hWnd、 FILE 等)都是特定于域的,并指向特定类型的结构以防止内存损坏。

句柄是 Windows 管理的对象的唯一标识符。它是 就像指针一样,但是从某种意义上说,不是指针不是一个可以被用户代码取消引用以获得对某些数据的访问的地址。相反,将句柄传递给一组函数,这些函数可以对句柄标识的对象执行操作。

句柄类似于数据库中记录的主键值。

编辑1: 嗯,为什么向下投票,一个主键唯一地标识一个数据库记录,和一个句柄在 Windows 系统中唯一地标识一个窗口,一个打开的文件,等等,这就是我要说的。