GUID_DEVINTERFACE_DISK 标识磁盘设备接口类。 系统提供的磁盘类驱动程序为它们提供的磁盘注册此类的实例。 枚举其已启用的实例是与堆栈无关的磁盘设备路径发现方式:传统堆栈与本机 NVMe 堆栈均公开了此接口类。
因此,应枚举 接口 GUID,而不是依据设置类 GUID(如 GUID_DEVCLASS_DISKDRIVE),因为后者并不能保证与堆栈无关。
接口 GUID 与设备类 GUID
GUID_DEVINTERFACE_DISK 和 GUID_DEVCLASS_DISKDRIVE 回答不同的问题,不可互换:
-
GUID_DEVINTERFACE_DISK标识接口类,而不是单个设备路径。 枚举其启用的实例将返回应用程序可以传递给 CreateFileW 的符号链接路径。 生成的句柄可用于目标磁盘支持的操作,并受请求的访问权限和适用权限的约束。 枚举路径不能保证打开磁盘会成功:在打开请求完成之前,设备可能会被移除或不可用。 -
GUID_DEVCLASS_DISKDRIVE是 PnP/SetupAPI 用于设备安装和分类的设备安装类,例如驱动程序安装、设备管理器分组、INF 匹配和类似的服务任务。 属于此安装程序类的设备实例并不保证会公开可供打开的接口,且按安装程序类进行枚举完全无法说明该设备实例当前公开了哪些功能接口(如果有)。 - 旧磁盘堆栈(
disk.sys/classpnp.sys)使用GUID_DEVCLASS_DISKDRIVE安装类,而原生 NVMe 堆栈(nvmedisk.sys)使用其自身的不同安装类{75416e63-5912-4dfa-ae8f-3efaccaffb14}。 因此,仅GUID_DEVCLASS_DISKDRIVE枚举将错过本机 NVMe 堆栈管理的每个磁盘。GUID_DEVINTERFACE_DISK不存在这种差距,因为两个堆栈都会为其管理的每个磁盘注册相同的接口。
枚举范围
此示例执行当前已启用磁盘设备接口的一次性枚举。 它不枚举卷或盘符,并且返回的列表不是持续维护的清单。
CM_GET_DEVICE_INTERFACE_LIST_PRESENT 用于选择处于存在/已启用状态的接口。 它不指示磁盘是否在磁盘管理中处于联机状态。
对于跟踪到达和删除的长时间运行的应用程序,请在执行初始枚举之前,在 CM_Register_Notification 注册设备接口通知。 将通知与初始列表进行核对,并处理重复项,因为到达接口可能会同时出现在两者之中。 完成后,使用 CM_Unregister_Notification 注销通知注册。 设备删除仍可与打开路径或执行 I/O 发生竞态。
示例代码:枚举磁盘
Note
重试机制可处理大小查询与列表检索之间发生的更改;但它并不能保证最终会成功,也无法保证所返回设备的持续可用性。 示例对 CR_BUFFER_SMALL 进行重试时未设置尝试次数限制,因此重复到达的接口在理论上可能会导致其无限期持续重试。 系统会报告其余故障并终止枚举。
#define INITGUID
#include <windows.h>
#include <ntddstor.h> // GUID_DEVINTERFACE_DISK
#include <cfgmgr32.h>
#include <stdio.h>
#include <stdlib.h> // malloc, free
#include <wchar.h> // wcslen
#pragma comment(lib, "cfgmgr32.lib")
//
// Opens and closes the disk using its device interface path.
// Zero desired access is intentional: this sample does not read or write data.
// When extending it, request the access required by the intended operation:
// GENERIC_READ for reads, GENERIC_WRITE for writes, and the access required
// by the specific IOCTL. Applicable device permissions also apply.
//
static void
ProcessDisk(
const wchar_t* DevicePath
)
{
HANDLE handle;
handle = CreateFileW(
DevicePath,
0, // No data read/write access requested.
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (handle == INVALID_HANDLE_VALUE)
{
wprintf(L" open failed (%lu): %s\n", GetLastError(), DevicePath);
return;
}
// TODO: perform a supported query whose access requirements are satisfied
// by this handle. Adjust the CreateFileW access argument before adding
// operations that require read or write access.
CloseHandle(handle);
}
int
wmain(
void
)
{
wchar_t* list;
CONFIGRET cr;
ULONG len;
DWORD index;
len = 0;
RetryEnumeration:
//
// Select currently enabled disk device interfaces.
// This does not indicate the disk's online/offline state.
//
cr = CM_Get_Device_Interface_List_SizeW(
&len,
(LPGUID)&GUID_DEVINTERFACE_DISK,
NULL,
CM_GET_DEVICE_INTERFACE_LIST_PRESENT
);
if (cr != CR_SUCCESS || len == 0)
{
wprintf(L"CM_Get_Device_Interface_List_SizeW failed: 0x%lx\n", cr);
return 1;
}
//
// The result is a REG_MULTI_SZ style buffer: a contiguous run of
// NUL-terminated device paths, terminated by an extra empty string.
// 'len' is expressed in characters (including all NULs), so size the
// allocation in wchar_t units, not bytes.
//
list = (wchar_t*)malloc((size_t)len * sizeof(wchar_t));
if (list == NULL)
{
wprintf(L"Out of memory allocating %lu characters\n", len);
return 1;
}
cr = CM_Get_Device_Interface_ListW(
(LPGUID)&GUID_DEVINTERFACE_DISK,
NULL,
list,
len,
CM_GET_DEVICE_INTERFACE_LIST_PRESENT
);
if (cr == CR_BUFFER_SMALL)
{
// An interface may have arrived after the size query.
free(list);
goto RetryEnumeration;
}
if (cr != CR_SUCCESS)
{
wprintf(L"CM_Get_Device_Interface_ListW failed: 0x%lx\n", cr);
free(list);
return 1;
}
//
// Walk the multi-string buffer, one NUL-terminated device path per
// iteration, stopping at the empty string that terminates the list.
// 'list' is guaranteed NUL-terminated by the API contract, so this
// cannot run past the allocation.
//
index = 0;
for (wchar_t* current = list; *current != L'\0'; current += wcslen(current) + 1)
{
wprintf(L"[%lu] %s\n", index, current);
ProcessDisk(current);
index++;
}
free(list);
return 0;
}