天天看點

實戰DeviceIoControl 之一:通過API通路裝置驅動程式(學習)

Q 在NT/2000/XP中,我想用VC編寫應用程式通路硬體裝置,如擷取磁盤參數、讀寫絕對扇區資料、測試光驅實際速度等,該從哪裡入手呢?

A 在NT/2000/XP中,應用程式可以通過API函數DeviceIoControl來實作對裝置的通路—擷取資訊,發送指令,交換資料等。利用該接口函數向指定的裝置驅動發送正确的控制碼及資料,然後分析它的響應,就可以達到我們的目的。

DeviceIoControl的函數原型為

BOOL DeviceIoControl(

    HANDLE hDevice,              // 裝置句柄

    DWORD dwIoControlCode,       // 控制碼

    LPVOID lpInBuffer,           // 輸入資料緩沖區指針

    DWORD nInBufferSize,         // 輸入資料緩沖區長度

    LPVOID lpOutBuffer,          // 輸出資料緩沖區指針

    DWORD nOutBufferSize,        // 輸出資料緩沖區長度

    LPDWORD lpBytesReturned,     // 輸出資料實際長度單元長度

    LPOVERLAPPED lpOverlapped    // 重疊操作結構指針

);

裝置句柄用來辨別你所通路的裝置。

發送不同的控制碼,可以調用裝置驅動程式的不同類型的功能。在頭檔案winioctl.h中,預定義的标準裝置控制碼,都以IOCTL或FSCTL開頭。例如,IOCTL_DISK_GET_DRIVE_GEOMETRY是對實體驅動器取結構參數(媒體類型、柱面數、每柱面磁道數、每磁道扇區數等)的控制碼,FSCTL_LOCK_VOLUME是對邏輯驅動器的卷加鎖的控制碼。

輸入輸出資料緩沖區是否需要,是何種結構,以及占多少位元組空間,完全由不同裝置的不同操作類型決定。在頭檔案winioctl.h中,已經為标準裝置預定義了一些輸入輸出資料結構。重疊操作結構指針設定為NULL,DeviceIoControl将進行阻塞調用;否則,應在程式設計時按異步操作設計。

Q 裝置句柄是從哪裡獲得的?

A 裝置句柄可以用API函數CreateFile獲得。它的原型為

HANDLE CreateFile(

    LPCTSTR lpFileName,                         // 檔案名/裝置路徑

    DWORD dwDesiredAccess,                      // 通路方式

    DWORD dwShareMode,                          // 共享方式

    LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全描述符指針

    DWORD dwCreationDisposition,                // 建立方式

    DWORD dwFlagsAndAttributes,                 // 檔案屬性及标志

    HANDLE hTemplateFile                        // 模闆檔案的句柄

);

CreateFile這個函數用處很多,這裡我們用它“打開”裝置驅動程式,得到裝置的句柄。操作完成後用CloseHandle關閉裝置句柄。

與普通檔案名有所不同,裝置驅動的“檔案名”(常稱為“裝置路徑”)形式固定為“//./DeviceName”(注意在C程式中該字元串寫法為“.//DeviceName”),DeviceName必須與裝置驅動程式内定義的裝置名稱一緻。

一般地,調用CreateFile獲得裝置句柄時,通路方式參數設定為0或GENERIC_READ|GENERIC_WRITE,共享方式參數設定為FILE_SHARE_READ|FILE_SHARE_WRITE,建立方式參數設定為OPEN_EXISTING,其它參數設定為0或NULL。

Q 可是,我怎麼知道裝置名稱是什麼呢?

A 一些儲存設備的名稱是微軟定義好的,不可能有什麼變化。大體列出如下

軟碟驅動器 A:, B:

硬碟邏輯分區 C:, D:, E:, ...

實體驅動器 PHYSICALDRIVEx

CD-ROM, DVD/ROM CDROMx

錄音帶機 TAPEx

其中,實體驅動器不包括軟驅和光驅。邏輯驅動器可以是IDE/SCSI/PCMCIA/USB接口的硬碟分區(卷)、光驅、MO、CF卡等,甚至是虛拟盤。x=0,1,2 ……

其它的裝置名稱需通過驅動接口的GUID調用裝置管理函數族取得,這裡暫不讨論。

Q 請舉一個簡單的例子說明如何通過DeviceIoControl通路裝置驅動程式。

A 這裡有一個從MSDN上摘抄來的demo程式,示範在NT/2000/XP中如何通過DeviceIoControl擷取硬碟的基本參數。

#include <windows.h>

#include <winioctl.h>

BOOL GetDriveGeometry(DISK_GEOMETRY *pdg)

{

    HANDLE hDevice;               // handle to the drive to be examined

    BOOL bResult;                 // results flag

    DWORD junk;                   // discard results

    hDevice = CreateFile(".//PhysicalDrive0",  // drive to open

                    0,                // no access to the drive

                    FILE_SHARE_READ | // share mode

                    FILE_SHARE_WRITE,

                    NULL,             // default security attributes

                    OPEN_EXISTING,    // disposition

                    0,                // file attributes

                    NULL);            // do not copy file attributes

    if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive

    {

        return (FALSE);

    }

    bResult = DeviceIoControl(hDevice,     // device to be queried

        IOCTL_DISK_GET_DRIVE_GEOMETRY,     // operation to perform

                    NULL, 0,               // no input buffer

                    pdg, sizeof(*pdg),     // output buffer

                    &junk,                 // # bytes returned

                    (LPOVERLAPPED) NULL);  // synchronous I/O

    CloseHandle(hDevice);

    return (bResult);

}

int main(int argc, char *argv[])

{

    DISK_GEOMETRY pdg;            // disk drive geometry structure

    BOOL bResult;                 // generic results flag

    ULONGLONG DiskSize;           // size of the drive, in bytes

    bResult = GetDriveGeometry (&pdg);

    if (bResult)

    {

        printf("Cylinders = %I64d/n", pdg.Cylinders);

        printf("Tracks per cylinder = %ld/n", (ULONG) pdg.TracksPerCylinder);

        printf("Sectors per track = %ld/n", (ULONG) pdg.SectorsPerTrack);

        printf("Bytes per sector = %ld/n", (ULONG) pdg.BytesPerSector);

        DiskSize = pdg.Cylinders.QuadPart * (ULONG)pdg.TracksPerCylinder *

            (ULONG)pdg.SectorsPerTrack * (ULONG)pdg.BytesPerSector;

        printf("Disk size = %I64d (Bytes) = %I64d (Mb)/n", DiskSize,

            DiskSize / (1024 * 1024));

    }

    else

    {

        printf("GetDriveGeometry failed. Error %ld./n", GetLastError());

    }

    return ((int)bResult);

}

Q 如果将裝置名換成“A:”就可以取A盤參數,換成“CDROM0”就可以取CDROM參數,是這樣嗎?

A 這個問題暫不做回答。請動手試一下。

現在我們總結一下通過DeviceIoControl通路裝置驅動程式的“三步曲”:首先用CreateFile取得裝置句柄,然後用DeviceIoControl與裝置進行I/O,最後别忘記用CloseHandle關閉裝置句柄。

繼續閱讀