Datenträgeraufzählung

GUID_DEVINTERFACE_DISK identifiziert die Geräteschnittstellenklasse des Datenträgers. Die vom System bereitgestellten Datenträgerklassentreiber registrieren Instanzen dieser Klasse für die von ihnen verfügbar gemachten Datenträger. Das Aufzählen der aktivierten Instanzen ist die stapelunabhängige Methode zum Ermitteln von Datenträgergerätepfaden: Sowohl der Legacy-Stapel als auch der native NVMe-Stapel machen diese Schnittstellenklasse verfügbar.

Aus diesem Grund sollten Sie die interface-GUID aufzählen, anstatt sich auf eine Setupklassen-GUID wie GUID_DEVCLASS_DISKDRIVE zu stützen, bei der nicht garantiert ist, dass sie stapelunabhängig ist.

Schnittstellen-GUID vs. Geräteklassen-GUID

GUID_DEVINTERFACE_DISK und GUID_DEVCLASS_DISKDRIVE beantworten verschiedene Fragen und sind nicht austauschbar:

  • GUID_DEVINTERFACE_DISK identifiziert eine Schnittstellenklasse, nicht einen einzelnen Gerätepfad. Durch aufzählen der aktivierten Instanzen werden symbolische Verknüpfungspfade zurückgegeben, die Anwendungen an CreateFileW übergeben können. Das resultierende Handle kann für Vorgänge verwendet werden, die vom Zieldatenträger unterstützt werden, vorbehaltlich der angeforderten Zugriffsrechte und anwendbarer Berechtigungen. Ein aufgezählter Pfad ist keine Garantie dafür, dass das Öffnen des Datenträgers erfolgreich ist: Das Gerät kann entfernt werden oder nicht mehr verfügbar sein, bevor die Anforderung zum Öffnen abgeschlossen ist.
  • GUID_DEVCLASS_DISKDRIVEist eine Gerätesetupklasse, die von PnP/SetupAPI für die Geräteinstallation und -klassifizierung verwendet wird, z. B. Treiberinstallation, Geräte-Manager Gruppierung, INF-Abgleich und ähnliche Wartungsaufgaben. Bei einer Geräteinstanz, die zu dieser Setupklasse gehört, ist nicht sichergestellt, dass sie eine Schnittstelle verfügbar macht, die Sie öffnen können, und die Enumeration nach der Setupklasse sagt nichts darüber aus, welche funktionalen Schnittstellen diese Geräteinstanz gegebenenfalls derzeit verfügbar macht.
  • Der Legacydatenträgerstapel (disk.sys/classpnp.sys) verwendet die GUID_DEVCLASS_DISKDRIVE Setupklasse, während der native NVMe-Stapel (nvmedisk.sys) eine eigene, andere Setupklasse {75416e63-5912-4dfa-ae8f-3efaccaffb14}verwendet. Nur GUID_DEVCLASS_DISKDRIVE aufzuzählen würde daher jeden Datenträger übersehen, der vom nativen NVMe-Stapel verwaltet wird. GUID_DEVINTERFACE_DISK hat diese Lücke nicht, da beide Stapel die gleiche Schnittstelle für jeden Datenträger registrieren, den sie verwalten.

Gültigkeitsbereich der Enumeration

In diesem Beispiel wird eine einmalige Enumerierung der derzeit aktivierten Datenträgergeräteschnittstellen ausgeführt. Sie listet keine Volumes oder Laufwerkbuchstaben auf, und die zurückgegebene Liste ist kein fortlaufend verwalteter Bestand.

CM_GET_DEVICE_INTERFACE_LIST_PRESENT wählt vorhandene/aktivierte Schnittstellen aus. Es wird nicht angegeben, ob ein Datenträger in der Datenträgerverwaltung online ist.

Registrieren Sie für eine lang laufende Anwendung, die Eintreffen und Entfernen nachverfolgt, Benachrichtigungen über die Geräteschnittstelle mit CM_Register_Notification, bevor Sie die anfängliche Enumeration ausführen. Gleichen Sie Benachrichtigungen mit der anfänglichen Liste ab, und behandeln Sie Duplikate, da eine eingehende Schnittstelle in beiden vorkommen kann. Heben Sie die Benachrichtigungsregistrierung mit CM_Unregister_Notification auf, wenn Sie fertig sind. Das Entfernen von Geräten kann weiterhin mit dem Öffnen eines Pfads oder der Durchführung von E/A in Konflikt geraten.

Beispielcode: Aufzählen von Datenträgern

Note

Durch Wiederholungen werden Änderungen zwischen der Größenabfrage und dem Abrufen der Liste behandelt. Dies garantiert jedoch keinen endgültigen Erfolg oder die fortgesetzte Verfügbarkeit der zurückgegebenen Geräte. Das Beispiel wiederholt den Vorgang CR_BUFFER_SMALL ohne Versuchslimit, sodass wiederholte Schnittstelleneingänge theoretisch zu endlosen Wiederholungen führen können. Andere Fehler werden gemeldet und beenden die Aufzählung.

#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;
}