.NET对象与Windows句柄(一):句柄的基本概念

时间:2021-08-10 06:17:45

在.NET编程中,得益于有效的内存管理机制,对象的创建和使用比较方便,大多数情况下我们无须关心对象创建和分配内存的细节,也可以放心的把对象的清理交给自动垃圾回收来完成。由于.NET类库对系统底层对象进行了封装,我们也不需要调用Windows API来操作非托管对象。但不直接操作非托管对象,并不意味着程序不会间接创建这些对象,如果不了解.NET对象与非托管资源的关系,我们很有可能因为不恰当的使用这些托管对象,而导致非托管资源泄露。本文尝试说明Windows对象和句柄的基本概念,以及.NET编程中的对象与它们的关系,并结合一些简单的示例程序来探讨句柄泄露的话题。

一、什么是句柄?

Windows编程中,程序需要访问各种各样的资源,如文件、网络、窗口、图标和线程等。不同类型的资源被系统封装成不同的数据结构,当需要使用这些资源时,程序需要依据这些数据结构创建出不同的对象,当操作完毕并不再需要这些对象时,程序应当及时释放它们。在Windows中,应用程序不能直接在内存中操作这些对象,而是通过一系列公开的Windows API由对象管理器(Object Manager)来创建、访问、跟踪和销毁这些对象。当调用这些API创建对象时,它们并不直接返回指向对象的指针,而是会返回一个32位或64位的整数值,这个在进程或系统范围内唯一的整数值就是句柄(Handle)。随后程序再次访问对象,或者删除对象,都将句柄作为Windows API的参数来间接对这些对象进行操作。在这个过程中,句柄作为系统中对象的标识来使用。

对象管理器是系统提供的用来统一管理所有Windows内部对象的系统组件。这里所说的内部对象,不同于高级编程语言如C#中“对象”的概念,而是由Windows内核或各个组件实现和使用的对象。这些对象及其结构,要么不对用户代码公开,要么只能使用句柄由封装好的Windows API进行操作。C#编程中,多数情况下,我们并不需要与这些Windows API打交道,这是因为.NET类库对这些API又进行了封装,但我们的托管程序仍然会间接创建出很多Windows内部对象,并持有它们的句柄。

如上所说,句柄是一个32位或64位的整数值(取决于操作系统),所以在32位系统中,C#完全可以用int来表示一个句柄。但.NET提供了一个结构体System.IntPtr专门用来代表句柄或指针,在需要表示句柄,或者要在unsafe代码中使用指针时,应当使用IntPtr类型。

二、C#中创建文件句柄的过程

举例来说,文件属于一种非托管的系统资源。在C#中,可以用File类的静态方法Open来得到一个FileStream对象,来对磁盘文件进行读写操作。FileStream对象本身是托管对象,它是如何与文件这个非托管资源产生联系的呢?

大致说来,C#中打开文件的操作会经过下列步骤:

调用.NET静态方法System.IO.File.Open时,File类会创建一个FileStream对象并传入必要的参数,如文件路径,FileMode和FileAccess选项。FileMode枚举表明是希望创建新文件,打开已有文件,覆盖原有文件或是在原文件上追加新内容;FileAccess枚举表明是希望读文件、写文件或两者都有。

接着FileStream调用自己的Init方法进行初始化,在这个过程中,有更多细节需要考虑。为了创建一个文件,初始化方法需要更多额外的信息和检查,比如本进程在使用文件时是否允许其它进程读写文件,文件路径是否有效,是否有足够的权限,目标文件是否是允许被访问的文件类型,是否正确设置了FileMode和FileAccess选项的组合等。

完成这些必要的检查后,FileStream.Init调用Win32Native.SafeCreateFile方法。

Win32Native类封闭了大量的Windows API,SafeCreateFile方法以P/Invoke的方式调用kernel32.dll中的CreateFile API,并返回SafeFileHandle。SafeFileHandle是一个有趣的类型,继承自SafeHandle,包含了真正的IntPtr类型的文件句柄。.NET的设计者有意让这个句柄字段对外不可见,但如果你非要拿到这个句柄值,SafeFileHandle也提供了DangerousGetHandle()方法满足你的要求:都告诉你Dangerous了,你自己看着办。

包含着文件句柄的SafeFileHandle会被返回并存放在FileStream对象中。随后的读取和写入操作,FileStream都会使用这个句柄与Windows API进行交互,直到最终关闭句柄。至始至终,我们的代码都无需直接关心句柄的存在,FileStream负责了绝大部分工作。

三、通过句柄操作对象的好处

Windows不允许应用程序直接访问内存中更底层的对象,而是由对象管理器统一管理,总的来说,至少有以下好处: