2010-10-31 42 views
8

Deseo enviar un comando de expulsión a un dispositivo USB específico identificado por su VID y PID. Puedo encontrar el dispositivo usando SetupDiEnumDeviceInfo() y SetupDiGetDeviceRegistryProperty() y haciendo coincidir los números VID/PID en la cadena HARDWAREID, pero eso es todo lo que tengo.Buscar y expulsar un dispositivo USB según su VID/PID

Tengo una estructura SP_DEVINFO_DATA y un identificador HDEVINFO. ¿Cómo los relacionaría con una letra de unidad o ruta de volumen para poder enviar un comando de expulsión?

+2

Sería interesante saber por qué esto fue downvoted dos veces. –

+0

¿Por qué se ha votado negativamente? –

Respuesta

8

Bueno, lo descubrí. El artículo de CodeProject vinculado por Luke muestra cómo hacer coincidir la letra de la unidad con la interfaz del dispositivo, que está a la mitad del camino, así que responderé a +1 pero no resuelve el problema en su totalidad.

Necesitaba encontrar la forma de encontrar la instancia del dispositivo para mi dispositivo USB y encontrar una forma de hacerla coincidir con la interfaz del dispositivo. Las funciones CM_Locate_DevNode() y CM_Get_Child() fueron la clave para esto. Finalmente puedo usar un IOCTL para expulsar el dispositivo.

El dispositivo con el que estoy tratando es una unidad de CD-ROM USB por lo que he codificado el tipo de dispositivo en CDROM. No puedo creer cuánto código se requiere para hacer lo que pensé que sería una tarea bastante sencilla (cité a mi cliente 2 horas para escribir este código, me tomó cuatro días descubrirlo). Aquí está el código de trabajo final que con suerte salvará a uno de ustedes de pasar por el mismo infierno que acabo de tener:

#include <SetupAPI.h> 
#include <cfgmgr32.h> 
#include <winioctl.h> 

// Finds the device interface for the CDROM drive with the given interface number. 
DEVINST GetDrivesDevInstByDeviceNumber(long DeviceNumber) 
{ 
    const GUID *guid = &GUID_DEVINTERFACE_CDROM; 

// Get device interface info set handle 
// for all devices attached to system 
    HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); 
    if(hDevInfo == INVALID_HANDLE_VALUE) 
     return 0; 

// Retrieve a context structure for a device interface of a device information set. 
    BYTE        buf[1024]; 
    PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)buf; 
    SP_DEVICE_INTERFACE_DATA   spdid; 
    SP_DEVINFO_DATA     spdd; 
    DWORD       dwSize; 

    spdid.cbSize = sizeof(spdid); 

// Iterate through all the interfaces and try to match one based on 
// the device number. 
    for(DWORD i = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL,guid, i, &spdid); i++) 
    { 
    // Get the device path. 
     dwSize = 0; 
     SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL); 
     if(dwSize == 0 || dwSize > sizeof(buf)) 
      continue; 

     pspdidd->cbSize = sizeof(*pspdidd); 
     ZeroMemory((PVOID)&spdd, sizeof(spdd)); 
     spdd.cbSize = sizeof(spdd); 
     if(!SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd, 
              dwSize, &dwSize, &spdd)) 
      continue; 

    // Open the device. 
     HANDLE hDrive = CreateFile(pspdidd->DevicePath,0, 
            FILE_SHARE_READ | FILE_SHARE_WRITE, 
            NULL, OPEN_EXISTING, 0, NULL); 
     if(hDrive == INVALID_HANDLE_VALUE) 
      continue; 

    // Get the device number. 
     STORAGE_DEVICE_NUMBER sdn; 
     dwSize = 0; 
     if(DeviceIoControl(hDrive, 
          IOCTL_STORAGE_GET_DEVICE_NUMBER, 
          NULL, 0, &sdn, sizeof(sdn), 
          &dwSize, NULL)) 
     { 
     // Does it match? 
      if(DeviceNumber == (long)sdn.DeviceNumber) 
      { 
       CloseHandle(hDrive); 
       SetupDiDestroyDeviceInfoList(hDevInfo); 
       return spdd.DevInst; 
      } 
     } 
     CloseHandle(hDrive); 
    } 

    SetupDiDestroyDeviceInfoList(hDevInfo); 
    return 0; 
} 


