在.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#中打开文件的操作会经过下列步骤:

  1. 调用.NET静态方法System.IO.File.Open时,File类会创建一个FileStream对象并传入必要的参数,如文件路径,FileMode和FileAccess选项。FileMode枚举表明是希望创建新文件,打开已有文件,覆盖原有文件或是在原文件上追加新内容;FileAccess枚举表明是希望读文件、写文件或两者都有。
  2. 接着FileStream调用自己的Init方法进行初始化,在这个过程中,有更多细节需要考虑。为了创建一个文件,初始化方法需要更多额外的信息和检查,比如本进程在使用文件时是否允许其它进程读写文件,文件路径是否有效,是否有足够的权限,目标文件是否是允许被访问的文件类型,是否正确设置了FileMode和FileAccess选项的组合等。
  3. 完成这些必要的检查后,FileStream.Init调用Win32Native.SafeCreateFile方法。
  4. Win32Native类封闭了大量的Windows API,SafeCreateFile方法以P/Invoke的方式调用kernel32.dll中的CreateFile API,并返回SafeFileHandle。SafeFileHandle是一个有趣的类型,继承自SafeHandle,包含了真正的IntPtr类型的文件句柄。.NET的设计者有意让这个句柄字段对外不可见,但如果你非要拿到这个句柄值,SafeFileHandle也提供了DangerousGetHandle()方法满足你的要求:都告诉你Dangerous了,你自己看着办。
  5. 包含着文件句柄的SafeFileHandle会被返回并存放在FileStream对象中。随后的读取和写入操作,FileStream都会使用这个句柄与Windows API进行交互,直到最终关闭句柄。至始至终,我们的代码都无需直接关心句柄的存在,FileStream负责了绝大部分工作。

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

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

  1. 在操作系统层面上,为所有程序使用系统资源提供了统一的接口和机制。如果没有对象管理器,不同程序会有各种各样的实现方式来访问资源,并且这些代码散落在各种,难以规范,也无从协调解决资源的争用。
  2. 将需要在系统级别保护的对象隔离起来,提供更高安全性。
  3. 所有对系统关键资源的访问都经由对象管理器,使得系统可以方便的追踪和限制资源的使用,进行权限控制。

四、查看进程的句柄数量

到现在为止,本文讨论的全是看不见的概念,有必要来直观的看一下系统中的句柄使用情况。有多种方式可以查看进程的句柄使用情况,先从两个工具开始,Windows任务管理器和Process Explorer。

任务管理器默认不显示句柄数,需要在“查看”-“选择列”中勾选“句柄数”后,才会显示进程中当前打开的句柄数量。如下图所示,可以看到记事本进程当前打开59个句柄。

系统自带的任务管理器查看句柄数量很方便,但如果想知道这些句柄具体是什么,可以使用Process Explorer。Process Explorer是Windows Sysinternals工具包中的一个进程查看器,可以从这里下载。如果你看到的视图跟下图不同,可以点击View,选中Show Lower Pane,并在Lower Pane View中选择Handles。在列表中选择进程后,下方面板中会显示该进程中句柄的详细列表。

五、为什么关注句柄数

句柄指向的是诸如窗口、线程、文件、菜单、进程和定时器之类的系统资源,和所有被称为“资源”的事物一样,稀缺性是它们共同的特点。对于计算机和操作系统来讲,内存是一种稀缺资源,而所有的句柄和对象都存储在内存中。基于这个事实,操作系统不允许进程无限制的创建对象和句柄。对于任务管理器中的“句柄数”来讲,每一进程允许打开的句柄数理论上来讲可达2^24个,但由于内存的限制,实际数字大打折扣。在我的测试中,32位的.NET进程“句柄数”在达到1500万以上后,程序开始出现各种各样的问题。事实上绝大多数程序不会使用到这么多句柄,除非特殊需要,在软件编程中,如果自己的程序“句柄数”上千甚至是几千时,就需要引起特别注意,这一般说明程序中已经存在句柄泄露的情况。

你可能已经留意到,本文前面任务管理器中,除了显示进程的“句柄数”之外,还显示了“用户对象”和“GDI对象”的数量,它们属于另外两种句柄。具体的区别我们将在后面介绍,现在我们需要清楚的是,系统对于这两种对象同样设置了数量限制。对于“用户对象”和“GDI对象”来说,每个进程允许创建的数量上限是在注册表中设定的,分别是HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows中的USERProcessHandleQuota项和GDIProcessHandleQuota项,在Windows 7的32位操作系统上,两个项都被默认设置为10000。你可以更改这个设置,用户对象最多只能设定为18000个,GDI对象最多为65536个。但是改变这个设置是不被推荐的,一般情况下当你的应用程序需要用到超过10000个用户对象或GDI对象时,应该首先检查哪里出现了句柄泄露,而不是更改上限数量;另一方面,更改上限并不意味着应用程序就真的可以创建和使用这么多对象句柄,实际可用的数量同时受制于当前系统可用内存。

 
作者:文禾
本文内容为原创,版权归作者所有,转载请保留原作者署名和出处。

