windows的磁盤操作之七——獲取當前所有的物理磁盤號

 

有了前幾節的基礎後,本節給出一個更復雜但卻非常實用的例子。
很多情況下,我們想知道當前系統下安裝了多少塊磁盤,他們的物理驅動器號都是多少,每一塊磁盤上有多少個分區,分區號怎麼分佈,每個分區大小是多少。這就類似於我們打開windows的磁盤管理看到的那種非常清晰的列表。對於後幾個問題,我們根據物理驅動器號調用第五節http://cutebunny.blog.51cto.com/301216/624567中的GetPartitionLetterFromPhysicalDrive函數,以及第三節http://cutebunny.blog.51cto.com/301216/624079中的GetDiskDriveLayout函數即可搞定。那麼我們這一節的重點放在如何獲得當前所有物理驅動器號上。
先引入一個新的概念,設備GUID,它是同類設備統一併且唯一的標識碼。對於磁盤,GUIDGUID_DEVINTERFACE_DISK,具體值爲{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}windows提供一組API,可以通過GUID枚舉出所有該類型的設備。先給出幾個相關API的簡要介紹
 
HDEVINFO
SetupDiGetClassDevs(
IN LPGUID  ClassGuid,  OPTIONAL
IN PCTSTR  Enumerator,  OPTIONAL
IN HWND  hwndParent,  OPTIONAL
IN DWORD  Flags
);
其中,ClassGuid填入我們感興趣的設備GUID,該函數返回滿足查詢條件的一組設備的信息集合的句柄,該句柄就是獲取設備信息的關鍵鑰匙。
 
WINSETUPAPI BOOL WINAPI
SetupDiEnumDeviceInterfaces(
IN HDEVINFO  DeviceInfoSet,
IN PSP_DEVINFO_DATA  DeviceInfoData,  OPTIONAL
IN LPGUID  InterfaceClassGuid,
IN DWORD  MemberIndex,
OUT PSP_DEVICE_INTERFACE_DATA  DeviceInterfaceData
);
該函數枚舉SetupDiGetClassDevs獲得的句柄中包含的所有設備。參數DeviceInfoSet填入我們上一步中獲得的句柄,InterfaceClassGuid仍舊是我們感興趣的GUIDMemberIndex爲設備在集合中的索引,從0開始計數,最後DeviceInterfaceData是輸出參數,存儲枚舉出的設備接口,後續可通過此接口獲得詳細的設備信息。
注意,參數DeviceInterfaceData.cbSize在調用前必須初始化爲sizeof(SP_DEVICE_INTERFACE_DATA),這是函數的強制要求。
 
WINSETUPAPI BOOL WINAPI
SetupDiGetDeviceInterfaceDetail(
IN HDEVINFO  DeviceInfoSet,
IN PSP_DEVICE_INTERFACE_DATA  DeviceInterfaceData,
OUT PSP_DEVICE_INTERFACE_DETAIL_DATA  DeviceInterfaceDetailData,  OPTIONAL
IN DWORD  DeviceInterfaceDetailDataSize,
OUT PDWORD  RequiredSize,  OPTIONAL
OUT PSP_DEVINFO_DATA  DeviceInfoData  OPTIONAL
);
該函數根據上兩步中的句柄和接口獲取設備的詳細信息數據。參數DeviceInfoSetDeviceInterfaceData在上兩步中獲得。輸出參數DeviceInterfaceDetailData存儲着設備信息數據,這個結構體中的成員DevicePath就是我們辛辛苦苦找尋的東西了。用它可以作爲設備名調用CreateFile函數打開設備,之後的操作,嘿嘿,你懂的
 
下面是具體代碼
/******************************************************************************
* Function: get device path from GUID
* input: lpGuid, GUID pointer
* output: pszDevicePath, device paths
* return: Succeed, the amount of found device paths
*         Fail, -1
******************************************************************************/
DWORD GetDevicePath(LPGUID lpGuid, CHAR **pszDevicePath)
{
    HDEVINFO hDevInfoSet;
    SP_DEVICE_INTERFACE_DATA ifdata;
    PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail;
    DWORD nCount;
    BOOL result;
 
    //get a handle to a device information set
    hDevInfoSet = SetupDiGetClassDevs(
                    lpGuid,      // class GUID
                    NULL,        // Enumerator
                    NULL,        // hwndParent
                    DIGCF_PRESENT | DIGCF_DEVICEINTERFACE    // present devices
                    );
 
    //fail...
    if (hDevInfoSet == INVALID_HANDLE_VALUE)
    {
        fprintf(stderr, "IOCTL_STORAGE_GET_DEVICE_NUMBER Error: %ld\n", GetLastError());
        return (DWORD)-1;
    }
 
    pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(INTERFACE_DETAIL_SIZE);
    if (pDetail == NULL)
    {
        return (DWORD)-1;
    }
    pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
 
    nCount = 0;
    result = TRUE;
 
    // device index = 0, 1, 2... test the device interface one by one
    while (result)
    {
        ifdata.cbSize = sizeof(ifdata);
 
        //enumerates the device interfaces that are contained in a device information set
        result = SetupDiEnumDeviceInterfaces(
                    hDevInfoSet,     // DeviceInfoSet
                    NULL,            // DeviceInfoData
                    lpGuid,          // GUID
                    nCount,   // MemberIndex
                    &ifdata        // DeviceInterfaceData
                    );
        if (result)
        {
            // get details about a device interface
            result = SetupDiGetDeviceInterfaceDetail(
                        hDevInfoSet,    // DeviceInfoSet
                        &ifdata,        // DeviceInterfaceData
                        pDetail,        // DeviceInterfaceDetailData
                        INTERFACE_DETAIL_SIZE,    // DeviceInterfaceDetailDataSize
                        NULL,           // RequiredSize
                        NULL          // DeviceInfoData
                        );
            if (result)
            {
                // copy the path to output buffer
                strcpy(pszDevicePath[nCount], pDetail->DevicePath);
                //printf("%s\n", pDetail->DevicePath);
                nCount++;
            }
        }
    }
 
    free(pDetail);
    (void)SetupDiDestroyDeviceInfoList(hDevInfoSet);
 
    return nCount;
}
執行完畢後,所有滿足條件的磁盤設備名稱都存儲在字符串數組pszDevicePath中。有了這個關鍵的數組,後面就可以爲所欲爲了。
 
