磁碟列舉

GUID_DEVINTERFACE_DISK 識別磁碟裝置介面類別。 系統提供的磁碟類別驅動程式會為其提供的磁碟註冊此類別的實例。 列舉其啟用的實例是發現磁碟裝置路徑的非依賴堆疊方式:舊有堆疊與原生 NVMe 堆疊皆暴露此介面類別。

這就是為什麼你應該列舉 介面 GUID,而不是使用像 GUID_DEVCLASS_DISKDRIVE 這類設定類別的 GUID,因為像 GUID_DEVCLASS_DISKDRIVE 這類設定類別的 GUID 並不保證與堆疊無關。

介面 GUID 與裝置類別 GUID 的比較

GUID_DEVINTERFACE_DISK 和 GUID_DEVCLASS_DISKDRIVE 回答不同的問題,且不可互換:

  • GUID_DEVINTERFACE_DISK 識別介面類別,而非單一裝置路徑。 列舉啟用的實例會回傳符號連結路徑,應用程式可將這些路徑傳遞給 CreateFileW。 所產生的 handle 可用於目標磁碟所支援的操作,但須受限於所要求的存取權限與適用的權限。 列舉路徑並不保證開啟磁碟會成功:裝置可能在開啟請求完成前被移除或無法使用。
  • 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;
}