句柄

在程序設計中，句柄（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在記憶體中申請緩衝區，做到記憶體頁對齊（從而硬碟扇區也對齊）。