Enumeración de discos

GUID_DEVINTERFACE_DISK identifica la clase de interfaz de dispositivo de disco. Los controladores de clase de disco proporcionados por el sistema registran instancias de esta clase para los discos que exponen. Enumerar sus instancias habilitadas es la manera independiente de la pila para detectar rutas de acceso de dispositivo de disco: tanto la pila heredada como la pila NVMe nativa exponen esta clase de interfaz.

Este es el motivo por el que debe enumerar el GUID de interfaz en lugar de basarse en un GUID de clase de instalación, como GUID_DEVCLASS_DISKDRIVE, que no se garantiza que sea independiente de la pila.

GUID de interfaz frente al GUID de clase de dispositivo

GUID_DEVINTERFACE_DISK y GUID_DEVCLASS_DISKDRIVE responden a preguntas diferentes y no son intercambiables:

  • GUID_DEVINTERFACE_DISK identifica una clase de interfaz, no una ruta de acceso de dispositivo individual. La enumeración de sus instancias habilitadas devuelve rutas de acceso de vínculo simbólico que las aplicaciones pueden pasar a CreateFileW. El identificador resultante se puede usar para las operaciones compatibles con el disco de destino, sujeto a los derechos de acceso solicitados y a los permisos aplicables. Una ruta de acceso enumerada no es una garantía de que la apertura del disco se realice correctamente: el dispositivo se puede quitar o dejar de estar disponible antes de que se complete la solicitud de apertura.
  • GUID_DEVCLASS_DISKDRIVE es una clase de configuración de dispositivo usada por PnP/SetupAPI para la instalación y clasificación de dispositivos, como la instalación de controladores, la agrupación en el Administrador de dispositivos, la coincidencia de INF y tareas de mantenimiento similares. No se garantiza que una instancia de dispositivo que pertenezca a esta clase de instalación exponga una interfaz que pueda abrirse, y la enumeración por clase de instalación no dice nada sobre qué interfaces funcionales, si las hay, expone actualmente esa instancia de dispositivo.
  • La pila de discos heredada (disk.sys/classpnp.sys) usa la clase de instalación GUID_DEVCLASS_DISKDRIVE, mientras que la pila NVMe nativa (nvmedisk.sys) usa su propia clase de instalación diferente, {75416e63-5912-4dfa-ae8f-3efaccaffb14}. Enumerar únicamente GUID_DEVCLASS_DISKDRIVE dejaría fuera, por tanto, todos los discos administrados por la pila NVMe nativa. GUID_DEVINTERFACE_DISK no tiene esta brecha, ya que ambas pilas registran la misma interfaz para cada disco que administran.

Ámbito de enumeración

En este ejemplo se realiza una enumeración única de interfaces de dispositivo de disco habilitadas actualmente. No enumera volúmenes ni letras de unidad y la lista devuelta no es un inventario mantenido continuamente.

CM_GET_DEVICE_INTERFACE_LIST_PRESENT selecciona interfaces presentes y habilitadas. No indica si un disco está en línea en administración de discos.

En una aplicación de ejecución prolongada que realice un seguimiento de llegadas y retiradas, registre las notificaciones de interfaz de dispositivo con CM_Register_Notification antes de realizar la enumeración inicial. Concilie las notificaciones con la lista inicial y gestione los duplicados, ya que una interfaz entrante puede aparecer en ambas. Libere el registro de notificaciones con CM_Unregister_Notification al finalizar. La eliminación del dispositivo todavía puede entrar en conflicto con la apertura de una ruta de acceso o la ejecución de E/S.

Código de ejemplo: enumerar discos

Note

El reintento gestiona los cambios entre la consulta de tamaño y la recuperación de la lista; no garantiza que acabe teniendo éxito ni la disponibilidad continua de los dispositivos devueltos. El ejemplo vuelve a intentarlo CR_BUFFER_SMALL sin un límite de intentos, por lo que las llegadas repetidas de la interfaz pueden, en teoría, hacer que siga reintentándolo indefinidamente. Los demás errores se notifican y terminan la enumeración.

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