Enumeração de discos

GUID_DEVINTERFACE_DISK identifica a classe de interface do dispositivo de disco. Os drivers de classe de disco fornecidos pelo sistema registram instâncias dessa classe para os discos que expõem. Enumerar suas instâncias habilitadas é a maneira independente da pilha de descobrir caminhos de dispositivo de disco: tanto a pilha herdada quanto a pilha NVMe nativa expõem essa classe de interface.

É por isso que você deve enumerar o GUID da interface em vez de usar como referência um GUID de classe de instalação, como GUID_DEVCLASS_DISKDRIVE, que não tem garantia de ser independente de pilha.

GUID de interface versus GUID da classe de dispositivo

GUID_DEVINTERFACE_DISK e GUID_DEVCLASS_DISKDRIVE respondem a perguntas diferentes e não são intercambiáveis:

  • GUID_DEVINTERFACE_DISK identifica uma classe de interface, não um caminho de dispositivo individual. Enumerar suas instâncias habilitadas retorna caminhos de link simbólico que os aplicativos podem passar para CreateFileW. O identificador resultante pode ser usado para operações compatíveis com o disco de destino, sujeito aos direitos de acesso solicitados e às permissões aplicáveis. Um caminho enumerado não é uma garantia de que abrir o disco terá êxito: o dispositivo pode ser removido ou ficar indisponível antes de a solicitação de abertura ser concluída.
  • GUID_DEVCLASS_DISKDRIVEé uma classe de instalação de dispositivo usada por PnP/SetupAPI para instalação e classificação de dispositivos, como instalação de driver, agrupamento Gerenciador de Dispositivos, correspondência inf e tarefas de manutenção semelhantes. Uma instância de dispositivo pertencente a essa classe de instalação não tem garantia de expor uma interface que você possa abrir; além disso, enumerar por classe de instalação não diz nada sobre quais interfaces funcionais, se houver, essa instância de dispositivo expõe no momento.
  • A pilha de disco herdada (disk.sys/classpnp.sys) usa a classe de instalação GUID_DEVCLASS_DISKDRIVE, enquanto a pilha NVMe nativa (nvmedisk.sys) usa sua própria classe de instalação diferente, {75416e63-5912-4dfa-ae8f-3efaccaffb14}. Só a enumeração GUID_DEVCLASS_DISKDRIVE seria perdida, logo, todos os discos gerenciados pela pilha NVMe nativa. GUID_DEVINTERFACE_DISK não tem essa lacuna, pois ambas as pilhas registram a mesma interface para cada disco que gerenciam.

Escopo de enumeração

Este código de exemplo executa uma enumeração única de interfaces de dispositivo de disco habilitadas no momento. Ele não enumera volumes ou letras de unidade e a lista retornada não é um inventário continuamente mantido.

CM_GET_DEVICE_INTERFACE_LIST_PRESENT seleciona interfaces presentes/habilitadas. Ele não indica se um disco está online no Gerenciamento de Disco.

Para um aplicativo de longa execução que rastreia chegadas e remoções, registre notificações de interface do dispositivo com CM_Register_Notification antes de executar a enumeração inicial. Reconcilie as notificações com a lista inicial e manipule duplicatas, pois uma interface que chega pode aparecer em ambas. Libere o registro de notificação com CM_Unregister_Notification ao finalizar seu uso. A remoção do dispositivo ainda pode ocorrer simultaneamente à abertura de um caminho ou à realização de E/S.

Código de exemplo: enumerar discos

Note

A repetição resolve alterações entre a consulta do tamanho e a recuperação da lista; ela não garante o êxito eventual ou a disponibilidade contínua dos dispositivos retornados. A amostra tenta novamente CR_BUFFER_SMALL sem um limite de tentativas, logo, eventos repetidos da interface podem teoricamente mantê-lo tentando indefinidamente. Outras falhas são relatadas e a enumeração é encerrada.

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