句柄

在程序设计中，句柄（handle）是对资源的抽象引用，当一个应用程序要引用其他系统（如数据库、操作系统）所管理的内存块或对象时，可以使用句柄。

一个资源句柄可以是一个不透明的标识符，这种情况下它通常是一个整数（常作为数组或用于管理该类资源的“表”中的数组索引），也可以是一个允许访问更多信息的指针。常见的资源句柄包括文件描述符、网络套接字、数据库连接、进程标识符（PID）和作业ID。PID和作业ID是显式可见的整数；而文件描述符和套接字（通常以文件描述符的形式实现）虽然也用整数表示，但通常被视为不透明的。在传统实现中，文件描述符是（每个进程的）文件描述符表的索引，进而指向（系统全局的）文件表。

## 句柄与指针

句柄与普通指针的区别在于，指针包含的是引用对象的内存地址，而句柄则是由系统所管理的引用标识，该标识可以被系统重新定位到一个内存地址上。这种间接访问对象的模式增强了系统对引用对象的控制。（参见封装）。通俗的说就是我们调用句柄就是调用句柄所提供的服务，即句柄已经把它能做的操作都设定好了，我们只能在句柄所提供的操作范围内进行操作，但是普通指针的操作却多种多样，不受限制。

## 句柄与安全性

客户获得句柄时，句柄不仅是资源的标识符，也被授予了对资源的特定访问权限。

## 句柄与操作系统

在上世纪80年代的操作系统（如Mac OS和Windows）的内存管理中，句柄被广泛应用。Unix系统的文件描述符基本上也属于句柄。和其它桌面环境一样，Windows API大量使用句柄来标识系统中的对象，并建立操作系统与用户空间之间的通信渠道。例如，桌面上的一个窗体由一个`HWND`类型的句柄来标识。

如今，内存容量的增大和虚拟内存算法使得更简单的指针愈加受到青睐，而指向另一指针的那类句柄受到冷淡。尽管如此，许多操作系统仍然把指向私有对象的指针以及进程传递给客户端的内部数组下标称为句柄。

## 译名

康奈尔大学副教授David Gries所著的《Compiler Construction for Digital Computer》. John Wiley and Sons, New York, 1971, 491 pages, ISBN 0-471-32776-X，给出如下定义：

```
 (2.3.10) DEFINITION. A handle of any sentential form is a leftmost simple phrase.
```

在该书的中译本： D.格里斯著,仲萃豪等译：《数字计算机的编译程序构造》，科学出版社, 1976年版，给出了如下翻译：

```
(2.3.10)定义.任一句型的句柄就是此句型的最左简单短语。
```

## Windows句柄的本质

内核对象是内存中的数据结构，由操作系统内核分配，并且只能由操作系统内核访问。内核对象的数据结构使用引用计数，计数变为0后操作系统内核就会销毁该内核对象。安全描述符（SECURITY_ATTRIBUTES结构）描述内核对象的安全性，指出内核对象的拥有者、哪些组或用户可以访问此对象。作为对照，用户对象或GDI对象（如菜单、窗口、鼠标光标等）在创建时不需要指出安全描述符。

操作系统内核中有一个全局句柄表。每个进程有自己的一个句柄表，是一个数据结构组成的数组，每个数据结构包含一个指向内核对象的指针、访问掩码、继承标识等。句柄是进程句柄表数组的下标。在32位系统中，句柄是一个32位值。64位系统中则是64位值。应用程序调用创建内核对象的API函数后，该API函数会返回一个句柄以标识操作系统内核所创建的内核对象。这个句柄可以由进程的任何线程使用。使用CloseHandle函数或者进程结束时，内核句柄表中相应项的计数值会被减1。

GetCurrentProcess函数返回当前进程的句柄，其值为-1；GetCurrentThread函数返回的句柄其值为-2。二者都是伪句柄，在进程的句柄表中没有这两项，仅代表当前进程和当前线程。

进程间共享内核对象有如下途径：

- 父子进程间的句柄继承：在调用API函数来创建内核对象时，使用SECURITY_ATTRIBUTES结构来指出可以被继承。创建子进程时，所有被继承的句柄在子进程句柄表中的位置与在父进程中完全一样；内核对象的使用计数也会加1。
- 命名对象：注意所有种类的内核对象使用同一个命名空间。
- 复制对象句柄：使用DuplicateHandle函数

## 文件/设备句柄的操作系统API

Windows操作系统提供下述API函数以访问文件或设备：

