原文链接 -> 传送门
函数功能:
CreateFile 函数用于创建或打开一个文件或者 I/O 设备。最常使用的 I/O 设备如下:文件、文件流、文件夹、物理磁盘、磁盘驱动器、控制台程序缓冲区、磁带、通信资源、邮槽和管道。此函数返回一个指向用于访问来自不同类型I/O的文件或设备的句柄,其访问权限取决于它所访问的文件、设备和我们所指定的标记位、属性。
为了使之可以处理事务型 I/O,请使用 CreateFileTransacted 函数。
API 函数原型:
注释:_In_ 说明该参数是输入的,_opt_ 说明该参数是可选的
HANDLE WINAPI CreateFile( _In_ LPCTSTR lpFileName, _In_ DWORD dwDesiredAccess, _In_ DWORD dwShareMode, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, _In_ DWORD dwCreationDisposition, _In_ DWORD dwFlagsAndAttributes, _In_opt_ HANDLE hTemplateFile );
参数解析:
|
参数 |
含义 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
lpFileName |
1. 指定要打开、创建的文件或设备的名字。你可以在名字中使用斜杠(/)或者反斜杠(\) |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
dwDesiredAccess |
1. 指定以何种权限打开文件或设备,可以归纳为:读访问权、写访问权、读写访问权、非读非写访问权 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
dwShareMode |
1. 设置文件或者设备的共享模式,包括读、写、读写、删除、全部权限或者以上什么权限都没有(参考下面的表格)。此参数不影响对属性和扩展属性的访问请求
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
lpSecurityAttributes |
1. 指向 SECURITY_ATTRIBUTES 结构体的指针。此结构体拥有两个分开但是相关的数据成员:一个是可选的安全描述符,另一个是决定返回的句柄是否可以被子进程继承的 Boolean 值 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
dwCreationDisposition |
1. 用于设置当文件存在或不存在时,要对文件或设备执行何种操作
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
dwFlagsAndAttributes |
1. 文件或设备的属性值和标记位,对于文件来说,FILE_ATTRIBUTE_NORMAL 是最常用的默认值
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
hTemplateFile |
1. 指向一个拥有 GENERIC_READ 访问权限的的模板文件的合法句柄 |
返回值:
1. 如果函数调用成功,返回指向指定文件的句柄、设备、命名管道或者邮槽;
2. 如果函数调用失败,返回值为 INVALID_HANDLE_VALUE。
想获取具体错误信息,调用 GetLastError 函数。
备注:
1. CreateFile 函数最初被官方开发用于文件操作,但后来功能被扩展加强,Windows 开发者可用它来处理绝大部分其他类型的 I/O 设备和机器。当前备注试图讲解开发者在不同的环境下使用不同的 I/O 设备过程中,调用 CreateFile 函数可能遇到的不同的问题。
这篇文章中,仅当数据是存储在一个文件系统上的真正的文件,才会使用”文件”这个词。然而,在提及“文件”的情况下,有一些只得是更广义上的 I/O 对象,这些 I/O 对象支持类似文件操作的机制。由于前面所提及到的历史原因,“文件”这个名词使用过于自由,已经广泛被各种常量名称和参数名称所使用。
2. 当 CreateFile 函数返回的句柄不再使用时,调用 CloseHandle 函数关闭这个句柄。这不仅只是释放系统资源,对于如文件或设备的共享和磁盘的数据提交等仍有很大的影响。具体细节在本文描述有介绍。
3. Windows Server 2003 and Windows XP:当我们试图打开一个远程文件或目录以用于删除,此时如果指定了 dwDesiredAccess 参数包含了删除标记(0x00010000),且远程文件或目录并未以 FILE_SHARE_DELETE 属性被打开,那么就会引发共享错误。在这个例子中,为了避免共享错误发生,仅且仅以删除权限打开远程文件或目录,或者不用打开文件而是直接调用 DeleteFile 函数去删除文件。
4. 一些文件系统,如 NTFS 文件系统,支持某些文件和文件夹的压缩和加密。如果一个分区装载了支持这些功能的文件系统,则一个新文件会从它所属文件夹中继承这些压缩和加密的属性。
5. 你无法使用 CreateFile 函数去控制文件的压缩、解压或加密。更多信息,参见 Creating and Opening Files, File Compression and Decompression 和 File Encryption。
6. Windows Server 2003 and Windows XP:为了实现向后兼容,当你在 lpSecurityAttributes 参数中指定一个安全描述符时,CreateFile 函数并不会应用继承规则。为了支持继承,后续询问文件安全描述符的函数可能会启发式地去决定并告知继承已经生效了。更多信息,参见 Automatic Propagation of Inheritable ACEs。
7. 正如前面所阐述的,如果 lpSecurityAttributes 参数为 NULL,则 CreateFile 函数返回的句柄无法被你的程序可能创建的任何子进程所继承。以下是关于此参数被应用的信息:
- 如果 bInheritHandle 成员的取值不是 FALSE,而是其他任意非 0 值,那么这个句柄可以被继承。因此如果你不希望此句柄可以被继承,注意要把此参数初始化为 FALSE
- 文件或目录默认安全描述符中的访问控制列表(ACL)继承自其父目录
- 若想使 lpSecurityDescriptor 成员对文件和目录有效,文件系统必须支持文件和目录安全,这由使用 GetVolumeInformation 函数决定
在 Windows 8 和 Windows Server 2012 中,此函数由以下技术支持:
|
技术 |
是否支持 |
|
Server Message Block (SMB) 3.0 protocol |
支持 |
|
SMB 3.0 Transparent Failover (TFO) |
参见备注 |
|
SMB 3.0 with Scale-out File Shares (SO) |
参见备注 |
|
Cluster Shared Volume File System (CsvFS) |
支持 |
|
Resilient File System (ReFS) |
支持 |
注意:当 CreateFile 函数设置将会替代旧文件,如果所处理的文件已经打开了一个数据流,则函数会执行失败。
8. 符号链接相关
如果此函数的调用产生了一个新文件,FILE_FLAG_OPEN_REPARSE_POINT 标记位被忽视,否则,考虑以下说明:
- 如果指定了 FILE_FLAG_OPEN_REPARSE_POINT 标记,则:
- 如果一个已存在的文件被打开且它是一个符号链接,则返回的句柄为一个执行符号链接的句柄
- 如果指定了 TRUNCATE_EXISTING 或 FILE_FLAG_DELETE_ON_CLOSE 标记,则被影响的文件是一个符号链接
- 如果未指定了 FILE_FLAG_OPEN_REPARSE_POINT 标记,则:
- 如果一个已存在的文件被打开且它是一个符号链接,则返回的句柄为指向这个目标的句柄
- 如果指定了 CREATE_ALWAYS, TRUNCATE_EXISTING 或 FILE_FLAG_DELETE_ON_CLOSE 标记,被影响的文件为目标文件
9. 文件缓存
CreateFile 函数的 dwFlagsAndAttributes 参数的一些取值,用于告知系统如何指定其返回的文件句柄的缓存模式。这些取值是:
- FILE_FLAG_NO_BUFFERING
- FILE_FLAG_RANDOM_ACCESS
- FILE_FLAG_SEQUENTIAL_SCAN
- FILE_FLAG_WRITE_THROUGH
- FILE_ATTRIBUTE_TEMPORARY
如果以上标记位都没有被指定,则系统使用一个默认的适用于大多数情况的缓存模式。否则,系统就会按照我们指定的标记位的模式,进行数据缓存。
某些标记位无法被组合。如组合 FILE_FLAG_RANDOM_ACCESS 标记位和 FILE_FLAG_SEQUENTIAL_SCAN 标记位将发生自我矛盾。
当顺序读取大文件,指定 FILE_FLAG_SEQUENTIAL_SCAN 标记位可以提高性能;当程序读取大文件时,大部分都是顺序读取,偶尔跳过小范围的字节数,则性能提高更明显。如果程序操作文件指针以用于随机访问,则大部分情况下是无法达到最优缓存效果的。然而,仍然可以保证操作的正确执行。
FILE_FLAG_WRITE_THROUGH 标记位和 FILE_FLAG_NO_BUFFERING 标记位是独立的,可以进行组合。
如果指定了 FILE_FLAG_WRITE_THROUGH 标记位,但是没有指定 FILE_FLAG_NO_BUFFERING 标记位,此时系统缓存机制仍在运行:数据被写到系统缓存区,但是马上被写回磁盘。
如果 FILE_FLAG_WRITE_THROUGH 标记位和 FILE_FLAG_NO_BUFFERING 标记位都被指定了,则系统缓冲区不会起作用,数据不经过 Windows 系统缓存区而是直接写入磁盘。系统会申请直接写入到本地磁盘的缓存中。
如果 FILE_FLAG_WRITE_THROUGH 标记位和 FILE_FLAG_NO_BUFFERING 标记位都被指定了,则系统缓存机制停止工作,数据不经过Windows系统缓存去,而是被直接写到磁盘上。系统会申请直接写入到本地磁盘的缓存中。
注意:并非所有磁盘硬件都支持直接写入的方法。
要想正确使用 FILE_FLAG_NO_BUFFERING 标记位,需要对程序一些更深的理解。更多信息,参见 File Buffering。
处理一个直接写入请求请求(通过 FILE_FLAG_WRITE_THROUGH 标志位来实现)时,会使得 NTFS 刷新任何被修改的元数据,如文件修改时间和重命名操作。为此,FILE_FLAG_WRITE_THROUGH 标记位常和 FILE_FLAG_NO_BUFFERING 标记位共同使用,以替代每次进行写操作后都调用 FlushFileBuffers 函数将数据写入文件,而后者会引起不必要的开销。使用这些标记位可以避免此类不必要的开销。更多关于文件和元数据缓存的信息,参见 File Caching。
当 FILE_FLAG_NO_BUFFERING 标记位和 FILE_FLAG_OVERLAPPED 标记位共同使用时,可以提供最大化异步 I/O 的效率,因为 I/O 操作并不依赖于内存管理器的同步操作。然而,一些 I/O 操作消耗更多时间,因为没有使用缓存区。此外,文件元数据可能仍然使用缓存(例如,当创建一个空文件)。为了保证元数据被写入磁盘,要使用 FlushFileBuffers 函数。
当指定了 FILE_ATTRIBUTE_TEMPORARY 属性值时,如果有可用的高效的缓冲内存,那么文件系统会避免将数据写回大容量存储器,因为程序在文件句柄被关闭后会删除临时文件。在这个情况下,系统可以完全避免将数据写回磁盘。虽然它不像前面提及到的标记位那样直接控制数据缓存行为,但是 FILE_ATTRIBUTE_TEMPORARY 属性值会告诉系统尽可能地不要将数据写到磁盘中,因此这可能会对某些程序很有意义。
10. 文件
如果你重命名或删除一个文件,然后又马上恢复原样,则系统会搜索缓存区中的文件信息去执行恢复操作。被缓存的信息包括短、长文件名和文件创建时间。
操作系统会延迟删除操作、直到所有指向此文件的句柄被关闭。如果某个文件正在等待被执行 DeleteFile 函数,此时对此文件调用 CreateFile 函数,则 CreateFile 函数执行失败。GetLastError 函数会返回 ERROR_ACCESS_DENIED。
dwDesiredAccess 参数可以为 0,此时如果程序的权限足够,则系统允许程序不访问文件而直接获取文件的属性值。 这在以下情况是很有用的:当我们不以读访问或写访问打开一个文件,却想测试一个文件是否存在或者获得其他关于此文件或目录的数据。参见 Obtaining and Setting File Information 和 GetFileInformationByHandle 函数。
如果指定了 CREATE_ALWAYS 和 FILE_ATTRIBUTE_NORMAL,则当文件存在且拥有 FILE_ATTRIBUTE_HIDDEN 属性值或 FILE_ATTRIBUTE_SYSTEM 属性值时,CreateFile 函数执行失败,且 last error 被设置为 ERROR_ACCESS_DENIED。为了避免这个错误,要为已存在文件设置与之相同的属性值。
当程序通过网络创建一个文件,dwDesiredAccess 参数设置为 GENERIC_READ | GENERIC_WRITE 会比单独设置 GENERIC_WRITE 要好。这样会使得代码执行的更快,因为重定向器可以使用缓存管理器,在发送更多数据的同时,附带更少的 SMB。二者组合用还可以防止通过网络写文件时,偶尔出现ERROR_ACCESS_DENIED错误。
更多信息,参见 Creating and Opening Files。
11. 同步和异步 I/O 句柄
CreateFile 函数可以创建的文件或设备句柄可以是同步也可以是异步的。一个同步句柄会导致当执行 I/O 调用函数时,调用者会被阻塞知道函数执行结束;而一个异步文件句柄在调用 I/O 函数时,无论 I/O 操作是否执行完成,调用者都会立刻返回。
正如前面所阐述的,使用同步 I/O 还是异步 I/O 由在 dwFlagsAndAttributes 参数中是否指定了 FILE_FLAG_OVERLAPPED 标记位决定。此外异步 I/O 会有一些复杂性和潜在的陷阱,更多信息,参见 Synchronous and Asynchronous I/O。
12. 文件流
在 NTFS 文件系统中,你可以使用 CreateFile 函数在一个文件中创建分离的流。想了解更多信息,参见 File Streams。
13. 目录
程序无法使用 CreateFile 函数创建一个目录,因此对目录操作时,在 dwCreationDisposition 参数里指定 OPEN_EXISTING 标记位才是合法的。为了创建一个目录,程序必须调用 CreateDirectory 函数或 CreateDirectoryEx 函数。
若要用 CreateFile 函数打开一个目录,需在 dwFlagsAndAttributes 参数里指定 FILE_FLAG_BACKUP_SEMANTICS 标记位。当程序没有 SE_BACKUP_NAME 和 SE_RESTORE_NAME 权限时,会触发适当的安全检查。
在 FAT 或 FAT32 文件系统中,如果我们在执行磁盘碎片整理过程中,使用 CreateFile 函数打开一个目录,那么不要指定 MAXIMUM_ALLOWED 访问权限;如果这样做,则对目录的访问将被拒绝;应使用 GENERIC_READ 访问权限替代之。
更多信息,参见 About Directory Management。
14. 物理磁盘和分区
对磁盘或分区的直接访问是受限制的。更多信息,参见“在 Windows Vista 和 Windows Server 2008 中对文件系统和存储堆栈进行的更改可限制对磁盘和卷的直接访问”,网址为 http://support.microsoft.com/kb/942448。
Windows Server 2003 和 Windows XP: 对磁盘或分区的直接访问是不受限制的。
你可以使用 CreateFile 函数打开一个物理磁盘或一个磁盘分卷,它会返回一个直接访问存储设备(DASD)句柄,这个句柄可以被 DeviceIoControl 函数所使用。这允许你直接访问磁盘或分区,例如磁盘的元数据分区表。然而,这会使得磁盘或分区的数据存在数据丢失的可能,因为使用这种方法对磁盘一次不正确的写操作可能使得操作系统无法识别修改后的数据。为了保证数据的完整性,要确保对 DeviceIoControl 函数足够熟悉,且了解其他API在使用文件系统句柄和直接访问句柄时,有什么区别。
若想对磁盘和分区直接访问成功执行,以下条件必须被满足:
- 调用者必须拥有 administrative 权限。更多信息,参见 Running with Special Privileges
- dwCreationDisposition 参数必须指定 OPEN_EXISTING 标记位。
- 当打开一个分区或者软盘时,dwShareMode 参数必须指定 FILE_SHARE_WRITE 标记位
注意:dwDesiredAccess 参数可以为 0,这允许程序不访问设备、就可以询问设备属性。例如:程序可以获得软盘驱动器的大小和其支持的格式却不用直接访问软盘。这还可以用于读取一些不需要获取高级别的数据读、写许可的就能读取的数据。
当打开一个物理驱动器 x:,则 lpFileName 字符串格式如下:"\\.\PhysicalDriverX"。硬盘下标从 0 开始。下表显示了一个有关物理驱动器字符串格式的例子:
|
字符串 |
含义 |
|
"\\.\PhysicalDrive0" |
打开第一个磁盘驱动器 |
|
"\\.\PhysicalDrive2" |
打开第三个磁盘驱动器 |
为了得到一个物理磁盘驱动器的 ID 值,需要先获得执行其的句柄,然后带入 IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS 调用 DeviceIoControl 函数。
关于如何打开一个物理驱动器,参见 Calling DeviceIoControl 例子。
当打开一个分区或者可移动媒体驱动器(如一个磁盘驱动器或者闪盘驱动器),那么字符串 lpFileName 应该为以下格式 "\\.\X:"。不要使用反斜线(\),这个符号表示驱动器的根目录。下表是一些驱动器目录字符串的例子:
|
字符串 |
含义 |
|
"\\.\A:" |
打开软盘 A |
|
"\\.\C:" |
打开磁盘驱动器 C |
|
"\\.\C:\" |
打开 C 盘的文件系统 |
你也可以通过磁盘分区名字来打开一个磁盘分区。更多信息,参见 Naming a Volume。
一个分区可以有多个文件系统。一些特定的文件系统中,分区句柄被以禁止缓存方式打开,即使 CreateFile 函数并未指定禁止缓存选项。你可以假定所有微软文件系统的分区句柄都是禁止缓存的,且对无缓存文件的某些限制同样适用于分区句柄。
一个文件系统可能会也可能不会要求缓存对齐,即使数据是禁止缓存的。然而,如果在打开一个分区时指定了禁止缓存选项,无论加载在分区上的是何种文件系统,缓冲对齐都是强制要求的。当你打开一个分区时,推荐打开禁止缓存选项,且遵循禁止缓存 I/O 限制。
注意:读写分区的最后几个扇区时,你必须调用 DeviceIoControl 函数并且指定 FSCTL_ALLOW_EXTENDED_DASD_IO 标记位。这会告知文件系统,调用分区的读写函数时,不要进行任何的 I/O 边界检查,而是由设备驱动器来执行边界检查。
15. Changer Device
DeviceIoControl 函数设置控制代码为 IOCTL_CHANGER_* 时,会接受一个指向 changer device 的句柄。要打开一个 changer device,使用如下格式的文件名:"\\.\Changerx",其中 x 表示要打开设备的下标,下标从 0 开始。如要在程序中使用 C 或者 C++ 打开 0 号 changer device,使用如下格式的文件名:"\\\\.\\Changer0"。
16. 磁带驱动器
你可以通过使用如下形式的名称来打开一个磁带驱动器:"\\.\TAPEx",x 表示要打开的驱动器的下标,下标从 0 开始。 如要使用 C 或 C++ 打开 0 号磁带驱动器,使用如下格式:"\\\\.\\TAPE0".
更多信息,参见 Backup。
17. 通信资源
CreateFile 函数可以创建指向通信资源的句柄,如串行端口 COM1。 要打开一个通信资源,dwCreationDisposition 参数必须设置为 OPEN_EXISTING,dwShareMode 参数必须设置为 0(即只能由自己访问),且 hTemplateFile 参数必须为 NULL。读、写或者读/写权限可以指定任意一个,且此句柄可用于异步 I/O。
如果要指定一个端口号大于 9 的 COM 端口,使用以下的语法:"\\.\COM10"。此语法适用于所有端口号和所有允许 COM 端口号被指定的硬件。
更多关于通信的信息,参见 Communications。
18. 控制台
你可以用 CreateFile 函数创建一个指向控制台输入(COCNIN$)的句柄。如果进程通过继承或复制获得这个句柄,它还可以创建一个指向活动屏幕缓存的句柄。调用进程必须被附加到一个被继承的控制台或者一个通过 AllocConsole 函数分配到的控制台。有关控制台句柄的参数设置如下:
|
参数 |
值 |
|
lpFileName |
1. 使用 CONIN$ 值指向控制台输入 |
|
dwDesiredAccess |
更推荐使用 GENERIC_READ | GENERIC_WRITE,但任选其一也可以用于设置权限 |
|
dwShareMode |
1. 当打开 CONIN$ 时,要指定 FILE_SHARE_READ;当打开 CONOUT$ 时,要指定 FILE_SHARE_WRITE |
|
lpSecurityAttributes |
如果你想此控制台被继承,那么 SECURITY_ATTRIBUTES 结构体的 bInheritHandle 成员必须为 TRUE |
|
dwCreationDisposition |
当使用 CreateFile 函数打开一个控制台时,你必须指定 OPEN_EXISTING |
|
dwFlagsAndAttributes |
忽略 |
|
hTemplateFile |
忽略 |
下表是 dwDesiredAccess 和 lpFileName 参数取不同值时的结果:
|
lpFileName |
dwDesiredAccess |
结果 |
|
"CON" |
GENERIC_READ |
打开一个控制台程序用于读操作 |
|
"CON" |
GENERIC_WRITE |
打开一个控制台程序用于写操作 |
|
"CON" |
GENERIC_READ | GENERIC_WRITE |
此时 CreateFile 函数执行失败;GetLastError 函数返回 ERROR_FILE_NOT_FOUND |
19. 邮槽
如果 CreateFile 函数打开邮槽,那么当客户端试图打开一个本地邮槽,而服务端并没有使用 CreateMailSlot 函数创建这个邮槽时,则返回 INVALID_HANDLE_VALUE。
更多信息,参见 Mailslots。
20. 管道
如果 CreateFile 函数打开一个客户端的命名管道,则此函数使用任意处于监听状态的命名管道句柄实例。进程可以无限复制命名管道句柄,但是当它被打开后,命名管道无法被另一个客户端再次打开。被指定的命名管道访问权限必须与使用函数 CreateNamedPipe 创建命名管道时,在 dwOpenMode 参数中设置的权限相兼容。
如果 CreateNamedPipe 函数没有在服务端被成功调用,则命名管道不会被创建且 CreateFile 函数执行失败,且返回 ERROR_FILE_NOT_FOUND。
如果在服务器上有至少一个活动的管道实例,但是不存在监听者管道,即所有的管道实现在此时都被连接了,CreateFile 函数执行失败且返回错误信息ERROR_PIPE_BUSY。
更多信息,参见 Pipes。
例子
1. 关于文件操作的例子如下:
- 将一个文件追加在另一个文件后
- 取消正在等待的 I/O 操作
- 创建一个输入和输出被重定向的子进程
- 创建和使用一个临时文件
- FSCTL_RECALL_FILE
- GetFinalPathNameByHandle 函数
- 锁定和解锁文件中的字节范围数据
- 通过文件句柄获取文件名
- 获取文件系统识别信息
- 打开一个文件用于读或写
- 获取最后修改时间
- SetFileInformationByHandle 函数
- 检测是否到文件末尾了
- 使用 Fibers
- 使用 Streams
- 浏览日志修改记录的缓冲区
- Wow64DisableWow64FsRedirection 函数
- Wow64EnableWow64FsRedirection 函数
2. 物理设备 I/O 在以下单元显示:
3. 一个使用命名管道的例子放在 Named Pipe Client。
4. 使用邮件槽的例子在 Writing to a Mailslot。
5. 一份磁盘备份的代码片段可以在这里找到 Creating a Backup Application。
需求:
|
Minimum supported client |
Windows XP [仅桌面应用程序] |
|
Minimum supported server |
Windows 2003 服务器版 [仅桌面应用程序] |
|
Header |
FileAPI.h (包含于 Windows.h) |
|
Library |
Kernel32.lib |
|
DLL |
Kernel32.dll |
|
Unicode and ANSI names |
CreateFileW (Unicode) 和 CreateFileA (ANSI) |




