创建和打开文件

CreateFile 函数可以创建新文件或打开现有文件。 必须指定文件名、创建说明和其他属性。 在应用程序创建新文件时,操作系统会将其添加到指定目录。

操作系统会为使用 CreateFile 打开或创建的每个文件分配一个唯一标识符,即所谓的句柄。 应用程序可以将此句柄与读取、写入和描述文件的函数配合使用。 在关闭对该句柄的所有引用之前,它将一直有效。 在应用程序启动时,如果句柄是作为可继承句柄创建的,它就会从启动它的进程继承所有打开的句柄。

在尝试使用句柄访问文件之前,应用程序应检查 CreateFile 返回的句柄值。 如果发生错误,句柄值将为 INVALID_HANDLE_VALUE,应用程序可使用 GetLastError 函数来获取扩展错误信息。

当应用程序使用 CreateFile 时,它必须使用 dwDesiredAccess 参数指定是要从文件读取、向文件写入、同时读取和写入,还是两者都不需要。 这就是所谓的请求访问模式。 应用程序还必须使用 dwCreationDisposition 参数来指定在文件已经存在的情况下应采取的操作,即所谓的创建处置。 例如,应用程序可以调用 CreateFile 并将 dwCreationDisposition 设置为 CREATE_ALWAYS,以始终创建一个新文件,即使同名文件已经存在(从而覆盖现有文件)。 成功与否取决于前一个文件的属性和安全设置等因素(有关详细信息,请参阅以下部分)。

应用程序还可使用 CreateFile 来指定是否要共享文件以供读取、写入、两者共享或两者都不共享。 这就是所谓的共享模式。 未共享的已打开文件(dwShareMode 设置为零)在其句柄关闭之前,无论是打开文件的应用程序还是其他应用程序都无法再次打开。 这也被称为独占访问。

当进程使用 CreateFile 尝试打开一个已经以共享模式(dwShareMode 设置为有效的非零值)打开的文件时,系统会将请求的访问和共享模式与打开文件时指定的模式进行比较。 如果指定的访问或共享模式与前次调用中指定的模式冲突,则 CreateFile 将失败。

下表说明了使用各种访问模式和共享模式(分别为 dwDesiredAccessdwShareMode)对 CreateFile 进行两次调用的有效组合。 调用 CreateFile 的顺序并不重要。 但是,每个文件句柄上的任何后续文件 I/O 操作仍将受到与该特定文件句柄相关的当前访问和共享模式的限制。

首次调用 CreateFile CreateFile 的有效第二次调用
GENERIC_READFILE_SHARE_READ
  • GENERIC_READFILE_SHARE_READ
  • GENERIC_READFILE_SHARE_READFILE_SHARE_WRITE
GENERIC_READFILE_SHARE_WRITE
  • GENERIC_WRITEFILE_SHARE_READ
  • GENERIC_WRITEFILE_SHARE_READFILE_SHARE_WRITE
GENERIC_READFILE_SHARE_READFILE_SHARE_WRITE
  • GENERIC_READFILE_SHARE_READ
  • GENERIC_READFILE_SHARE_READFILE_SHARE_WRITE
  • GENERIC_WRITEFILE_SHARE_READ
  • GENERIC_WRITEFILE_SHARE_READFILE_SHARE_WRITE
  • GENERIC_READGENERIC_WRITEFILE_SHARE_READ
  • GENERIC_READGENERIC_WRITEFILE_SHARE_READFILE_SHARE_WRITE
GENERIC_WRITEFILE_SHARE_READ
  • GENERIC_READFILE_SHARE_WRITE
  • GENERIC_READFILE_SHARE_READFILE_SHARE_WRITE
GENERIC_WRITEFILE_SHARE_WRITE
  • GENERIC_WRITEFILE_SHARE_WRITE
  • GENERIC_WRITEFILE_SHARE_READFILE_SHARE_WRITE
