在Windows中讨论资源时,什么是“句柄”?他们是如何运作的?
答案 0 :(得分:147)
它是资源的抽象引用值,通常是内存或打开文件或管道。
正确,在Windows中,(通常在计算中)句柄是一种抽象,它隐藏了API用户的真实内存地址,允许系统透明地重新组织物理内存到程序。将句柄解析为指针会锁定内存,释放句柄会使指针无效。在这种情况下,将其视为指针表的索引...您使用索引进行系统API调用,系统可以随意更改表中的指针。
当API编写者打算将API的用户与返回的地址的具体内容隔离时,可以给出真实指针作为句柄;在这种情况下,必须考虑句柄指向的内容可能随时发生变化(从API版本到版本,甚至从调用到调用返回句柄的API) - 因此句柄应该被视为简单的不透明值只有 到API。
我应该补充说,在任何现代操作系统中,即使是所谓的“真实指针”仍然是进程虚拟内存空间的不透明句柄,这使得O / S能够管理和重新安排内存而不会使指针无效在这个过程中。
答案 1 :(得分:88)
HANDLE
是特定于上下文的唯一标识符。根据特定于上下文,我的意思是从一个上下文获得的句柄不一定可以在任何其他可用于HANDLE
s的上游语境中使用。
例如,GetModuleHandle
向当前加载的模块返回唯一标识符。返回的句柄可用于接受模块句柄的其他函数。它不能用于需要其他类型句柄的函数。例如,您无法提供从GetModuleHandle
返回到HeapDestroy
的句柄,并希望它能做一些明智的事情。
HANDLE
本身只是一个整体类型。通常,但不一定,它是指向某些底层类型或内存位置的指针。例如,HANDLE
返回的GetModuleHandle
实际上是指向模块的基本虚拟内存地址的指针。但是没有规则说句柄必须是指针。句柄也可以只是一个简单的整数(某些Win32 API可能会将其用作数组的索引)。
HANDLE
是故意不透明的表示,提供内部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
struct Widget
struct 这两种后果都可能是不可取的。
第二个示例通过仅返回void *
来隐藏用户代码中的内部详细信息。用户代码不需要访问定义Widget
结构的头。
第三个示例与第二个示例完全相同,但我们只需将void *
称为HANDLE
。也许这会阻止用户代码试图找出void *
指向的确切内容。
为什么要经历这个麻烦?考虑这个相同API的较新版本的第四个示例:
typedef void * HANDLE;
HANDLE GetWidget (std::string name)
{
NewImprovedWidget *w;
w = findImprovedWidget(name);
return reinterpret_cast<HANDLE>(w);
}
请注意,该函数的界面与上面的第三个示例相同。这意味着即使“幕后”实现已更改为使用NewImprovedWidget
结构,用户代码仍可继续使用此新版本的API,而无需任何更改。
这些示例中的句柄实际上只是void *
的一个新的,可能更友好的名称,这正是Win32 API中的HANDLE
(查找at MSDN) 。它在用户代码和Win32库的内部表示之间提供了一个不透明的墙,它增加了Windows版本之间使用Win32 API的代码的可移植性。
答案 2 :(得分:33)
Win32编程中的HANDLE是一个表示由Windows内核管理的资源的标记。句柄可以是窗口,文件等。
句柄只是一种使用Win32 API识别要使用的微粒资源的方法。
例如,如果您想创建一个窗口并在屏幕上显示它,您可以执行以下操作:
// Create the window
HWND hwnd = CreateWindow(...);
if (!hwnd)
return; // hwnd not created
// Show the window.
ShowWindow(hwnd, SW_SHOW);
在上面的示例中,HWND表示“窗口的句柄”。
如果您习惯于面向对象的语言,您可以将HANDLE视为一个类的实例,没有方法,其状态只能由其他函数修改。在这种情况下, ShowWindow 函数会修改Window HANDLE的状态。
有关详细信息,请参阅Handles and Data Types。
答案 3 :(得分:7)
句柄是Windows管理的对象的唯一标识符。它就像一个指针,但不是一个指针,因为它不是一个可以被用户代码取消引用以获取对某些数据的访问权的地址。而是将句柄传递给一组函数,这些函数可以对句柄识别的对象执行操作。
答案 4 :(得分:5)
句柄就像数据库中记录的主键值。
编辑1:好吧,为什么downvote,主键唯一标识数据库记录,Windows系统中的句柄唯一标识一个窗口,一个打开的文件等,这就是我所说的。
答案 5 :(得分:4)
因此,在最基本的层面上,任何类型的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兆内存的函数时,这可能导致操作系统移动内存更新obj的位置。由于你已经按值传递指针,如果obj被移动,那么操作系统会更新指针而不是函数中的副本,并可能导致问题。
对foo的最后更新:
void foo(Object **bar){
LargeObj lo = LargeObj();
Object * b = &bar;
b->val++;
}
这将始终打印更新后的值。
请参阅,当编译器为指针分配内存时,它将它们标记为不可移动,因此由大对象分配传递给函数的值导致的内存重新重排将指向正确的地址以找出最终位置在内存中更新。
任何特定类型的HANDLE(hWnd,FILE等)都是特定于域的,并指向某种类型的结构以防止内存损坏。
答案 6 :(得分:2)
将Windows中的窗口视为描述它的结构。此结构是Windows的内部部分,您无需了解它的详细信息。相反,Windows为该结构的struct提供了一个typedef。这就是你可以抓住窗户的“手柄”。