65.9K
CodeProject 正在变化。 阅读更多。
Home

如何刷新存储卷的文件缓存,锁定并卸载它,以及弹出介质

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (11投票s)

2011年9月27日

CPOL

3分钟阅读

viewsIcon

61207

downloadIcon

1602

演示如何打开一个存储卷并对其进行一些有趣的操作。

引言

弹出介质是 CD/DVD 驱动器的常见任务。在这里,我们进行物理弹出。但 ZIP/JAZ 驱动器和读卡器(带有可移动介质的驱动器)也支持弹出,并且由于它们不是只读的,因此需要在弹出之前卸载其文件系统以防止数据丢失。

背景

弹出介质是通过 DeviceIoControl 调用 IOCTL_STORAGE_EJECT_MEDIA 执行的。但这有点鲁莽,即使驱动器上有打开的文件,它也会弹出!因此,最好在执行此操作之前先刷新、锁定和卸载。

示例

此示例是我命令行工具 EjectMedia 的简化版本。

它期望将要弹出的驱动器盘符作为参数。对于非 CD/DVD 驱动器,第一步是使用 FlushFileBuffers API 调用刷新文件缓存。它需要具有写入访问权限的句柄。因此,为了刷新整个存储卷,我们必须使用写入访问权限打开它,对于受限用户,这将失败并显示 ERROR_ACCESS_DENIED

// "\\.\X:" -> to open the volume
char szVolumeAccessPath[] = "\\\\.\\X:";
szVolumeAccessPath[4] = DriveLetter;

HANDLE hVolWrite = CreateFile(szVolumeAccessPath,
                              GENERIC_READ | GENERIC_WRITE,
                              FILE_SHARE_READ | FILE_SHARE_WRITE,
                              NULL, OPEN_EXISTING, 0, NULL);
if ( hVolWrite != INVALID_HANDLE_VALUE ) {
  FlushFileBuffers(hVolWrite);
  CloseHandle(hVolWrite);
}

然后我们尝试锁定和卸载。在这里,只读访问权限就足够了,即使是受限用户也可以获得。只有当此卷上没有打开文件的句柄时,锁定才会成功。即使锁定失败且存在打开的句柄,卸载也会成功。卸载后,卷上的所有打开的文件句柄仍然打开但无效,任何尝试将其用于读写操作都会导致 ERROR_NOT_READY。因此,即使锁定失败,也可以选择卸载,这就是 'Force' 布尔值的含义。

bool Force = false;
HANDLE hVolRead = CreateFile(szVolumeAccessPath,
                  GENERIC_READ,
                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                  NULL, OPEN_EXISTING, 0, NULL);
if ( hVolRead != INVALID_HANDLE_VALUE ) {
  int Locked = DeviceIoControl(hVolRead, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &dwRet, NULL);
  if ( Locked || Force ) {
    res = DeviceIoControl(hVolRead, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &dwRet, NULL);
  }
}

最后,我们执行弹出操作

res = DeviceIoControl(hVolRead, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0, NULL, 0, &dwRet, NULL);

由于我们很友好,如果获得锁,我们会释放它

if ( Locked ) {
  DeviceIoControl(hVolRead, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &dwRet, NULL);
}

顺便说一句:弹出的反义词是加载介质。这仅适用于 CD/DVD 驱动器

res = DeviceIoControl(hVolRead, IOCTL_STORAGE_LOAD_MEDIA, NULL, 0, NULL, 0, &dwRet, NULL);

讨论

为什么没有检查驱动器类型 DRIVE_CDROM 和 DRIVE_REMOVABLE?

Windows 在这里不关心驱动器类型。弹出请求会传递到驱动器的驱动程序,然后驱动程序将其传递到硬件。最后,硬件会给出答案。有一些 USB 硬盘驱动器支持弹出。我见过安全移除失败但弹出成功的情况。

什么是确切的卸载?

卸载意味着将文件系统与存储卷分离。这保证了文件系统不会处于不一致状态,这在由另一个驱动程序挂载它之前很重要,例如,当系统处于休眠状态并且您从可启动 CD 启动时。

这与删除驱动器盘符 (mountvol X: /D) 相同吗?

不,绝对不是! /D 并不代表卸载,它代表删除挂载点。仅此而已。没有刷新,没有卸载,没有句柄被关闭或无效。

我们如何重新挂载卷?

如果锁被释放,那么只需尝试打开卷上的一个文件。Windows 随后会自动挂载该卷。在执行卸载之前已打开的句柄将无法用于读/写,即使该卷再次被挂载,它们也会永久生成 ERROR_NOT_READY

为什么锁定有时需要一段时间?

似乎锁定也会刷新文件缓存,这可能需要一点时间。此外,应用程序可以注册自定义通知 (RegisterDeviceNotification),然后 DBT_CUSTOMEVENT 附带 GUID_IO_VOLUME_LOCK。在所有注册了有关该卷的自定义通知的应用程序处理完消息之前,FSCTL_LOCK_VOLUME 不会返回。

演示项目

演示项目是用 VS6 制作的。它需要来自 Microsoft Platform SDK 的一些头文件。不需要特殊的 LIB。

历史

  • 2011 年 9 月 26 日 - 首次发布。
  • 2011 年 10 月 25 日 - 更正:卸载后的错误是 ERROR_NOT_READY,而不是 ERROR_INVALID_HANDLE,并且在演示项目中缺少 CloseHandle
© . All rights reserved.