实战DeviceIoControl.docx
《实战DeviceIoControl.docx》由会员分享,可在线阅读,更多相关《实战DeviceIoControl.docx(51页珍藏版)》请在冰豆网上搜索。
实战DeviceIoControl
实战DeviceIoControl
实战DeviceIoControl之一:
通过API访问设备驱动程序
问题:
在NT/2000/XP中,我想用VC编写应用程序访问硬件设备,如获取磁盘参数、读写绝对扇区数据、测试光驱实际速度等,该从哪里入手呢?
回答:
在NT/2000/XP中,应用程序可以通过API函数DeviceIoControl来实现对设备的访问—获取信息,发送命令,交换数据等。
利用该接口函数向指定的设备驱动发送正确的控制码及数据,然后分析它的响应,就可以达到我们的目的。
DeviceIoControl的函数原型为
BOOLDeviceIoControl(
HANDLEhDevice,//设备句柄
DWORDdwIoControlCode,//控制码
LPVOIDlpInBuffer,//输入数据缓冲区指针
DWORDnInBufferSize,//输入数据缓冲区长度
LPVOIDlpOutBuffer,//输出数据缓冲区指针
DWORDnOutBufferSize,//输出数据缓冲区长度
LPDWORDlpBytesReturned,//输出数据实际长度单元长度
LPOVERLAPPEDlpOverlapped//重叠操作结构指针);
设备句柄用来标识你所访问的设备。
发送不同的控制码,可以调用设备驱动程序的不同类型的功能。
在头文件winioctl.h中,预定义的标准设备控制码,都以IOCTL或FSCTL开头。
例如,
IOCTL_DISK_GET_DRIVE_GEOMETRY是对物理驱动器取结构参数(介质类型、柱面数、每柱面磁道数、每磁道扇区数等)的控制码,FSCTL_LOCK_VOLUME是对逻辑驱动器的卷加锁的控制码。
输入输出数据缓冲区是否需要,是何种结构,以及占多少字节空间,完全由不同设备的不同操作类型决定。
在头文件winioctl.h中,已经为标准设备预定义了一些输入输出数据结构。
重叠操作结构指针设置为NULL,DeviceIoControl将进行阻塞调用;否则,应在编程时按异步操作设计。
问题:
设备句柄是从哪里获得的?
回答:
设备句柄可以用API函数CreateFile获得。
它的原型为
HANDLECreateFile(
LPCTSTRlpFileName,//文件名/设备路径
DWORDdwDesiredAccess,//访问方式
DWORDdwShareMode,//共享方式
LPSECURITY_ATTRIBUTESlpSecurityAttributes,//安全描述符指针
DWORDdwCreationDisposition,//创建方式
DWORDdwFlagsAndAttributes,//文件属性及标志
HANDLEhTemplateFile//模板文件的句柄);
CreateFile这个函数用处很多,这里我们用它“打开”设备驱动程序,得到设备的句柄。
操作完成后用CloseHandle关闭设备句柄。
与普通文件名有所不同,设备驱动的“文件名”(常称为“设备路径”)形式固定为“\\.\DeviceName”(注意在C程序中该字符串写法为“\\\\.\\DeviceName”),DeviceName必须与设备驱动程序内定义的设备名称一致。
一般地,调用CreateFile获得设备句柄时,访问方式参数设置为0或GENERIC_READ|GENERIC_WRITE,
共享方式参数设置为FILE_SHARE_READ|FILE_SHARE_WRITE,创建方式参数设置为OPEN_EXISTING,其它参数设置为0或NULL。
问题:
可是,我怎么知道设备名称是什么呢?
回答:
一些存储设备的名称是微软定义好的,不可能有什么变化。
大体列出如下
软盘驱动器A:
B:
硬盘逻辑分区C:
D:
E:
...
物理驱动器PHYSICALDRIVEx
CD-ROM,DVD/ROMCDROMx
磁带机TAPExIDE是什么?
接口吗?
其中,物理驱动器不包括软驱和光驱。
逻辑驱动器可以是IDE/SCSI/PCMCIA/USB接口的硬盘分区(卷)、光驱、MO、CF卡等,甚至是虚拟盘。
x=0,1,2……
其它的设备名称需通过驱动接口的GUID调用设备管理函数族取得,这里暂不讨论。
驱动接口的GUIDS是什么意思?
问题:
请举一个简单的例子说明如何通过DeviceIoControl访问设备驱动程序。
回答:
这里有一个从MSDN上摘抄来的demo程序,演示在NT/2000/XP中如何通过DeviceIoControl获取硬盘的基本参数。
/*ThecodeofinterestisinthesubroutineGetDriveGeometry.
ThecodeinmainshowshowtointerprettheresultsoftheIOCTLcall.*/
#include
#include
BOOLGetDriveGeometry(DISK_GEOMETRY*pdg)
{
HANDLEhDevice;//handletothedrivetobeexamined
BOOLbResult;
DWORDjunk;//discardresults__junk垃圾,discard丢弃
hDevice=CreateFile("\\\\.\\PhysicalDrive0",0,FILE_SHARE_READ
//微软定义的,本来就有
|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,NULL);
if(hDevice==INVALID_HANDLE_VALUE)
return(FALSE);
bResult=DeviceIoControl(hDevice,IOCTL_DISK_GET_DRIVE_GEOMETRY,
NULL,0,pdg,sizeof(*pdg),&junk,(LPOVERLAPPED)NULL);
CloseHandle(hDevice);
return(bResult);
}
intmain(intargc,char*argv[])
{
DISK_GEOMETRYpdg;//diskdrivegeometrystructure
BOOLbResult;//genericresultsflag
ULONGLONGDiskSize;//sizeofthedrive,inbytes64位,因为Cylinders是LARGE_INTEGER的数据类型
bResult=GetDriveGeometry(&pdg);
if(bResult)
{
printf("Cylinders=%I64d\n",pdg.Cylinders);
printf("Trackspercylinder=%ld\n",(ULONG)pdg.TracksPerCylinder);
printf("Sectorspertrack=%ld\n",(ULONG)pdg.SectorsPerTrack);
printf("Bytespersector=%ld\n",(ULONG)pdg.BytesPerSector);
DiskSize=pdg.Cylinders.QuadPart*(ULONG)pdg.TracksPerCylinder
*(ULONG)pdg.SectorsPerTrack*(ULONG)pdg.BytesPerSector;
printf("Disksize=%I64d(Bytes)=%I64d(Mb)\n",
DiskSize,DiskSize/(1024*1024));
}
else
{
printf("GetDriveGeometryfailed.Error%ld.\n",GetLastError());
}
return((int)bResult);
}
问题:
如果将设备名换成“A:
”就可以取A盘参数,换成“CDROM0”就可以取CDROM参数,是这样吗?
回答:
这个问题暂不做回答。
请动手试一下。
现在我们总结一下通过DeviceIoControl访问设备驱动程序的“三步曲”:
首先用CreateFile取得设备句柄,然后用DeviceIoControl与设备进行I/O,最后别忘记用CloseHandle关闭设备句柄。
实战DeviceIoControl之二:
获取软盘/硬盘/光盘的参数
问题:
在MSDN的那个demo中,将设备名换成“A:
”取A盘参数,先用资源管理器读一下盘,再运行这个程序可以成功,但换一张盘后就失败;换成“CDROM0”取CDROM参数,无论如何都不行。
这个问题如何解决呢?
回答:
取软盘参数是从软盘上读取格式化后的信息,也就是必须执行读操作,这一点与硬盘不同。
将CreateFile中的访问方式改为GENERIC_READ就行了。
IOCTL_DISK_GET_DRIVE_GEOMETRY这个I/O控制码,对软盘和硬盘有效,但对一些可移动媒介如CD/DVD-ROM、TAPE等就不管用了。
要取CDROM参数,还得另辟蹊径。
IOCTL_STORAGE_GET_MEDIA_TYPES_EX能够帮我们解决问题。
问题:
使用这些I/O控制码,需要什么样的输入输出数据格式呢?
回答:
DeviceIoControl使用这两个控制码时,都不需要输入数据。
IOCTL_DISK_GET_DRIVE_GEOMETRY直接输出一个DISK_GEOMETRY结构:
typedefstruct_DISK_GEOMETRY
{
LARGE_INTEGERCylinders;//柱面数
MEDIA_TYPEMediaType;//介质类型
DWORDTracksPerCylinder;//每柱面的磁道数
DWORDSectorsPerTrack;//每磁道的扇区数
DWORDBytesPerSector;//每扇区的字节数
}DISK_GEOMETRY;
IOCTL_STORAGE_GET_MEDIA_TYPES_EX输出一个GET_MEDIA_TYPES结构:
typedefstruct_GET_MEDIA_TYPES
{
DWORDDeviceType;//设备类型
DWORDMediaInfoCount;//介质信息条数
DEVICE_MEDIA_INFOMediaInfo[1];//介质信息
}GET_MEDIA_TYPES;
让我们来看一下DEVICE_MEDIA_INFO结构的定义:
typedefstruct_DEVICE_MEDIA_INFO
{
union
{
struct
{
LARGE_INTEGERCylinders;//柱面数
STORAGE_MEDIA_TYPEMediaType;//介质类型
DWORDTracksPerCylinder;//每柱面的磁道数
DWORDSectorsPerTrack;//每磁道的扇区数
DWORDBytesPerSector;//每扇区的字节数
DWORDNumberMediaSides;//介质面数
DWORDMediaCharacteristics;//介质特性
}DiskInfo;//硬盘信息
struct
{
LARGE_INTEGERCylinders;//柱面数
STORAGE_MEDIA_TYPEMediaType;//介质类型
DWORDTracksPerCylinder;//每柱面的磁道数
DWORDSectorsPerTrack;//每磁道的扇区数
DWORDBytesPerSector;//每扇区的字节数
DWORDNumberMediaSides;//介质面数
DWORDMediaCharacteristics;//介质特性
}RemovableDiskInfo;//“可移动盘”信息
struct
{
STORAGE_MEDIA_TYPEMediaType;//介质类型
DWORDMediaCharacteristics;//介质特性
DWORDCurrentBlockSize;//块的大小
}TapeInfo;//磁带信息
}DeviceSpecific;
}DEVICE_MEDIA_INFO;
其中CD-ROM属于“可移动盘”的范围。
请注意,GET_MEDIA_TYPES结构本身只定义了一条DEVICE_MEDIA_INFO,额外的DEVICE_MEDIA_INFO需要紧接此结构的另外的空间。
问题:
调用方法我了解了,请用VC举个例子来实现我所期待已久的功能吧?
回答:
好,现在就演示一下如何取软盘/硬盘/光盘的参数。
测试时,记得要有软盘/光盘插在驱动器里喔!
首先,用MFCAppWizard生成一个单文档的应用程序,取名为DiskGeometry,让它的View基于CEditView。
然后,添加以下的.h和.cpp文件。
//GetDiskGeometry.h#if!
defined(GET_DISK_GEOMETRY_H__)
#defineGET_DISK_GEOMETRY_H__
#if_MSC_VER>1000
#pragmaonce
#endif//_MSC_VER>1000
#include
BOOLGetDriveGeometry(constchar*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,故定义足够的空间
#defineMEDIA_INFO_SIZE
sizeof(GET_MEDIA_TYPES)+15*sizeof(DEVICE_MEDIA_INFO)//16个DEVICE_MEDIA_INFO
//filename--用于设备的文件名
//pdg--参数缓冲区指针
BOOLGetDriveGeometry(constchar*filename,DISK_GEOMETRY*pdg)
{
HANDLEhDevice;//设备句柄
BOOLbResult;//DeviceIoControl的返回结果
GET_MEDIA_TYPES*pmt;//内部用的输出缓冲区
DWORDdwOutBytes;//输出数据长度
//打开设备hDevice=:
:
CreateFile(filename,//文件名
GENERIC_READ,//软驱需要读盘
FILE_SHARE_READ|FILE_SHARE_WRITE,//共享方式
NULL,//默认的安全描述符
OPEN_EXISTING,//创建方式
0,//不需设置文件属性
NULL);//不需参照模板文件
if(hDevice==INVALID_HANDLE_VALUE)
{//设备无法打开...
returnFALSE;
}
//用IOCTL_DISK_GET_DRIVE_GEOMETRY取磁盘参数
//少了好多参数啊~~,不行啊~~
bResult=:
:
DeviceIoControl(hDevice,//设备句柄磁盘结构体
IOCTL_GET_DISK_DRIVE_GEOMETRY,
NULL,0,
pdg,sizeof(DISK_GEOMETRY),//输出数据缓冲区
&dwOutBytes,//输出数据长度
(LPOVERLAPPED)NULL);//用同步I/O
上面的也可以获得介质类型啊pdg->MediaType?
?
?
;不过只能获得磁盘的下面的什么类型都可以知道了。
//如果失败,再用IOCTL_STORAGE_GET_MEDIA_TYPES_EX取介质类型参数
if(!
bResult)
{
pmt=(GET_MEDIA_TYPES*)newBYTE[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));
}
deletepmt;
}
//关闭设备句柄
:
:
CloseHandle(hDevice);
return(bResult);
}
然后,在Toolbar的IDR_MAINFRAME上添加一个按钮,ID为ID_GET_DISK_GEOMETRY。
打开ClassWizard,在DiskGeometryView中
添加ID_GET_DISK_GEOMETRY的映射函数OnGetDiskGeometry。
打开DiskGeometryView.cpp,包含头文件GetDiskGeometry.h。
在OnGetDiskGeometry中,添加以下代码
constchar*szDevName[]={"\\\\.\\A:
","\\\\.\\B:
","\\\\.\\PhysicalDrive0","\\\\.\\PhysicalDrive1","\\\\.\\PhysicalDrive2","\\\\.\\PhysicalDrive3","\\\\.\\Cdrom0","\\\\.\\Cdrom1",};DISK_GEOMETRYdg;
ULONGLONGDiskSize;
BOOLbResult;
CStringstrMsg;
CStringstrTmp;
for(inti=0;i{
bResult=GetDriveGeometry(szDevName[i],&dg);
strTmp.Format("\r\n%sresult=%s\r\n",szDevName[i],bResult?
"success":
"failure");
strMsg+=strTmp;
if(!
bResult)
continue;
strTmp.Format("MediaType=%d\r\n",dg.MediaType);
strMsg+=strTmp;
strTmp.Format("Cylinders=%I64d\r\n",dg.Cylinders);
strMsg+=strTmp;
strTmp.Format("Trackspercylinder=%ld\r\n",(ULONG)dg.TracksPerCylinder);
strMsg+=strTmp;
strTmp.Format("Sectorspertrack=%ld\r\n",(ULONG)dg.SectorsPerTrack);
strMsg+=strTmp;
strTmp.Format("Bytespersector=%ld\r\n",(ULONG)dg.BytesPerSector);
strMsg+=strTmp;
DiskSize=dg.Cylinders.QuadPart*(ULONG)dg.TracksPerCylinder*
(ULONG)dg.SectorsPerTrack*(ULONG)dg.BytesPerSector;
strTmp.Format("Disksize=%I64d(Bytes)=%I64d(Mb)\r\n",DiskSize,DiskSize/(1024*1024));
strMsg+=strTmp;
}
CEdit&Edit=GetEditCtrl();
Edit.SetWindowText(strMsg);
最后,最后干什么呢?
编译,运行......
实战DeviceIoControl之三:
制作磁盘镜像文件
问题:
DOS命令DISKCOPY给我很深的印象,现在也有许多“克隆”软件,可以对磁盘进行全盘复制。
我想,要制作磁盘镜像文件,DeviceIoControl应该很有用武之地吧?
回答:
是的。
这里举一个制作软盘镜像文件,功能类似于“DISKCOPY”的例子。
本例实现其功能的核心代码如下:
//打开磁盘
HANDLEOpenDisk(LPCTSTRfilename)
{
HANDLEhDisk;
//打开设备
hDisk=:
:
CreateFile(filename,//文件名
GENERIC_READ|GENERIC_WRITE,//读写方式
FILE_SHARE_READ|FILE_SHARE_WRITE,//共享方式
NULL,//默认的安全描述符
OPEN_EXISTI