GENERIC_WRITEFILE_SHARE_READFILE_SHARE_WRITE
  • GENERIC_READFILE_SHARE_WRITE
  • GENERIC_READFILE_SHARE_READFILE_SHARE_WRITE
  • GENERIC_WRITEFILE_SHARE_WRITE
  • GENERIC_WRITEFILE_SHARE_READFILE_SHARE_WRITE
  • GENERIC_READGENERIC_WRITEFILE_SHARE_WRITE
  • GENERIC_READGENERIC_WRITEFILE_SHARE_READFILE_SHARE_WRITE
GENERIC_READGENERIC_WRITEFILE_SHARE_READ
  • GENERIC_READFILE_SHARE_READFILE_SHARE_WRITE
GENERIC_READGENERIC_WRITEFILE_SHARE_WRITE
  • GENERIC_WRITEFILE_SHARE_READFILE_SHARE_WRITE
GENERIC_READGENERIC_WRITEFILE_SHARE_READFILE_SHARE_WRITE
  • GENERIC_READFILE_SHARE_READFILE_SHARE_WRITE
  • GENERIC_WRITEFILE_SHARE_READFILE_SHARE_WRITE
  • GENERIC_READGENERIC_WRITEFILE_SHARE_READFILE_SHARE_WRITE

除标准文件属性外,还可以指定安全属性,方法是将指向 SECURITY_ATTRIBUTES 结构的指针作为 CreateFile 的第四个参数。 但是,基础文件系统必须支持安全性才能产生相关效果(例如,NTFS 文件系统支持安全性,但各种 FAT 文件系统不支持安全性)。 有关安全属性的详细信息,请参阅访问控制

创建新文件的应用程序可以提供一个模板文件的可选句柄,CreateFile 会从中获取文件属性和扩展属性,以用于创建新文件。

CreateFile 方案

使用 CreateFile 函数启动文件访问权限有几种基本方案。 概括起来包括:

  • 在不存在同名文件的情况下创建新文件。
  • 即使已经存在同名文件,也要创建新文件,从而清除其数据并清空。
  • 只有在现有文件存在的情况下才能打开它,而且必须保持不变。
  • 仅在现有文件存在的情况下打开该文件,并将其截断为空。
  • 打开文件时始终:如果存在则按原样打开,如果不存在则创建新文件。

这些方案可通过正确使用 dwCreationDisposition 参数来控制。 下面将详细介绍这些情景如何与该参数的值相对应,以及使用这些值时的情况。

在创建或打开一个新文件时,如果尚未存在具有该文件名的文件(dwCreationDisposition 设置为 CREATE_NEWCREATE_ALWAYSOPEN_ALWAYS),CreateFile 函数将执行以下操作:

  • dwFlagsAndAttributes 指定的文件属性和标志与 FILE_ATTRIBUTE_ARCHIVE 相结合。
  • 将文件长度设置为零。
  • 如果指定了 hTemplateFile 参数,则将模板文件提供的扩展属性复制到新文件中(这将覆盖前面指定的所有 FILE_ATTRIBUTE_* 标志)。
  • 设置 bInheritHandle 成员指定的继承标志和 lpSecurityAttributes 参数(SECURITY_ATTRIBUTES 结构)中 lpSecurityDescriptor 成员指定的安全描述符(如果提供)。

在创建新文件时,即使同名文件已经存在(dwCreationDisposition 设置为 CREATE_ALWAYS),CreateFile 函数也会执行以下操作:

  • 检查当前文件属性和安全设置是否允许写入访问,如果拒绝则失败。
  • dwFlagsAndAttributes 指定的文件属性和标志与 FILE_ATTRIBUTE_ARCHIVE 和现有文件属性相结合。
  • 将文件长度设置为零(也就是说,文件中的任何数据都不再可用,文件为空)。
  • 如果指定了 hTemplateFile 参数,则将模板文件提供的扩展属性复制到新文件中(这将覆盖前面指定的所有 FILE_ATTRIBUTE_* 标志)。
  • 设置 lpSecurityAttributes 参数(SECURITY_ATTRIBUTES 结构)的 bInheritHandle 成员指定的继承标志(如果提供),但忽略 SECURITY_ATTRIBUTES 结构的 lpSecurityDescriptor 成员。
  • 如果调用成功(即 CreateFile 返回一个有效句柄),调用 GetLastError 将产生代码 ERROR_ALREADY_EXISTS,尽管在这种特定的使用情况下,这实际上并不是一个错误(如果你打算创建一个“新的”(空)文件来代替现有文件)。