以下是獲得所有物理磁盤號的完整代碼
/******************************************************************************
* Function: get all present disks' physical number
* input: N/A
* output: ppDisks, array of disks' physical number
* return: Succeed, the amount of present disks
*         Fail, -1
******************************************************************************/
DWORD GetAllPresentDisks(DWORD **ppDisks)
{
    CHAR *szDevicePath[MAX_DEVICE];        // device path
    DWORD nDevice;
    HANDLE hDevice;
    STORAGE_DEVICE_NUMBER number;
    BOOL result;
    DWORD readed;
    WORD i, j;
 
    for (i = 0; i < MAX_DEVICE; i++)
    {
        szDevicePath[i] = (CHAR *)malloc(INTERFACE_DETAIL_SIZE);
        if (NULL == szDevicePath[i])
        {
            for (j = 0; j < i; j++)
            {
                free(szDevicePath[i]);
            }
            return (DWORD)-1;
        }
    }
 
    // get the device paths
    nDevice = GetDevicePath(const_cast<LPGUID>(&GUID_DEVINTERFACE_DISK), szDevicePath);
    if ((DWORD)-1 == nDevice)
    {
        for (i = 0; i < MAX_DEVICE; i++)
        {
            free(szDevicePath[i]);
        }
        return (DWORD)-1;
    }
 
    *ppDisks = (DWORD *)malloc(sizeof(DWORD) * nDevice);
    // get the disk's physical number one by one
    for (i = 0; i < nDevice; i++)
    {
        hDevice = CreateFile(
                    szDevicePath[i], // drive to open
                    GENERIC_READ | GENERIC_WRITE,     // access to the drive
                    FILE_SHARE_READ | FILE_SHARE_WRITE, //share mode
                    NULL,             // default security attributes
                    OPEN_EXISTING,    // disposition
                    0,                // file attributes
                    NULL            // do not copy file attribute
                    );
        if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive
        {
            for (j = 0; j < MAX_DEVICE; j++)
            {
                free(szDevicePath[j]);
            }
            free(*ppDisks);
            fprintf(stderr, "CreateFile() Error: %ld\n", GetLastError());
            return DWORD(-1);
        }
        result = DeviceIoControl(
                    hDevice,                // handle to device
                    IOCTL_STORAGE_GET_DEVICE_NUMBER, // dwIoControlCode
                    NULL,                            // lpInBuffer
                    0,                               // nInBufferSize
                    &number,           // output buffer
                    sizeof(number),         // size of output buffer
                    &readed,       // number of bytes returned
                    NULL      // OVERLAPPED structure
                    );
        if (!result) // fail
        {
            fprintf(stderr, "IOCTL_STORAGE_GET_DEVICE_NUMBER Error: %ld\n", GetLastError());
            for (j = 0; j < MAX_DEVICE; j++)
            {
                free(szDevicePath[j]);
            }
            free(*ppDisks);
            (void)CloseHandle(hDevice);
            return (DWORD)-1;
        }
        *(*ppDisks + i) = number.DeviceNumber;
 
        (void)CloseHandle(hDevice);
    }
 
    for (i = 0; i < MAX_DEVICE; i++)
    {
        free(szDevicePath[i]);
    }
    return nDevice;
}
代碼說明:
1. 調用函數GetDevicePath獲得前面所說的磁盤設備名稱數組。
2. 對每一個磁盤設備,調用CreateFile打開並獲得設備句柄。
3. 調用操作碼爲IOCTL_STORAGE_GET_DEVICE_NUMBERDeviceIoControl函數獲得磁盤物理驅動器號。
4. 將所有物理磁盤號存入數組返回。
 
大功告成了。可能有朋友會問,GetDevicePath不是已經獲得了磁盤路徑麼,你前面說過,這個路徑不是\\.\PhysicalDriveX就是\\.\X: ,那我們解析一下這個字符串不就可以獲得磁盤號或者盤符了麼。很可惜,這裏的磁盤路徑出現了第三種形式,而且是毫無章法的形式。打開函數GetDevicePath中的註釋行//printf("%s\n", pDetail->DevicePath);將這種形式的路徑打印出來,可以看到類似爲
\\?\ide#diskwdc_wd1600aajs-08b4a0___________________01.03a01#5&245a6b6d&0&0.0.0#
{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
\\?\ide#diskwdc_wd1600aajs-08b4a0___________________01.03a01#5&37141c12&0&0.1.0#
{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
所以,沒辦法,我們還是得用DeviceIoControl找出磁盤號。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章