大家好,又见面了,我是你们的朋友全栈君。 实战DeviceIoControl 之一:通过API访问设备驱动程序
Q 在NT/2000/XP中,我想用VC编写应用程序访问硬件设备,如获取磁盘参数、读写绝对扇区数据、测试光驱实际速度等,该从哪里入手呢?
A 在NT/2000/XP中,应用程序可以通过API函数DeviceIoControl来实现对设备的访问—获取信息,发送命令,交换数据等。利用该接口函数向指定的设备驱动发送正确的控制码及数据,然后分析它的响应,就可以达到我们的目的。
DeviceIoControl的函数原型为
代码语言:javascript复制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获得。它的原型为
代码语言:javascript复制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获取硬盘的基本参数。
代码语言:javascript复制/* The code of interest is in the subroutine GetDriveGeometry. The code in main shows how to interpret the results of the IOCTL call. */ #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 = %I64dn", pdg.Cylinders); printf("Tracks per cylinder = %ldn", (ULONG) pdg.TracksPerCylinder); printf("Sectors per track = %ldn", (ULONG) pdg.SectorsPerTrack); printf("Bytes per sector = %ldn", (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关闭设备句柄。
实战DeviceIoControl 之二:获取软盘/硬盘/光盘的参数
Q 在MSDN的那个demo中,将设备名换成“A:”取A盘参数,先用资源管理器读一下盘,再运行这个程序可以成功,但换一张盘后就失败;换成“CDROM0”取CDROM参数,无论如何都不行。这个问题如何解决呢?
A 取软盘参数是从软盘上读取格式化后的信息,也就是必须执行读操作,这一点与硬盘不同。将CreateFile中的访问方式改为GENERIC_READ就行了。
IOCTL_DISK_GET_DRIVE_GEOMETRY这个I/O控制码,对软盘和硬盘有效,但对一些可移动媒介如CD/DVD-ROM、TAPE等就不管用了。要取CDROM参数,还得另辟蹊径。IOCTL_STORAGE_GET_MEDIA_TYPES_EX能够帮我们解决问题。
Q 使用这些I/O控制码,需要什么样的输入输出数据格式呢?
A DeviceIoControl使用这两个控制码时,都不需要输入数据。
IOCTL_DISK_GET_DRIVE_GEOMETRY直接输出一个DISK_GEOMETRY结构:
代码语言:javascript复制typedef struct _DISK_GEOMETRY { LARGE_INTEGER Cylinders; // 柱面数 MEDIA_TYPE MediaType; // 介质类型 DWORD TracksPerCylinder; // 每柱面的磁道数 DWORD SectorsPerTrack; // 每磁道的扇区数 DWORD BytesPerSector; // 每扇区的字节数 } DISK_GEOMETRY;
IOCTL_STORAGE_GET_MEDIA_TYPES_EX输出一个GET_MEDIA_TYPES结构:
代码语言:javascript复制typedef struct _GET_MEDIA_TYPES { DWORD DeviceType; // 设备类型 DWORD MediaInfoCount; // 介质信息条数 DEVICE_MEDIA_INFO MediaInfo[1]; // 介质信息 } GET_MEDIA_TYPES;
让我们来看一下DEVICE_MEDIA_INFO结构的定义:
代码语言:javascript复制typedef struct _DEVICE_MEDIA_INFO { union { struct { LARGE_INTEGER Cylinders; // 柱面数 STORAGE_MEDIA_TYPE MediaType; // 介质类型 DWORD TracksPerCylinder; // 每柱面的磁道数 DWORD SectorsPerTrack; // 每磁道的扇区数 DWORD BytesPerSector; // 每扇区的字节数 DWORD NumberMediaSides; // 介质面数 DWORD MediaCharacteristics; // 介质特性 } DiskInfo; // 硬盘信息 struct { LARGE_INTEGER Cylinders; // 柱面数 STORAGE_MEDIA_TYPE MediaType; // 介质类型 DWORD TracksPerCylinder; // 每柱面的磁道数 DWORD SectorsPerTrack; // 每磁道的扇区数 DWORD BytesPerSector; // 每扇区的字节数 DWORD NumberMediaSides; // 介质面数 DWORD MediaCharacteristics; // 介质特性 } RemovableDiskInfo; // “可移动盘”信息 struct { STORAGE_MEDIA_TYPE MediaType; // 介质类型 DWORD MediaCharacteristics; // 介质特性 DWORD CurrentBlockSize; // 块的大小 } TapeInfo; // 磁带信息 } DeviceSpecific; } DEVICE_MEDIA_INFO;
其中CD-ROM属于“可移动盘”的范围。请注意,GET_MEDIA_TYPES结构本身只定义了一条DEVICE_MEDIA_INFO,额外的DEVICE_MEDIA_INFO需要紧接此结构的另外的空间。
Q 调用方法我了解了,请用VC举个例子来实现我所期待已久的功能吧?
A 好,现在就演示一下如何取软盘/硬盘/光盘的参数。测试时,记得要有软盘/光盘插在驱动器里喔!
首先,用MFC AppWizard生成一个单文档的应用程序,取名为DiskGeometry,让它的View基于CEditView。
然后,添加以下的.h和.cpp文件。
代码语言:javascript复制// // GetDiskGeometry.h // #if !defined(GET_DISK_GEOMETRY_H__) #define GET_DISK_GEOMETRY_H__ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include <winioctl.h> BOOL GetDriveGeometry(const char* filename, DISK_GEOMETRY *pdg); #endif // !defined(GET_DISK_GEOMETRY_H__) // // GetDiskGeometry.cpp // #include "stdafx.h" #include "GetDiskGeometry.h" // IOCTL_STORAGE_GET_MEDIA_TYPES_EX可能返回不止一条DEVICE_MEDIA_INFO,故定义足够的空间 #define MEDIA_INFO_SIZE sizeof(GET_MEDIA_TYPES) 15*sizeof(DEVICE_MEDIA_INFO) // filename -- 用于设备的文件名 // pdg -- 参数缓冲区指针 BOOL GetDriveGeometry(const char* filename, DISK_GEOMETRY *pdg) { HANDLE hDevice; // 设备句柄 BOOL bResult; // DeviceIoControl的返回结果 GET_MEDIA_TYPES *pmt; // 内部用的输出缓冲区 DWORD dwOutBytes; // 输出数据长度 // 打开设备 hDevice = ::CreateFile(filename, // 文件名 GENERIC_READ, // 软驱需要读盘 FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享方式 NULL, // 默认的安全描述符 OPEN_EXISTING, // 创建方式 0, // 不需设置文件属性 NULL); // 不需参照模板文件 if (hDevice == INVALID_HANDLE_VALUE) { // 设备无法打开... return FALSE; } // 用IOCTL_DISK_GET_DRIVE_GEOMETRY取磁盘参数 bResult = ::DeviceIoControl(hDevice, // 设备句柄 IOCTL_DISK_GET_DRIVE_GEOMETRY, // 取磁盘参数 NULL, 0, // 不需要输入数据 pdg, sizeof(DISK_GEOMETRY), // 输出数据缓冲区 &dwOutBytes, // 输出数据长度 (LPOVERLAPPED)NULL); // 用同步I/O // 如果失败,再用IOCTL_STORAGE_GET_MEDIA_TYPES_EX取介质类型参数 if (!bResult) { pmt = (GET_MEDIA_TYPES *)new BYTE[MEDIA_INFO_SIZE]; bResult = ::DeviceIoControl(hDevice, // 设备句柄 IOCTL_STORAGE_GET_MEDIA_TYPES_EX, // 取介质类型参数 NULL, 0, // 不需要输入数据 pmt, MEDIA_INFO_SIZE, // 输出数据缓冲区 &dwOutBytes, // 输出数据长度 (LPOVERLAPPED)NULL); // 用同步I/O if (bResult) { // 注意到结构DEVICE_MEDIA_INFO是在结构DISK_GEOMETRY的基础上扩充的 // 为简化程序,用memcpy代替如下多条赋值语句: // pdg->MediaType = (MEDIA_TYPE)pmt->MediaInfo[0].DeviceSpecific.DiskInfo.MediaType; // pdg->Cylinders = pmt->MediaInfo[0].DeviceSpecific.DiskInfo.Cylinders; // pdg->TracksPerCylinder = pmt->MediaInfo[0].DeviceSpecific.DiskInfo.TracksPerCylinder; // ... ... ::memcpy(pdg, pmt->MediaInfo, sizeof(DISK_GEOMETRY)); } delete pmt; } // 关闭设备句柄 ::CloseHandle(hDevice); return (bResult); }
然后,在Toolbar的IDR_MAINFRAME上添加一个按钮,ID为ID_GET_DISK_GEOMETRY。打开ClassWizard,在DiskGeometryView中
添加ID_GET_DISK_GEOMETRY的映射函数OnGetDiskGeometry。打开DiskGeometryView.cpp,包含头文件GetDiskGeometry.h。
在OnGetDiskGeometry中,添加以下代码
代码语言:javascript复制 const char *szDevName[]= { "\\.\A:", "\\.\B:", "\\.\PhysicalDrive0", "\\.\PhysicalDrive1", "\\.\PhysicalDrive2", "\\.\PhysicalDrive3", "\\.\Cdrom0", "\\.\Cdrom1", }; DISK_GEOMETRY dg; ULONGLONG DiskSize; BOOL bResult; CString strMsg; CString strTmp; for (int i = 0; i < sizeof(szDevName)/sizeof(char*); i ) { bResult = GetDriveGeometry(szDevName[i], &dg); strTmp.Format("rn%s result = %srn", szDevName[i], bResult ? "success" : "failure"); strMsg =strTmp; if (!bResult) continue; strTmp.Format(" Media Type = %drn", dg.MediaType); strMsg =strTmp; strTmp.Format(" Cylinders = %I64drn", dg.Cylinders); strMsg =strTmp; strTmp.Format(" Tracks per cylinder = %ldrn", (ULONG) dg.TracksPerCylinder); strMsg =strTmp; strTmp.Format(" Sectors per track = %ldrn", (ULONG) dg.SectorsPerTrack); strMsg =strTmp; strTmp.Format(" Bytes per sector = %ldrn", (ULONG) dg.BytesPerSector); strMsg =strTmp; DiskSize = dg.Cylinders.QuadPart * (ULONG)dg.TracksPerCylinder * (ULONG)dg.SectorsPerTrack * (ULONG)dg.BytesPerSector; strTmp.Format(" Disk size = %I64d (Bytes) = %I64d (Mb)rn", DiskSize, DiskSize / (1024 * 1024)); strMsg =strTmp; } CEdit& Edit = GetEditCtrl(); Edit.SetWindowText(strMsg);
最后,最后干什么呢?编译,运行……
本文Demo源码:DiskGeometry.zip (21KB)
实战DeviceIoControl 之三:制作磁盘镜像文件
Q DOS命令DISKCOPY给我很深的印象,现在也有许多“克隆”软件,可以对磁盘进行全盘复制。我想,要制作磁盘镜像文件,DeviceIoControl应该很有用武之地吧?
A 是的。这里举一个制作软盘镜像文件,功能类似于“DISKCOPY”的例子。
本例实现其功能的核心代码如下:
代码语言:javascript复制// 打开磁盘 HANDLE OpenDisk(LPCTSTR filename) { HANDLE hDisk; // 打开设备 hDisk = ::CreateFile(filename, // 文件名 GENERIC_READ | GENERIC_WRITE, // 读写方式 FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享方式 NULL, // 默认的安全描述符 OPEN_EXISTING, // 创建方式 0, // 不需设置文件属性 NULL); // 不需参照模板文件 return hDisk; } // 获取磁盘参数 BOOL GetDiskGeometry(HANDLE hDisk, PDISK_GEOMETRY lpGeometry) { DWORD dwOutBytes; BOOL bResult; // 用IOCTL_DISK_GET_DRIVE_GEOMETRY取磁盘参数 bResult = ::DeviceIoControl(hDisk, // 设备句柄 IOCTL_DISK_GET_DRIVE_GEOMETRY, // 取磁盘参数 NULL, 0, // 不需要输入数据 lpGeometry, sizeof(DISK_GEOMETRY), // 输出数据缓冲区 &dwOutBytes, // 输出数据长度 (LPOVERLAPPED)NULL); // 用同步I/O return bResult; } // 从指定磁道开始读磁盘 BOOL ReadTracks(HANDLE hDisk, PDISK_GEOMETRY lpGeometry, LPVOID pBuf, DWORD dwStartCylinder, DWORD dwCylinderNumber) { DWORD VirtBufSize; DWORD BytesRead; // 大小 VirtBufSize = lpGeometry->TracksPerCylinder * lpGeometry->SectorsPerTrack * lpGeometry->BytesPerSector; // 偏移 ::SetFilePointer(hDisk, VirtBufSize*dwStartCylinder, NULL, FILE_BEGIN); return ::ReadFile(hDisk, pBuf, VirtBufSize*dwCylinderNumber, &BytesRead, NULL); } // 从指定磁道开始写磁盘 BOOL WriteTracks(HANDLE hDisk, PDISK_GEOMETRY lpGeometry, LPVOID pBuf, DWORD dwStartCylinder, DWORD dwCylinderNumber) { DWORD VirtBufSize; DWORD BytesWritten; // 大小 VirtBufSize = lpGeometry->TracksPerCylinder * lpGeometry->SectorsPerTrack * lpGeometry->BytesPerSector; // 偏移 ::SetFilePointer(hDisk, VirtBufSize*dwStartCylinder, NULL, FILE_BEGIN); return ::WriteFile(hDisk, pBuf, VirtBufSize*dwCylinderNumber, &BytesWritten, NULL); } // 从指定磁道开始格式化磁盘 BOOL LowLevelFormatTracks(HANDLE hDisk, PDISK_GEOMETRY lpGeometry, DWORD dwStartCylinder, DWORD dwCylinderNumber) { FORMAT_PARAMETERS FormatParameters; PBAD_TRACK_NUMBER lpBadTrack; DWORD dwOutBytes; DWORD dwBufSize; BOOL bResult; FormatParameters.MediaType = lpGeometry->MediaType; FormatParameters.StartCylinderNumber = dwStartCylinder; FormatParameters.EndCylinderNumber = dwStartCylinder dwCylinderNumber - 1; FormatParameters.StartHeadNumber = 0; FormatParameters.EndHeadNumber = lpGeometry->TracksPerCylinder - 1; dwBufSize = lpGeometry->TracksPerCylinder * sizeof(BAD_TRACK_NUMBER); lpBadTrack = (PBAD_TRACK_NUMBER) new BYTE[dwBufSize]; // 用IOCTL_DISK_FORMAT_TRACKS对连续磁道进行低级格式化 bResult = ::DeviceIoControl(hDisk, // 设备句柄 IOCTL_DISK_FORMAT_TRACKS, // 低级格式化 &FormatParameters, sizeof(FormatParameters), // 输入数据缓冲区 lpBadTrack, dwBufSize, // 输出数据缓冲区 &dwOutBytes, // 输出数据长度 (LPOVERLAPPED)NULL); // 用同步I/O delete lpBadTrack; return bResult; } // 将卷锁定 BOOL LockVolume(HANDLE hDisk) { DWORD dwOutBytes; BOOL bResult; // 用FSCTL_LOCK_VOLUME锁卷 bResult = ::DeviceIoControl(hDisk, // 设备句柄 FSCTL_LOCK_VOLUME, // 锁卷 NULL, 0, // 不需要输入数据 NULL, 0, // 不需要输出数据 &dwOutBytes, // 输出数据长度 (LPOVERLAPPED)NULL); // 用同步I/O return bResult; } // 将卷解锁 BOOL UnlockVolume(HANDLE hDisk) { DWORD dwOutBytes; BOOL bResult; // 用FSCTL_UNLOCK_VOLUME开卷锁 bResult = ::DeviceIoControl(hDisk, // 设备句柄 FSCTL_UNLOCK_VOLUME, // 开卷锁 NULL, 0, // 不需要输入数据 NULL, 0, // 不需要输出数据 &dwOutBytes, // 输出数据长度 (LPOVERLAPPED)NULL); // 用同步I/O return bResult; } // 将卷卸下 // 该操作使系统重新辨识磁盘,等效于重新插盘 BOOL DismountVolume(HANDLE hDisk) { DWORD dwOutBytes; BOOL bResult; // 用FSCTL_DISMOUNT_VOLUME卸卷 bResult = ::DeviceIoControl(hDisk, // 设备句柄 FSCTL_DISMOUNT_VOLUME, // 卸卷 NULL, 0, // 不需要输入数据 NULL, 0, // 不需要输出数据 &dwOutBytes, // 输出数据长度 (LPOVERLAPPED)NULL); // 用同步I/O return bResult; }
将软盘保存成镜像文件的步骤简单描述为: 1、创建空的镜像文件。 2、调用OpenDisk打开软盘。成功转3,失败转8。 3、调用LockVolume将卷锁定。成功转4,失败转7。 4、调用GetDiskGeometry获取参数。成功转5,失败转6。 5、将磁盘参数写入镜像文件作为文件头。调用ReadTracks按柱面读出数据,保存在镜像文件中。循环次数等于柱面数。 6、调用UnlockVolume将卷解锁。 7、调用CloseDisk关闭软盘。 8、关闭镜像文件。
将镜像文件载入软盘的步骤简单描述为: 1、打开镜像文件。 2、调用OpenDisk打开软盘。成功转3,失败转11。 3、调用LockVolume将卷锁定。成功转4,失败转10。 4、调用GetDiskGeometry获取参数。成功转5,失败转9。 5、从镜像文件中读出文件头,判断两个磁盘参数是否一致。不一致转6,否则转7。 6、调用LowLevelFormatTracks按柱面格式化软盘。循环次数等于柱面数。成功转7,失败转8。 7、从镜像文件中读出数据,并调用WriteTracks按柱面写入磁盘。循环次数等于柱面数。 8、调用DismountVolume将卷卸下。 9、调用UnlockVolume将卷解锁。 10、调用CloseDisk关闭软盘。 11、关闭镜像文件。
Q 我注意到,磁盘读写和格式化是按柱面进行的,有什么道理吗?
A 没有特别的原因,只是因为在这个例子中可以方便地显示处理进度。
有一点需要特别提及,按绝对地址读写磁盘数据时,“最小单位”是扇区,地址一定要与扇区对齐,长度也要等于扇区长度的整数倍。比如,每扇区512字节,那么起始地址和数据长度都应能被512整除才行。
Q 我忽然产生了一个伟大的想法,用绝对地址读写的方式使用磁盘,包括U盘啦,MO啦,而不是用现成的文件系统,那不是可以将数据保密了吗?
A 当然,只要你喜欢。可千万别在你的系统盘上做试验,否则……可别怪bhw98没有提醒过你喔!
Q 我知道怎么测试光驱的传输速度了,就用上面的方法,读出一定长度数据,除以所需时间,应该可以吧?
A 可以。但取光盘参数时要用IOCTL_STORAGE_GET_MEDIA_TYPES_EX,我们已经探讨过的。
本文Demo源码:FloppyImage.zip (16KB)
Microsoft的例子:vs6samples.exe (134,518KB)
实战DeviceIoControl 之四:获取硬盘的详细信息
Q 用IOCTL_DISK_GET_DRIVE_GEOMETRY或IOCTL_STORAGE_GET_MEDIA_TYPES_EX只能得到很少的磁盘参数,我想获得包括硬盘序列号在内的更加详细的信息,有什么办法呀?
A 确实,用你所说的I/O控制码,只能得到最基本的磁盘参数。获取磁盘出厂信息的I/O控制码,微软在VC/MFC环境中没有开放,在DDK中可以发现一些线索。早先,Lynn McGuire写了一个很出名的获取IDE硬盘详细信息的程序DiskID32,下面的例子是在其基础上经过增删和改进而成的。
本例中,我们要用到ATA/APAPI的IDENTIFY DEVICE指令。ATA/APAPI是国际组织T13起草和发布的IDE/EIDE/UDMA硬盘及其它可移动存储设备与主机接口的标准,至今已经到了ATA/APAPI-7版本。该接口标准规定了ATA/ATAPI设备的输入输出寄存器和指令集。欲了解更详细的ATA/ATAPI技术资料,可访问T13的站点。
用到的常量及数据结构有以下一些:
代码语言:javascript复制/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释
* // IOCTL控制码 // #define DFP_SEND_DRIVE_COMMAND 0x0007c084 #define DFP_SEND_DRIVE_COMMAND CTL_CODE(IOCTL_DISK_BASE, 0x0021, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) // #define DFP_RECEIVE_DRIVE_DATA 0x0007c088 #define DFP_RECEIVE_DRIVE_DATA CTL_CODE(IOCTL_DISK_BASE, 0x0022, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) #define FILE_DEVICE_SCSI 0x0000001B #define IOCTL_SCSI_MINIPORT_IDENTIFY ((FILE_DEVICE_SCSI << 16) 0x0501) #define IOCTL_SCSI_MINIPORT 0x0004D008 // see NTDDSCSI.H for definition // ATA/ATAPI指令 #define IDE_ATA_IDENTIFY 0xEC // ATA的ID指令(IDENTIFY DEVICE) // IDE命令寄存器 typedef struct _IDEREGS { BYTE bFeaturesReg; // 特征寄存器(用于SMART命令) BYTE bSectorCountReg; // 扇区数目寄存器 BYTE bSectorNumberReg; // 开始扇区寄存器 BYTE bCylLowReg; // 开始柱面低字节寄存器 BYTE bCylHighReg; // 开始柱面高字节寄存器 BYTE bDriveHeadReg; // 驱动器/磁头寄存器 BYTE bCommandReg; // 指令寄存器 BYTE bReserved; // 保留 } IDEREGS, *PIDEREGS, *LPIDEREGS; // 从驱动程序返回的状态 typedef struct _DRIVERSTATUS { BYTE bDriverError; // 错误码 BYTE bIDEStatus; // IDE状态寄存器 BYTE bReserved[2]; // 保留 DWORD dwReserved[2]; // 保留 } DRIVERSTATUS, *PDRIVERSTATUS, *LPDRIVERSTATUS; // IDE设备IOCTL输入数据结构 typedef struct _SENDCMDINPARAMS { DWORD cBufferSize; // 缓冲区字节数 IDEREGS irDriveRegs; // IDE寄存器组 BYTE bDriveNumber; // 驱动器号 BYTE bReserved[3]; // 保留 DWORD dwReserved[4]; // 保留 BYTE bBuffer[1]; // 输入缓冲区(此处象征性地包含1字节) } SENDCMDINPARAMS, *PSENDCMDINPARAMS, *LPSENDCMDINPARAMS; // IDE设备IOCTL输出数据结构 typedef struct _SENDCMDOUTPARAMS { DWORD cBufferSize; // 缓冲区字节数 DRIVERSTATUS DriverStatus; // 驱动程序返回状态 BYTE bBuffer[1]; // 输入缓冲区(此处象征性地包含1字节) } SENDCMDOUTPARAMS, *PSENDCMDOUTPARAMS, *LPSENDCMDOUTPARAMS; // IDE的ID命令返回的数据 // 共512字节(256个WORD),这里仅定义了一些感兴趣的项(基本上依据ATA/ATAPI-4) typedef struct _IDINFO { USHORT wGenConfig; // WORD 0: 基本信息字 USHORT wNumCyls; // WORD 1: 柱面数 USHORT wReserved2; // WORD 2: 保留 USHORT wNumHeads; // WORD 3: 磁头数 USHORT wReserved4; // WORD 4: 保留 USHORT wReserved5; // WORD 5: 保留 USHORT wNumSectorsPerTrack; // WORD 6: 每磁道扇区数 USHORT wVendorUnique[3]; // WORD 7-9: 厂家设定值 CHAR sSerialNumber[20]; // WORD 10-19:序列号 USHORT wBufferType; // WORD 20: 缓冲类型 USHORT wBufferSize; // WORD 21: 缓冲大小 USHORT wECCSize; // WORD 22: ECC校验大小 CHAR sFirmwareRev[8]; // WORD 23-26: 固件版本 CHAR sModelNumber[40]; // WORD 27-46: 内部型号 USHORT wMoreVendorUnique; // WORD 47: 厂家设定值 USHORT wReserved48; // WORD 48: 保留 struct { USHORT reserved1:8; USHORT DMA:1; // 1=支持DMA USHORT LBA:1; // 1=支持LBA USHORT DisIORDY:1; // 1=可不使用IORDY USHORT IORDY:1; // 1=支持IORDY USHORT SoftReset:1; // 1=需要ATA软启动 USHORT Overlap:1; // 1=支持重叠操作 USHORT Queue:1; // 1=支持命令队列 USHORT InlDMA:1; // 1=支持交叉存取DMA } wCapabilities; // WORD 49: 一般能力 USHORT wReserved1; // WORD 50: 保留 USHORT wPIOTiming; // WORD 51: PIO时序 USHORT wDMATiming; // WORD 52: DMA时序 struct { USHORT CHSNumber:1; // 1=WORD 54-58有效 USHORT CycleNumber:1; // 1=WORD 64-70有效 USHORT UnltraDMA:1; // 1=WORD 88有效 USHORT reserved:13; } wFieldValidity; // WORD 53: 后续字段有效性标志 USHORT wNumCurCyls; // WORD 54: CHS可寻址的柱面数 USHORT wNumCurHeads; // WORD 55: CHS可寻址的磁头数 USHORT wNumCurSectorsPerTrack; // WORD 56: CHS可寻址每磁道扇区数 USHORT wCurSectorsLow; // WORD 57: CHS可寻址的扇区数低位字 USHORT wCurSectorsHigh; // WORD 58: CHS可寻址的扇区数高位字 struct { USHORT CurNumber:8; // 当前一次性可读写扇区数 USHORT Multi:1; // 1=已选择多扇区读写 USHORT reserved1:7; } wMultSectorStuff; // WORD 59: 多扇区读写设定 ULONG dwTotalSectors; // WORD 60-61: LBA可寻址的扇区数 USHORT wSingleWordDMA; // WORD 62: 单字节DMA支持能力 struct { USHORT Mode0:1; // 1=支持模式0 (4.17Mb/s) USHORT Mode1:1; // 1=支持模式1 (13.3Mb/s) USHORT Mode2:1; // 1=支持模式2 (16.7Mb/s) USHORT Reserved1:5; USHORT Mode0Sel:1; // 1=已选择模式0 USHORT Mode1Sel:1; // 1=已选择模式1 USHORT Mode2Sel:1; // 1=已选择模式2 USHORT Reserved2:5; } wMultiWordDMA; // WORD 63: 多字节DMA支持能力 struct { USHORT AdvPOIModes:8; // 支持高级POI模式数 USHORT reserved:8; } wPIOCapacity; // WORD 64: 高级PIO支持能力 USHORT wMinMultiWordDMACycle; // WORD 65: 多字节DMA传输周期的最小值 USHORT wRecMultiWordDMACycle; // WORD 66: 多字节DMA传输周期的建议值 USHORT wMinPIONoFlowCycle; // WORD 67: 无流控制时PIO传输周期的最小值 USHORT wMinPOIFlowCycle; // WORD 68: 有流控制时PIO传输周期的最小值 USHORT wReserved69[11]; // WORD 69-79: 保留 struct { USHORT Reserved1:1; USHORT ATA1:1; // 1=支持ATA-1 USHORT ATA2:1; // 1=支持ATA-2 USHORT ATA3:1; // 1=支持ATA-3 USHORT ATA4:1; // 1=支持ATA/ATAPI-4 USHORT ATA5:1; // 1=支持ATA/ATAPI-5 USHORT ATA6:1; // 1=支持ATA/ATAPI-6 USHORT ATA7:1; // 1=支持ATA/ATAPI-7 USHORT ATA8:1; // 1=支持ATA/ATAPI-8 USHORT ATA9:1; // 1=支持ATA/ATAPI-9 USHORT ATA10:1; // 1=支持ATA/ATAPI-10 USHORT ATA11:1; // 1=支持ATA/ATAPI-11 USHORT ATA12:1; // 1=支持ATA/ATAPI-12 USHORT ATA13:1; // 1=支持ATA/ATAPI-13 USHORT ATA14:1; // 1=支持ATA/ATAPI-14 USHORT Reserved2:1; } wMajorVersion; // WORD 80: 主版本 USHORT wMinorVersion; // WORD 81: 副版本 USHORT wReserved82[6]; // WORD 82-87: 保留 struct { USHORT Mode0:1; // 1=支持模式0 (16.7Mb/s) USHORT Mode1:1; // 1=支持模式1 (25Mb/s) USHORT Mode2:1; // 1=支持模式2 (33Mb/s) USHORT Mode3:1; // 1=支持模式3 (44Mb/s) USHORT Mode4:1; // 1=支持模式4 (66Mb/s) USHORT Mode5:1; // 1=支持模式5 (100Mb/s) USHORT Mode6:1; // 1=支持模式6 (133Mb/s) USHORT Mode7:1; // 1=支持模式7 (166Mb/s) ??? USHORT Mode0Sel:1; // 1=已选择模式0 USHORT Mode1Sel:1; // 1=已选择模式1 USHORT Mode2Sel:1; // 1=已选择模式2 USHORT Mode3Sel:1; // 1=已选择模式3 USHORT Mode4Sel:1; // 1=已选择模式4 USHORT Mode5Sel:1; // 1=已选择模式5 USHORT Mode6Sel:1; // 1=已选择模式6 USHORT Mode7Sel:1; // 1=已选择模式7 } wUltraDMA; // WORD 88: Ultra DMA支持能力 USHORT wReserved89[167]; // WORD 89-255 } IDINFO, *PIDINFO; // SCSI驱动所需的输入输出共用的结构 typedef struct _SRB_IO_CONTROL { ULONG HeaderLength; // 头长度 UCHAR Signature[8]; // 特征名称 ULONG Timeout; // 超时时间 ULONG ControlCode; // 控制码 ULONG ReturnCode; // 返回码 ULONG Length; // 缓冲区长度 } SRB_IO_CONTROL, *PSRB_IO_CONTROL;
*/
需要引起注意的是IDINFO第57-58 WORD (CHS可寻址的扇区数),因为不满足32位对齐的要求,不可定义为一个ULONG字段。Lynn McGuire的程序里正是由于定义为一个ULONG字段,导致该结构不可用。
以下是核心代码:
代码语言:javascript复制/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释
* // 打开设备 // filename: 设备的“文件名”(设备路径) HANDLE OpenDevice(LPCTSTR filename) { HANDLE hDevice; // 打开设备 hDevice = ::CreateFile(filename, // 文件名 GENERIC_READ | GENERIC_WRITE, // 读写方式 FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享方式 NULL, // 默认的安全描述符 OPEN_EXISTING, // 创建方式 0, // 不需设置文件属性 NULL); // 不需参照模板文件 return hDevice; } // 向驱动发“IDENTIFY DEVICE”命令,获得设备信息 // hDevice: 设备句柄 // pIdInfo: 设备信息结构指针 BOOL IdentifyDevice(HANDLE hDevice, PIDINFO pIdInfo) { PSENDCMDINPARAMS pSCIP; // 输入数据结构指针 PSENDCMDOUTPARAMS pSCOP; // 输出数据结构指针 DWORD dwOutBytes; // IOCTL输出数据长度 BOOL bResult; // IOCTL返回值 // 申请输入/输出数据结构空间 pSCIP = (PSENDCMDINPARAMS)::GlobalAlloc(LMEM_ZEROINIT, sizeof(SENDCMDINPARAMS) - 1); pSCOP = (PSENDCMDOUTPARAMS)::GlobalAlloc(LMEM_ZEROINIT, sizeof(SENDCMDOUTPARAMS) sizeof(IDINFO) - 1); // 指定ATA/ATAPI命令的寄存器值 // pSCIP->irDriveRegs.bFeaturesReg = 0; // pSCIP->irDriveRegs.bSectorCountReg = 0; // pSCIP->irDriveRegs.bSectorNumberReg = 0; // pSCIP->irDriveRegs.bCylLowReg = 0; // pSCIP->irDriveRegs.bCylHighReg = 0; // pSCIP->irDriveRegs.bDriveHeadReg = 0; pSCIP->irDriveRegs.bCommandReg = IDE_ATA_IDENTIFY; // 指定输入/输出数据缓冲区大小 pSCIP->cBufferSize = 0; pSCOP->cBufferSize = sizeof(IDINFO); // IDENTIFY DEVICE bResult = ::DeviceIoControl(hDevice, // 设备句柄 DFP_RECEIVE_DRIVE_DATA, // 指定IOCTL pSCIP, sizeof(SENDCMDINPARAMS) - 1, // 输入数据缓冲区 pSCOP, sizeof(SENDCMDOUTPARAMS) sizeof(IDINFO) - 1, // 输出数据缓冲区 &dwOutBytes, // 输出数据长度 (LPOVERLAPPED)NULL); // 用同步I/O // 复制设备参数结构 ::memcpy(pIdInfo, pSCOP->bBuffer, sizeof(IDINFO)); // 释放输入/输出数据空间 ::GlobalFree(pSCOP); ::GlobalFree(pSCIP); return bResult; } // 向SCSI MINI-PORT驱动发“IDENTIFY DEVICE”命令,获得设备信息 // hDevice: 设备句柄 // pIdInfo: 设备信息结构指针 BOOL IdentifyDeviceAsScsi(HANDLE hDevice, int nDrive, PIDINFO pIdInfo) { PSENDCMDINPARAMS pSCIP; // 输入数据结构指针 PSENDCMDOUTPARAMS pSCOP; // 输出数据结构指针 PSRB_IO_CONTROL pSRBIO; // SCSI输入输出数据结构指针 DWORD dwOutBytes; // IOCTL输出数据长度 BOOL bResult; // IOCTL返回值 // 申请输入/输出数据结构空间 pSRBIO = (PSRB_IO_CONTROL)::GlobalAlloc(LMEM_ZEROINIT, sizeof(SRB_IO_CONTROL) sizeof(SENDCMDOUTPARAMS) sizeof(IDINFO) - 1); pSCIP = (PSENDCMDINPARAMS)((char *)pSRBIO sizeof(SRB_IO_CONTROL)); pSCOP = (PSENDCMDOUTPARAMS)((char *)pSRBIO sizeof(SRB_IO_CONTROL)); // 填充输入/输出数据 pSRBIO->HeaderLength = sizeof(SRB_IO_CONTROL); pSRBIO->Timeout = 10000; pSRBIO->Length = sizeof(SENDCMDOUTPARAMS) sizeof(IDINFO) - 1; pSRBIO->ControlCode = IOCTL_SCSI_MINIPORT_IDENTIFY; ::strncpy ((char *)pSRBIO->Signature, "SCSIDISK", 8); // 指定ATA/ATAPI命令的寄存器值 // pSCIP->irDriveRegs.bFeaturesReg = 0; // pSCIP->irDriveRegs.bSectorCountReg = 0; // pSCIP->irDriveRegs.bSectorNumberReg = 0; // pSCIP->irDriveRegs.bCylLowReg = 0; // pSCIP->irDriveRegs.bCylHighReg = 0; // pSCIP->irDriveRegs.bDriveHeadReg = 0; pSCIP->irDriveRegs.bCommandReg = IDE_ATA_IDENTIFY; pSCIP->bDriveNumber = nDrive; // IDENTIFY DEVICE bResult = ::DeviceIoControl(hDevice, // 设备句柄 IOCTL_SCSI_MINIPORT, // 指定IOCTL pSRBIO, sizeof(SRB_IO_CONTROL) sizeof(SENDCMDINPARAMS) - 1, // 输入数据缓冲区 pSRBIO, sizeof(SRB_IO_CONTROL) sizeof(SENDCMDOUTPARAMS) sizeof(IDINFO) - 1, // 输出数据缓冲区 &dwOutBytes, // 输出数据长度 (LPOVERLAPPED)NULL); // 用同步I/O // 复制设备参数结构 ::memcpy(pIdInfo, pSCOP->bBuffer, sizeof(IDINFO)); // 释放输入/输出数据空间 ::GlobalFree(pSRBIO); return bResult; } // 将串中的字符两两颠倒 // 原因是ATA/ATAPI中的WORD,与Windows采用的字节顺序相反 // 驱动程序中已经将收到的数据全部反过来,我们来个负负得正 void AdjustString(char* str, int len) { char ch; int i; // 两两颠倒 for (i = 0; i < len; i = 2) { ch = str[i]; str[i] = str[i 1]; str[i 1] = ch; } // 若是右对齐的,调整为左对齐 (去掉左边的空格) i = 0; while ((i < len) && (str[i] == ' ')) i ; ::memmove(str, &str[i], len - i); // 去掉右边的空格 i = len - 1; while ((i >= 0) && (str[i] == ' ')) { str[i] = '