在 Windows 中讨论资源时,什么是“句柄”?它们是如何工作的?
7 回答
它是对资源的抽象引用值,通常是内存或打开的文件或管道。
正确地说,在 Windows 中(通常在计算中),句柄是一种抽象,它向 API 用户隐藏真实内存地址,允许系统对程序透明地重新组织物理内存。将句柄解析为指针会锁定内存,释放句柄会使指针无效。在这种情况下,可以将其视为指针表的索引……您将索引用于系统 API 调用,系统可以随意更改表中的指针。
或者,当 API 编写者希望 API 的用户与返回的地址指向的具体内容隔离开时,可以给出一个真正的指针作为句柄;在这种情况下,必须考虑句柄指向的内容可能随时更改(从 API 版本到版本,甚至从返回句柄的 API 的调用到调用) - 因此句柄应被视为简单的不透明值仅对API有意义。
我应该补充一点,在任何现代操作系统中,即使是所谓的“真实指针”仍然是进程虚拟内存空间的不透明句柄,这使得 O/S 能够管理和重新排列内存而不会使进程内的指针失效.
AHANDLE
是特定于上下文的唯一标识符。通过上下文特定,我的意思是从一个上下文获得的句柄不一定用于任何其他也适用于HANDLE
s 的任意上下文。
例如,GetModuleHandle
为当前加载的模块返回一个唯一标识符。返回的句柄可以在其他接受模块句柄的函数中使用。它不能提供给需要其他类型句柄的函数。例如,您不能给出一个从GetModuleHandle
to返回的句柄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
结构的内部部分
这两种后果都可能是不可取的。
第二个示例通过仅返回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);
}
请注意,该函数的接口与上面的第三个示例相同。这意味着用户代码可以继续使用这个新版本的 API,而无需进行任何更改,即使“幕后”实现已更改为使用NewImprovedWidget
结构体。
这些示例中的句柄实际上只是一个新的、可能更友好的名称 for void *
,这正是 Win32 API 中的 a (在 MSDNHANDLE
中查找)。它在用户代码和 Win32 库的内部表示之间提供了一道不透明的墙,从而增加了使用 Win32 API 的代码在 Windows 版本之间的可移植性。
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 的状态。
有关详细信息,请参阅句柄和数据类型。
句柄是由 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 是按值传递的(制作一个副本并将其提供给函数),所以 printf 将打印原始值 1。
现在,如果我们将 foo 更新为:
void foo(Object * bar)
{
LargeObj lo = new LargeObj();
bar->val++;
}
printf 有可能会打印更新后的值 2。但也有可能 foo 会导致某种形式的内存损坏或异常。
原因是当您现在使用指针将 obj 传递给函数时,您还分配了 2 Megs 的内存,这可能导致操作系统在更新 obj 的位置时移动内存。由于您已按值传递指针,因此如果 obj 被移动,则操作系统会更新指针而不是函数中的副本,并可能导致问题。
对 foo 的最终更新:
void foo(Object **bar){
LargeObj lo = LargeObj();
Object * b = &bar;
b->val++;
}
这将始终打印更新的值。
看,当编译器为指针分配内存时,它会将它们标记为不可移动,因此由分配给函数的值的大对象引起的任何内存重新洗牌将指向正确的地址,以找出内存中的最终位置更新。
任何特定类型的 HANDLE(hWnd、FILE 等)都是特定于域的,并指向特定类型的结构以防止内存损坏。
句柄就像数据库中记录的主键值。
编辑1:好吧,为什么downvote,主键唯一标识数据库记录,Windows系统中的句柄唯一标识窗口,打开的文件等,这就是我要说的。
将 Windows 中的窗口视为描述它的结构。这个结构是 Windows 的一个内部部分,你不需要知道它的细节。相反,Windows 为该结构的结构指针提供了 typedef。那是你可以抓住窗户的“把手”。