// Returns true if the given device instance belongs to the USB device with the given VID and PID. 
bool matchDevInstToUsbDevice(DEVINST device, DWORD vid, DWORD pid) 
{ 
// This is the string we will be searching for in the device harware IDs. 
    TCHAR hwid[64]; 
    _stprintf(hwid, _T("VID_%04X&PID_%04X"), vid, pid); 

// Get a list of hardware IDs for all USB devices. 
    ULONG ulLen; 
    CM_Get_Device_ID_List_Size(&ulLen, NULL, CM_GETIDLIST_FILTER_NONE); 
    TCHAR *pszBuffer = new TCHAR[ulLen]; 
    CM_Get_Device_ID_List(NULL, pszBuffer, ulLen, CM_GETIDLIST_FILTER_NONE); 

// Iterate through the list looking for our ID. 
    for(LPTSTR pszDeviceID = pszBuffer; *pszDeviceID; pszDeviceID += _tcslen(pszDeviceID) + 1) 
    { 
    // Some versions of Windows have the string in upper case and other versions have it 
    // in lower case so just make it all upper. 
     for(int i = 0; pszDeviceID[i]; i++) 
      pszDeviceID[i] = toupper(pszDeviceID[i]); 

     if(_tcsstr(pszDeviceID, hwid)) 
     { 
     // Found the device, now we want the grandchild device, which is the "generic volume" 
      DEVINST MSDInst = 0; 
      if(CR_SUCCESS == CM_Locate_DevNode(&MSDInst, pszDeviceID, CM_LOCATE_DEVNODE_NORMAL)) 
      { 
       DEVINST DiskDriveInst = 0; 
       if(CR_SUCCESS == CM_Get_Child(&DiskDriveInst, MSDInst, 0)) 
       { 
       // Now compare the grandchild node against the given device instance. 
        if(device == DiskDriveInst) 
         return true; 
       } 
      } 
     } 
    } 

    return false; 
} 

// Eject the given drive. 
void ejectDrive(TCHAR driveletter) 
{ 
    TCHAR devicepath[16]; 
    _tcscpy(devicepath, _T("\\\\.\\?:")); 
    devicepath[4] = driveletter; 

    DWORD dwRet = 0; 
    HANDLE hVol = CreateFile(devicepath, GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); 
    if(hVol == INVALID_HANDLE_VALUE) 
     return; 

    if(!DeviceIoControl(hVol, FSCTL_LOCK_VOLUME, 0, 0, 0, 0, &dwRet, 0)) 
     return; 

    if(!DeviceIoControl(hVol, FSCTL_DISMOUNT_VOLUME, 0, 0, 0, 0, &dwRet, 0)) 
     return; 

    DeviceIoControl(hVol, IOCTL_STORAGE_EJECT_MEDIA, 0, 0, 0, 0, &dwRet, 0); 

    CloseHandle(hVol); 
} 

// Find a USB device by it's Vendor and Product IDs. When found, eject it. 
void usbEjectDevice(unsigned vid, unsigned pid) 
{ 
    TCHAR devicepath[8]; 
    _tcscpy(devicepath, _T("\\\\.\\?:")); 

    TCHAR drivepath[4]; 
    _tcscpy(drivepath, _T("?:\\")); 

// Iterate through every drive letter and check if it is our device. 
    for(TCHAR driveletter = _T('A'); driveletter <= _T('Z'); driveletter++) 
    { 
    // We are only interested in CDROM drives. 
     drivepath[0] = driveletter; 
     if(DRIVE_CDROM != GetDriveType(drivepath)) 
      continue; 

    // Get the "storage device number" for the current drive. 
     long DeviceNumber = -1; 
     devicepath[4]  = driveletter; 
     HANDLE hVolume = CreateFile(devicepath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 
             NULL, OPEN_EXISTING, 0, NULL); 
     if(INVALID_HANDLE_VALUE == hVolume) 
      continue; 

     STORAGE_DEVICE_NUMBER sdn; 
     DWORD dwBytesReturned = 0; 
     if(DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, 
          NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL)) 
      DeviceNumber = sdn.DeviceNumber; 
     CloseHandle(hVolume); 
     if(DeviceNumber < 0) 
      continue; 

    // Use the data we have collected so far on our drive to find a device instance. 
     DEVINST DevInst = GetDrivesDevInstByDeviceNumber(DeviceNumber); 

    // If the device instance corresponds to the USB device we are looking for, eject it. 
     if(DevInst) 
     { 
      if(matchDevInstToUsbDevice(DevInst, vid, pid)) 
       ejectDrive(driveletter); 
     } 
    } 
} 
Cuestiones relacionadas