.NET对象与Windows句柄(一):句柄的基本概念的更多相关文章

  1. .NET对象与Windows句柄(三):句柄泄露实例分析

    在上篇文章.NET对象与Windows句柄(二):句柄分类和.NET句柄泄露的例子中,我们有一个句柄泄露的例子.例子中多次创建和Dispose了DataReceiver和DataAnalyzer对象, ...

  2. .NET对象与Windows句柄(二):句柄分类和.NET句柄泄露的例子

    上一篇文章介绍了句柄的基本概念,也描述了C#中创建文件句柄的过程.我们已经知道句柄代表Windows内部对象,文件对象就是其中一种,但显然系统中还有更多其它类型的对象.本文将简单介绍Windows对象 ...

  3. 【C# 线程】转载 句柄的基本概念 .NET对象与Windows句柄

    转载自:https://www.cnblogs.com/silverb/p/5300255.html 句柄的基本概念 1.句柄就是进程句柄表中的索引.2.句柄是对进程范围内一个内核对象地址的引用,一个 ...

  4. [转]Windows中的句柄(handle)

    1.句柄是什么?   在windows中,句柄是和对象一一对应的32位无符号整数值.对象可以映射到唯一的句柄,句柄也可以映射到唯一的对象.2.为什么我们需要句柄?   更准确地说,是windows需要 ...

  5. Windows中的句柄

    (一)句柄 在程序设计中,句柄(handle)是一种特殊的智能指针.当一个应用程序要引用其他系统(如数据库.操作系统)所管理的内存块或对象时,就要使用句柄. 句柄与普通指针的区别在于,指针包含的是引用 ...

  6. 谈windows中的句柄

    谈windows中的句柄   每当一个进程打开一个对象,系统就返回一个句柄作为凭证,由此可以想到,句柄是依赖于具体的进程的,换句话说,句柄一定属于某个进程,以后在访问这个对象时就要使用这个凭证!   ...

  7. 从普通函数到对象方法 ------Windows窗口过程的面向对象封装

    原文地址:http://blog.csdn.net/linzhengqun/article/details/1451088 从普通函数到对象方法 ------Windows窗口过程的面向对象封装 开始 ...

  8. Windows打印体系结构之Print Spooler概念与架构

    Windows打印体系结构之Print Spooler概念与架构Windows 思杰之路(陶菘) · 2016-09-06 22:07 房子好不好,对我而言始终都是肉体的栖居.对于灵魂,我从来不知道该 ...

  9. Delphi对象变成Windows控件的前世今生(关键是设置句柄和回调函数)goodx

    ----------------------------------------------------------------------第一步,准备工作:预定义一个全局Win控件变量,以及一个精简 ...

随机推荐

  1. [Sass]命令编译

    [Sass]命令编译 命令编译是指使用你电脑中的命令终端,通过输入 Sass 指令来编译 Sass.这种编译方式是最直接也是最简单的一种方式.因为只需要在你的命令终端输入: 单文件编译: sass & ...

  2. bzoj3439 trie+可持久化线段树

    挺好想的 trie建树后,按dfn序建可持久化 注意:计数变量多的题目一定要注意检查会不会用的时候搞混了 #include <cstdio> #include <cstdlib> ...

  3. mysql知识

    1.Limit 在语句的最后,起到限制条目的作用 Limit [offset,] [N] offset:偏移量 N:取出条目 例子:select * from stu limit 3,3; 2.左连接 ...

  4. LeetCode之389. Find the Difference

    -------------------------------------------------- 先计算每个字母的出现次数然后减去,最后剩下的那一个就是后来添加的了. AC代码: public c ...

  5. HTTP TCP UDP Socket 关系的几个经典图

      从上图可以看到,TCP/IP是个协议组,可分为三个层次:网络层.传输层和应用层. 在网络层有IP协议.ICMP协议.ARP协议.RARP协议和BOOTP协议. 在传输层中有TCP协议与UDP协议. ...

  6. html标签思维导图

  7. Linux 第05天

    Linux 第05天 1.连接到Internet 1.1 配置网络信息 dmesg命令————查看网卡信息 dmesg | grep -i net ifconfig命令————查看IP.网关等相关信息 ...

  8. WebRTC APM音频处理流程概述

    本文主要介绍WebRTC的APM. 现在主要介绍一下audio_processing.h. 首先插入了几个类,这些都是audio_processing的核心模块. class AudioFrame; ...

  9. kvc(键-值编码)

    kvc(键-值编码) { NSString *_name; Author *_author; NSArray *_kvcArray; float price;} //kvc,setValue 的设的值 ...

  10. 解决Winform应用程序中窗体背景闪烁的问题

    本文转载:https://my.oschina.net/Tsybius2014/blog/659742 我的操作系统是Win7,使用的VS版本是VS2012,文中的代码都是C#代码. 这几天遇到一个问 ...