打开现有文件(dwCreationDisposition 设置为 OPEN_EXISTINGOPEN_ALWAYSTRUNCATE_EXISTING)时,CreateFile 函数将执行以下操作:

  • 检查请求访问的当前文件属性和安全设置,如果拒绝则失败。
  • dwFlagsAndAttributes 指定的文件标志 (FILE_FLAG_*) 与现有文件属性相结合,并忽略 dwFlagsAndAttributes 指定的任何文件属性 (FILE_ATTRIBUTE_*)。
  • 只有当 dwCreationDisposition 设置为 TRUNCATE_EXISTING 时,才会将文件长度设置为零,否则将保持当前文件长度,并按原样打开文件。
  • 忽略 hTemplateFile 参数。
  • 设置 lpSecurityAttributes 参数(SECURITY_ATTRIBUTES 结构)的 bInheritHandle 成员指定的继承标志(如果提供),但忽略 SECURITY_ATTRIBUTES 结构的 lpSecurityDescriptor 成员。

文件属性和目录

文件属性是与文件或目录相关联的元数据的一部分,每个属性都有自己的用途,以及如何设置和更改的规则。 其中一些属性只适用于文件,一些属性只适用于目录。 例如,FILE_ATTRIBUTE_DIRECTORY 属性只适用于目录:文件系统使用该属性来确定磁盘上的对象是否为目录,但不能更改现有文件系统对象的该属性。

一些文件属性可以为某个目录设置,但只对在该目录下创建的文件有意义,它们就像默认属性一样。 例如,可以在目录对象上设置 FILE_ATTRIBUTE_COMPRESSED,但由于目录对象本身不包含实际数据,因此它并不是真正的压缩对象;不过,标有该属性的目录会告诉文件系统压缩添加到该目录中的任何新文件。 可以在目录上设置的任何文件属性,以及添加到该目录的新文件也将设置的任何文件属性,都被称为继承属性

CreateFile 函数提供了一个参数,用于在创建文件时设置某些文件属性。 一般来说,这些属性是应用程序在创建文件时最常用的属性,但 CreateFile 并不能使用所有可能的文件属性。 在文件已存在之后,一些文件属性需要使用其他函数,例如 SetFileAttributesDeviceIoControlDecryptFile。 在 FILE_ATTRIBUTE_DIRECTORY 的情况下,创建时需要使用 CreateDirectory 函数,因为 CreateFile 无法创建目录。 其他需要特殊处理的文件属性包括 FILE_ATTRIBUTE_REPARSE_POINTFILE_ATTRIBUTE_SPARSE_FILE,它们需要 DeviceIoControl。 有关详细信息,请参阅 SetFileAttributes

如前所述,文件属性继承发生在创建文件时,文件属性是从文件所在目录属性中读取的。 下表概述了这些继承属性及其与 CreateFile 功能的关系。

目录属性状态 CreateFile 会让新文件继承替代功能
FILE_ATTRIBUTE_COMPRESSED 已设置。
无控制。 使用 DeviceIoControl 清除。
FILE_ATTRIBUTE_COMPRESSED 未设置。
无控制。 使用 DeviceIoControl 进行设置。
FILE_ATTRIBUTE_ENCRYPTED 已设置。
无控制。 使用 DecryptFile
FILE_ATTRIBUTE_ENCRYPTED 未设置。
可以使用 CreateFile 进行设置。
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 已设置。
无控制。 使用 SetFileAttributes 清除。
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 未设置。
无控制。 使用 SetFileAttributes 进行设置。

访问控制

CreateFile

DeviceIoControl

文件特性常量

文件压缩和解压缩

文件加密

文件管理函数

句柄和对象

处理继承

打开文件进行读取或写入

SetFileAttributes