Énumération des disques

GUID_DEVINTERFACE_DISK identifie la classe d’interface d’appareil disque. Les pilotes de classe de disque fournis par le système inscrivent des instances de cette classe pour les disques qu’ils exposent. Énumérer ses instances activées est la méthode, quelle que soit la pile utilisée, pour découvrir les chemins d’accès des périphériques de disque : la pile héritée et la pile NVMe native exposent cette classe d’interface.

C’est pourquoi vous devez énumérer le GUID d’interface plutôt que de vous baser sur un GUID de classe d’installation, tel que GUID_DEVCLASS_DISKDRIVE, qui ne garantit pas une compatibilité avec toutes les piles de stockage.

GUID d’interface vs. GUID de classe d’appareil

GUID_DEVINTERFACE_DISK et GUID_DEVCLASS_DISKDRIVE répondent à différentes questions et ne sont pas interchangeables :

  • GUID_DEVINTERFACE_DISK identifie une classe d’interface, et non un chemin d’accès d’appareil individuel. L’énumération de ses instances activées retourne des chemins de liaison symbolique que les applications peuvent transmettre à CreateFileW. Le descripteur résultant peut être utilisé pour les opérations prises en charge par le disque cible, sous réserve des droits d’accès demandés et des autorisations applicables. Un chemin énuméré n’est pas une garantie que l’ouverture du disque réussira : le périphérique peut être retiré ou devenir indisponible avant la fin de la requête d’ouverture.
  • GUID_DEVCLASS_DISKDRIVE est une classe d’installation de périphériques utilisée par PnP/SetupAPI pour l’installation et la classification des périphériques, notamment l’installation des pilotes, le regroupement dans le Gestionnaire de périphériques, la correspondance des fichiers INF et les opérations de maintenance similaires. Il n’est pas garanti qu’une instance d’appareil appartenant à cette classe d’installation expose une interface que vous pouvez ouvrir, et l’énumération par classe d’installation n’indique rien sur les interfaces fonctionnelles, le cas échéant, que cette instance d’appareil expose actuellement.
  • La pile de disques héritée (disk.sys/classpnp.sys) utilise la classe d’installation GUID_DEVCLASS_DISKDRIVE, tandis que la pile NVMe native (nvmedisk.sys) utilise sa propre classe d’installation, différente, {75416e63-5912-4dfa-ae8f-3efaccaffb14}. Énumérer uniquement GUID_DEVCLASS_DISKDRIVE manquerait donc tous les disques gérés par la pile NVMe native. GUID_DEVINTERFACE_DISK n’a pas cet écart, car les deux stacks enregistrent la même interface pour chaque disque qu’elles gèrent.

Étendue de l’énumération

Cet exemple effectue une énumération ponctuelle des interfaces d’appareil disque actuellement activées. Elle n’énumère pas les volumes ou les lettres de lecteur, et la liste retournée n’est pas un inventaire géré en permanence.

CM_GET_DEVICE_INTERFACE_LIST_PRESENT sélectionne les interfaces présentes et activées. Il n’indique pas si un disque est en ligne dans Gestion des disques.

Pour une application de longue durée qui effectue le suivi des arrivées et des suppressions, inscrivez des notifications d’interface d’appareil avec CM_Register_Notification avant d’effectuer l’énumération initiale. Rapprochez les notifications avec la liste initiale et gérez les doublons, car une interface arrivante peut apparaître dans les deux. Libérez l’enregistrement de notification avec CM_Unregister_Notification lorsque vous avez terminé. Le retrait d’un périphérique peut toujours survenir simultanément à l’ouverture d’un chemin d’accès ou à l’exécution d’une opération d’E/S.

Exemple de code : énumérer les disques

Note

Les nouvelles tentatives permettent de gérer les modifications survenues entre la requête de taille et la récupération de la liste. Elles ne garantissent ni la réussite à terme de l’opération ni la disponibilité continue des périphériques retournés. L’exemple relance l’opération CR_BUFFER_SMALL sans limite de tentatives. Par conséquent, des arrivées répétées de nouvelles interfaces peuvent, en théorie, entraîner des tentatives indéfinies. Les autres erreurs sont signalées et entraînent l’arrêt de l’énumération.

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