```cpp
SetHandleInformation： 设置指定句柄的当前标识

GetHandleInformation： 返回指定句柄的当前标识

HANDLE CreateFile(  
                  LPCTSTR,lpFileName,                        //指向文件名的指针  
                  DWORD dwDesiredAccess,                     //访问模式（读/写/读写/0表示不读写仅获取文件信息）  
                  DWORD dwShareMode,                         //共享模式  
                  LPSECURITY_ATTRIBUTES lpSecurityAttributes,//用于确定如何在子进程中继承这个句柄  
                  DWORD dwCreationDisposition,               // 文件存在或不存在时的操作
                                                                //CREATE_NEW：创建文件，如果文件存在会出错。
                                                                //CREATE_ALWAYS：创建文件，如果该文件已经存在,函数将覆盖已存在的文件并清除已存在的文件属性
                                                                //OPEN_EXISTING：打开已存在文件。
                                                                //OPEN_ALWAYS：如果不存在就创建。
                                                                //TRUNCATE_EXISTING：将现有的文件缩短为零长度。调用进程必须用GENERIC_WRITE访问模式打开文件.如果文件不存在则函数就会失败.  
                  DWORD dwFlagAndAttributes,                 //指定新创建文件的属性；以及文件的标志位 
                                                                //FILE_ATTRIBUTE_ARCHIVE：标记为归档属性。
                                                                //FILE_ATTRIBUTE_NORMAL：默认属性；文件没有被设置任何属性；仅单独使用生效。
                                                                //FILE_ATTRIBUTE_HIDDEN：隐藏文件或目录。
                                                                //FILE_ATTRIBUTE_OFFLINE 文件数据不可直接利用。指出文件数据已经在物理上移动到脱机存储。
                                                                //FILE_ATTRIBUTE_READONLY：文件为只读。
                                                                //FILE_ATTRIBUTE_SYSTEM：文件为系统文件。
                                                                //FILE_ATTRIBUTE_COMPRESSED 将文件标记为已压缩，或者标记为文件在目录中的默认压缩方式 
                                                                //FILE_ATTRIBUTE_TEMPORARY 文件用于临时存储；文件系统尽量把文件数据保存在内存而不是刷回主存储；临时文件不再使用后应用程序应该尽快删除临时文件。
                                                                //FILE_FLAG_WRITE_THROUGH 	系统写通过任何中间缓存直接写入硬盘；系统仍然可以缓存写操作，但不得懒惰地刷回硬盘
                                                                //FILE_FLAG_OVERLAPPED 	允许对文件进行[[重叠I/O]]操作
                                                                //FILE_FLAG_NO_BUFFERING 	禁止对文件进行缓冲处理。文件只能写入磁盘卷的扇区块
                                                                //FILE_FLAG_RANDOM_ACCESS 	针对随机访问对文件缓冲进行优化
                                                                //FILE_FLAG_SEQUENTIAL_SCAN 	针对从头到尾的顺序访问方式对大文件缓冲进行优化
                                                                //FILE_FLAG_DELETE_ON_CLOSE 	关闭了所有打开的句柄后，文件立即被删除。特别适合临时文件。如果没有使用FILE_SHARE_DELETE,后续的打开文件的请求将会失败.
                                                                //FILE_FLAG_BACKUP_SEMANTICS 指示系统文件的打开或创建将用于备份或恢复操作。系统保证调用进程如果有必要的特权（SE_BACKUP_NAME与SE_RESTORE_NAME），将忽略文件安全检查。这个标志也适用于文件夹句柄。
                                                                //FILE_FLAG_POSIX_SEMANTICS 指明文件基于POSIX规则被访问。例如多个文件使用只有大小写区别的文件名（如果文件系统支持的话）。在MS-DOS与16位Windows下，由于难以兼容，需谨慎使用。
                                                                //FILE_FLAG_OPEN_REPARSE_POINT 指出禁止[[NTFS重解析点]]的行为。标志不能够和CREAT_ALWAYS一起使用.
                                                                //FILE_FLAG_OPEN_NO_RECALL 指明需要文件数据,但是将继续从远程存储器中读写，不会将数据存放在本地存储器中。这个标志由远程存储系统或分层存储管理器系统使用.
                  HANDLE hTemplateFile                       //如果不为0，则指定一个文件句柄，新的文件将从这个文件中复制扩展属性  
                 )；//如果函数失败,返会值会是INVALID_HANDLE_VALUE

SetFilePointer；//设置文件指针位置

BOOL WriteFile(  
               HANDLE fFile,                  //文件句柄  
               LPCVOID lpBuffer,              //数据缓存区指针  
               DWORD nNumberOfBytesToWrite,   //所要写的字节数  
               LPDWORD lpNumberOfBytesWritten,//用于保存实际写入字节数的存储区的指针  
               LPOVERLAPPED lpOverlapped      //OVERLAPPED结构体指针  
              )。

BOOL ReadFile(  
                   HANDLE fFile,                  //文件句柄  
                   LPCVOID lpBuffer,              //数据缓存区指针  
                   DWORD nNumberOfBytesToRead,    //所要写的字节数  
                   LPDWORD lpNumberOfBytesRead,   //用于保存实际写入字节数的存储区的指针  
                   LPOVERLAPPED lpOverlapped      //OVERLAPPED结构体指针  
                  )  。

CloseHandle(hFILE); //关闭句柄

GetDiskFreeSpace(); //确定扇区的尺寸
```

文件存取的字节数必须是扇区尺寸的整倍数。进行读和写操作的地址必须扇区位置对齐（在内存中地址对齐到扇区容量的整倍数），可用VirtualAlloc在内存中申请缓冲区，做到内存页对齐（从而硬盘扇区也对齐）。