转载:http://m.blog.csdn.net/article/details?id=21602051

DeviceIoControl的其实和ReadFile和WriteFile是一样的, 不过这个功能更强, 一次交互能够输入数据, 也可以输出数据.

DeviceIoControl内部创建的IRP是IRP_MJ_DEVICE_CONTROL类型的IRP, 然后操作系统会将这个IRP转发给驱动程序的分发函数中. 就是类似Win32下面的发送自定义消息了.. 但是又有点区别, 如果发送自定义消息, 如果跨进程, 那是不可以传递指针的, 但是和驱动那就无所谓了, 可以传递一块缓冲区过去。

利用该接口函数向指定的设备驱动发送正确的控制码及数据,然后分析它的响应,就可以达到我们的目的。

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                        // 模板文件的句柄
);
参数  类型及说明
hDevice Long,设备句柄
dwIoControlCode Long,带有 FSCTL_ 前缀的常数。参考设备控制选项的部分列表
lpInBuffer Any,具体取决于dwIoControlCode参数。参考设备控制选项的部分列表
nInBufferSize Long,输入缓冲区的长度
lpOutBuffer Any,具体取决于dwIoControlCode参数。参考设备控制选项的部分列表
nOutBufferSize Long,输出缓冲区的长度
lpBytesReturned  Long,实际装载到输出缓冲区的字节数量
lpOverlapped OVERLAPPED,这个结构用于重叠操作。针对同步操作,请用ByVal As Long传递零值
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调用设备管理函数族取得,这里暂不讨论。
访问设备必须用设备句柄,而得到设备句柄必须知道设备路径,这个套路以你我之力是改变不了的。每个设备都有它所属类型的GUID,我们顺着这个GUID就能获得设备路径。

GUID是同类或同种设备的全球唯一识别码,它是一个128 bit(16字节)的整形数,真实面目为

typedef struct _GUID
{unsigned long  Data1;unsigned short Data2;unsigned short Data3;unsigned char  Data4[8];
} GUID, *PGUID;
例如,Disk类的GUID为“53f56307-b6bf-11d0-94f2-00a0c91efb8b”,在我们的程序里可以定义为

const GUID DiskClassGuid = {0x53f56307L, 0xb6bf, 0x11d0, {0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b)};
或者用一个宏来定义

DEFINE_GUID(DiskClassGuid, 0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
通过GUID找出设备路径,需要用到一组设备管理的API函数

SetupDiGetClassDevs, SetupDiEnumDeviceInterfaces, SetupDiGetInterfaceDeviceDetail, SetupDiDestroyDeviceInfoList,

以及结构SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA。

有关信息请查阅MSDN,这里就不详细介绍了。

实现GUID到设备路径的代码如下:

// SetupDiGetInterfaceDeviceDetail所需要的输出长度,定义足够大
#define INTERFACE_DETAIL_SIZE    (1024)// 根据GUID获得设备路径
// lpGuid: GUID指针
// pszDevicePath: 设备路径指针的指针
// 返回: 成功得到的设备路径个数,可能不止1个
int GetDevicePath(LPGUID lpGuid, LPTSTR* pszDevicePath)
{HDEVINFO hDevInfoSet;SP_DEVICE_INTERFACE_DATA ifdata;PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail;int nCount;BOOL bResult;// 取得一个该GUID相关的设备信息集句柄hDevInfoSet = ::SetupDiGetClassDevs(lpGuid,     // class GUID NULL,                    // 无关键字 NULL,                    // 不指定父窗口句柄 DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);    // 目前存在的设备// 失败...if (hDevInfoSet == INVALID_HANDLE_VALUE){return 0;}// 申请设备接口数据空间pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)::GlobalAlloc(LMEM_ZEROINIT, INTERFACE_DETAIL_SIZE);pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);nCount = 0;bResult = TRUE;// 设备序号=0,1,2... 逐一测试设备接口,到失败为止while (bResult){ifdata.cbSize = sizeof(ifdata);// 枚举符合该GUID的设备接口bResult = ::SetupDiEnumDeviceInterfaces(hDevInfoSet,     // 设备信息集句柄NULL,            // 不需额外的设备描述lpGuid,          // GUID(ULONG)nCount,   // 设备信息集里的设备序号&ifdata);        // 设备接口信息if (bResult){// 取得该设备接口的细节(设备路径)bResult = SetupDiGetInterfaceDeviceDetail(hDevInfoSet,    // 设备信息集句柄&ifdata,        // 设备接口信息pDetail,        // 设备接口细节(设备路径)INTERFACE_DETAIL_SIZE,    // 输出缓冲区大小NULL,           // 不需计算输出缓冲区大小(直接用设定值)NULL);          // 不需额外的设备描述if (bResult){// 复制设备路径到输出缓冲区::strcpy(pszDevicePath[nCount], pDetail->DevicePath);// 调整计数值nCount++;}}}// 释放设备接口数据空间::GlobalFree(pDetail);// 关闭设备信息集句柄::SetupDiDestroyDeviceInfoList(hDevInfoSet);return nCount;
}
调用GetDevicePath函数时要注意,pszDevicePath是个指向字符串指针的指针,例如可以这样

    int i;char* szDevicePath[MAX_DEVICE];        // 设备路径// 分配需要的空间for (i = 0; i < MAX_DEVICE; i++){szDevicePath[i] = new char[256];}// 取设备路径nDevice = ::GetDevicePath((LPGUID)&DiskClassGuid, szDevicePath);// 逐一获取设备信息for (i = 0; i < nDevice; i++){// 打开设备hDevice = ::OpenDevice(szDevicePath[i]);if (hDevice != INVALID_HANDLE_VALUE){... ...        // I/O操作::CloseHandle(hDevice);}}// 释放空间for (i = 0; i & lt; MAX_DEVICE; i++){delete []szDevicePath[i];}
本例的Project中除了要包含winioctl.h外,还要包含initguid.h,setupapi.h,以及连接setupapi.lib。

Q 得到设备路径后,就可以到下一步,用CreateFile打开设备,然后用DeviceIoControl进行读写了吧?

A 是的。尽管该设备路径与以前我们接触的那些不太一样。本是“\\.\PhysicalDrive0”,现在鸟枪换炮,变成了类似这样的一副尊容:

“\\?\ide#diskmaxtor_2f040j0__________________________vam51jj0#3146563447534558202020202020202020202020#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}”。

其实这个设备名在注册表的某处可以找到,例如在Win2000中这个名字可以位于

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Disk\Enum\0,

只不过“#”换成了“\”。分析一下这样的设备路径,你会发现很有趣的东西,它们是由接口类型、产品型号、固件版本、序列号、计算机名、GUID等信息组合而成的。当然,它是没有规范的,不能指望从这里面得到你希望知道的东西。

用CreateFile打开设备后,对于存储设备,IOCTL_DISK_GET_DRIVE_GEOMETRY,IOCTL_STORAGE_GET_MEDIA_TYPES_EX等I/O控制码照常使用。

今天我们讨论一个新的控制码:IOCTL_STORAGE_QUERY_PROPERTY,获取设备属性信息,希望得到系统中所安装的各种固定的和可移动的硬盘、优盘和CD/DVD-ROM/R/W的接口类型、序列号、产品ID等信息。

// IOCTL控制码
#define IOCTL_STORAGE_QUERY_PROPERTY   CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS)
// 存储设备的总线类型
typedef enum _STORAGE_BUS_TYPE {BusTypeUnknown = 0x00,BusTypeScsi,BusTypeAtapi,BusTypeAta,BusType1394,BusTypeSsa,BusTypeFibre,BusTypeUsb,BusTypeRAID,BusTypeMaxReserved = 0x7F
} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;// 查询存储设备属性的类型
typedef enum _STORAGE_QUERY_TYPE {PropertyStandardQuery = 0,          // 读取描述PropertyExistsQuery,                // 测试是否支持PropertyMaskQuery,                  // 读取指定的描述PropertyQueryMaxDefined             // 验证数据
} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE;// 查询存储设备还是适配器属性
typedef enum _STORAGE_PROPERTY_ID {StorageDeviceProperty = 0,          // 查询设备属性StorageAdapterProperty              // 查询适配器属性
} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID;// 查询属性输入的数据结构
typedef struct _STORAGE_PROPERTY_QUERY {STORAGE_PROPERTY_ID PropertyId;     // 设备/适配器STORAGE_QUERY_TYPE QueryType;       // 查询类型 UCHAR AdditionalParameters[1];      // 额外的数据(仅定义了象征性的1个字节)
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;// 查询属性输出的数据结构
typedef struct _STORAGE_DEVICE_DESCRIPTOR {ULONG Version;                    // 版本ULONG Size;                       // 结构大小UCHAR DeviceType;                 // 设备类型UCHAR DeviceTypeModifier;         // SCSI-2额外的设备类型BOOLEAN RemovableMedia;           // 是否可移动BOOLEAN CommandQueueing;          // 是否支持命令队列ULONG VendorIdOffset;             // 厂家设定值的偏移ULONG ProductIdOffset;            // 产品ID的偏移ULONG ProductRevisionOffset;      // 产品版本的偏移ULONG SerialNumberOffset;         // 序列号的偏移STORAGE_BUS_TYPE BusType;         // 总线类型ULONG RawPropertiesLength;        // 额外的属性数据长度UCHAR RawDeviceProperties[1];     // 额外的属性数据(仅定义了象征性的1个字节)
} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;// 取设备属性信息
// hDevice -- 设备句柄
// pDevDesc -- 输出的设备描述和属性信息缓冲区指针(包含连接在一起的两部分)
BOOL GetDriveProperty(HANDLE hDevice, PSTORAGE_DEVICE_DESCRIPTOR pDevDesc)
{STORAGE_PROPERTY_QUERY Query;    // 查询输入参数DWORD dwOutBytes;                // IOCTL输出数据长度BOOL bResult;                    // IOCTL返回值// 指定查询方式Query.PropertyId = StorageDeviceProperty;Query.QueryType = PropertyStandardQuery;// 用IOCTL_STORAGE_QUERY_PROPERTY取设备属性信息bResult = ::DeviceIoControl(hDevice, // 设备句柄IOCTL_STORAGE_QUERY_PROPERTY,    // 取设备属性信息&Query, sizeof(STORAGE_PROPERTY_QUERY),    // 输入数据缓冲区pDevDesc, pDevDesc->Size,        // 输出数据缓冲区&dwOutBytes,                     // 输出数据长度(LPOVERLAPPED)NULL);             // 用同步I/O    return bResult;
}
Q 我用这个方法从IOCTL_STORAGE_QUERY_PROPERTY返回的数据中,没有得到CDROM和USB接口的外置硬盘的序列号、产品ID等信息。但从设备路径上看,明明是有这些信息的,为什么它没有填充到STORAGE_DEVICE_DESCRIPTOR中呢?再就是为什么硬盘序列号本是“D22P7KHE            ”,为什么它填充的是“3146563447534558202020202020202020202020”这种形式呢?

A 对这两个问题我也是心存疑惑,但又不敢妄加猜测,正琢磨着向微软请教呢。

Q 在NT/2000/XP中,如何读取CMOS数据?

Q 在NT/2000/XP中,如何控制speaker发声?

Q 在NT/2000/XP中,如何直接访问物理端口?

A 看似小小问题,难倒多少好汉!

NT/2000/XP从安全性、可靠性、稳定性上考虑,应用程序和操作系统是分开的,操作系统代码运行在核心态,有权访问系统数据和硬件,能执行特权指令;应用程序运行在用户态,能够使用的接口和访问系统数据的权限都受到严格限制。当用户程序调用系统服务时,处理器捕获该调用,然后把调用的线程切换到核心态。当系统服务完成后,操作系统将线程描述表切换回用户态,调用者继续运行。

想在用户态应用程序中实现I/O读写,直接存取硬件,可以通过编写驱动程序,实现CreateFile、CloseHandle、 DeviceIOControl、ReadFile、WriteFile等功能。从Windows 2000开始,引入WDM核心态驱动程序的概念。

下面是本人写的一个非常简单的驱动程序,可实现字节型端口I/O。

#include <ntddk.h>
#include "MyPort.h"// 设备类型定义
// 0-32767被Microsoft占用,用户自定义可用32768-65535
#define FILE_DEVICE_MYPORT    0x0000f000// I/O控制码定义
// 0-2047被Microsoft占用,用户自定义可用2048-4095
#define MYPORT_IOCTL_BASE 0xf00#define IOCTL_MYPORT_READ_BYTE   CTL_CODE(FILE_DEVICE_MYPORT, MYPORT_IOCTL_BASE, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_MYPORT_WRITE_BYTE  CTL_CODE(FILE_DEVICE_MYPORT, MYPORT_IOCTL_BASE+1, METHOD_BUFFERED, FILE_ANY_ACCESS)// IOPM是65536个端口的位屏蔽矩阵,包含8192字节(8192 x 8 = 65536)
// 0 bit: 允许应用程序访问对应端口
// 1 bit: 禁止应用程序访问对应端口#define IOPM_SIZE    8192typedef UCHAR IOPM[IOPM_SIZE];IOPM *pIOPM = NULL;// 设备名(要求以UNICODE表示)
const WCHAR NameBuffer[] = L"\\Device\\MyPort";
const WCHAR DOSNameBuffer[] = L"\\DosDevices\\MyPort";// 这是两个在ntoskrnl.exe中的未见文档的服务例程
// 没有现成的已经说明它们原型的头文件,我们自己声明
void Ke386SetIoAccessMap(int, IOPM *);
void Ke386IoSetAccessProcess(PEPROCESS, int);// 函数原型预先说明
NTSTATUS MyPortDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
void MyPortUnload(IN PDRIVER_OBJECT DriverObject);// 驱动程序入口,由系统自动调用,就像WIN32应用程序的WinMain
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{PDEVICE_OBJECT deviceObject;NTSTATUS status;UNICODE_STRING uniNameString, uniDOSString;// 为IOPM分配内存pIOPM = MmAllocateNonCachedMemory(sizeof(IOPM));if (pIOPM == 0){return STATUS_INSUFFICIENT_RESOURCES;}// IOPM全部初始化为0(允许访问所有端口)RtlZeroMemory(pIOPM, sizeof(IOPM));// 将IOPM加载到当前进程Ke386IoSetAccessProcess(PsGetCurrentProcess(), 1);Ke386SetIoAccessMap(1, pIOPM);// 指定驱动名字RtlInitUnicodeString(&uniNameString, NameBuffer);RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);// 创建设备status = IoCreateDevice(DriverObject, 0,&uniNameString,FILE_DEVICE_MYPORT,0, FALSE, &deviceObject);if (!NT_SUCCESS(status)){return status;}// 创建WIN32应用程序需要的符号连接status = IoCreateSymbolicLink (&uniDOSString, &uniNameString);if (!NT_SUCCESS(status)){return status;}// 指定驱动程序有关操作的模块入口(函数指针)// 涉及以下两个模块:MyPortDispatch和MyPortUnloadDriverObject->MajorFunction[IRP_MJ_CREATE]         =DriverObject->MajorFunction[IRP_MJ_CLOSE]          =DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyPortDispatch;DriverObject->DriverUnload = MyPortUnload;return STATUS_SUCCESS;
}// IRP处理模块
NTSTATUS MyPortDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{PIO_STACK_LOCATION IrpStack;ULONG              dwInputBufferLength;ULONG              dwOutputBufferLength;ULONG              dwIoControlCode;PULONG             pvIOBuffer;NTSTATUS           ntStatus;// 填充几个默认值Irp->IoStatus.Status = STATUS_SUCCESS;    // 返回状态Irp->IoStatus.Information = 0;            // 输出长度IrpStack = IoGetCurrentIrpStackLocation(Irp);// Get the pointer to the input/output buffer and it's length// 输入输出共用的缓冲区// 因为我们在IOCTL中指定了METHOD_BUFFERED,pvIOBuffer = Irp->AssociatedIrp.SystemBuffer;switch (IrpStack->MajorFunction){case IRP_MJ_CREATE:        // 与WIN32应用程序中的CreateFile对应break;case IRP_MJ_CLOSE:        // 与WIN32应用程序中的CloseHandle对应break;case IRP_MJ_DEVICE_CONTROL:        // 与WIN32应用程序中的DeviceIoControl对应dwIoControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;switch (dwIoControlCode){// 我们约定,缓冲区共两个DWORD,第一个DWORD为端口,第二个DWORD为数据// 一般做法是专门定义一个结构,此处简单化处理了case IOCTL_MYPORT_READ_BYTE:        // 从端口读字节pvIOBuffer[1] = _inp(pvIOBuffer[0]);Irp->IoStatus.Information = 8;  // 输出长度为8break;case IOCTL_MYPORT_WRITE_BYTE:       // 写字节到端口_outp(pvIOBuffer[0], pvIOBuffer[1]);break;default:        // 不支持的IOCTLIrp->IoStatus.Status = STATUS_INVALID_PARAMETER;}}ntStatus = Irp->IoStatus.Status;IoCompleteRequest (Irp, IO_NO_INCREMENT);return ntStatus;
}// 删除驱动
void MyPortUnload(IN PDRIVER_OBJECT DriverObject)
{UNICODE_STRING uniDOSString;if(pIOPM){// 释放IOPM占用的空间MmFreeNonCachedMemory(pIOPM, sizeof(IOPM));}RtlInitUnicodeString(&uniDOSString, DOSNameBuffer);// 删除符号连接和设备IoDeleteSymbolicLink (&uniDOSString);IoDeleteDevice(DriverObject->DeviceObject);
}
下面给出实现设备驱动程序的动态加载的源码。动态加载的好处是,你不用做任何添加新硬件的操作,也不用编辑注册表,更不用重新启动计算机。

// 安装驱动并启动服务
// lpszDriverPath:  驱动程序路径
// lpszServiceName: 服务名
BOOL StartDriver(LPCTSTR lpszDriverPath, LPCTSTR lpszServiceName)
{SC_HANDLE hSCManager;        // 服务控制管理器句柄SC_HANDLE hService;          // 服务句柄DWORD dwLastError;           // 错误码BOOL bResult = FALSE;        // 返回值// 打开服务控制管理器hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);if (hSCManager){// 创建服务hService = CreateService(hSCManager,lpszServiceName,lpszServiceName,SERVICE_ALL_ACCESS,SERVICE_KERNEL_DRIVER,SERVICE_DEMAND_START,SERVICE_ERROR_NORMAL,lpszDriverPath,NULL,NULL,NULL,NULL,NULL);if (hService == NULL){if (::GetLastError() == ERROR_SERVICE_EXISTS){hService = ::OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS);}}if (hService){// 启动服务bResult = StartService(hService, 0, NULL);// 关闭服务句柄CloseServiceHandle(hService);}// 关闭服务控制管理器句柄CloseServiceHandle(hSCManager);}return bResult;
}// 停止服务并卸下驱动
// lpszServiceName: 服务名
BOOL StopDriver(LPCTSTR lpszServiceName)
{SC_HANDLE hSCManager;        // 服务控制管理器句柄SC_HANDLE hService;          // 服务句柄BOOL bResult;                // 返回值SERVICE_STATUS ServiceStatus;bResult = FALSE;// 打开服务控制管理器hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);if (hSCManager){// 打开服务hService = OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS);if (hService){// 停止服务bResult = ControlService(hService, SERVICE_CONTROL_STOP, &ServiceStatus);// 删除服务bResult = bResult && DeleteService(hService);// 关闭服务句柄CloseServiceHandle(hService);}// 关闭服务控制管理器句柄CloseServiceHandle(hSCManager);}return bResult;
}
应用程序实现端口I/O的接口如下:

// 全局的设备句柄
HANDLE hMyPort;// 打开设备
// lpszDevicePath: 设备的路径
HANDLE OpenDevice(LPCTSTR lpszDevicePath)
{HANDLE hDevice;// 打开设备hDevice = ::CreateFile(lpszDevicePath,    // 设备路径GENERIC_READ | GENERIC_WRITE,        // 读写方式FILE_SHARE_READ | FILE_SHARE_WRITE,  // 共享方式NULL,                    // 默认的安全描述符OPEN_EXISTING,           // 创建方式0,                       // 不需设置文件属性NULL);                   // 不需参照模板文件return hDevice;
}// 打开端口驱动
BOOL OpenMyPort()
{BOOL bResult;// 设备名为"MyPort",驱动程序位于Windows的"system32\drivers"目录中bResult = StartDriver("system32\\drivers\\MyPort.sys", "MyPort");// 设备路径为"\\.\MyPort"if (bResult){hMyPort = OpenDevice("\\\\.\\MyPort");}return (bResult && (hMyPort != INVALID_HANDLE_VALUE));
}// 关闭端口驱动
BOOL CloseMyPort()
{return (CloseHandle(hMyPort) && StopDriver("MyPort"));
}// 从指定端口读一个字节
// port: 端口
BYTE ReadPortByte(WORD port)
{DWORD buf[2];            // 输入输出缓冲区            DWORD dwOutBytes;        // IOCTL输出数据长度buf[0] = port;           // 第一个DWORD是端口
//  buf[1] = 0;              // 第二个DWORD是数据// 用IOCTL_MYPORT_READ_BYTE读端口::DeviceIoControl(hMyPort,   // 设备句柄IOCTL_MYPORT_READ_BYTE,  // 取设备属性信息buf, sizeof(buf),        // 输入数据缓冲区buf, sizeof(buf),        // 输出数据缓冲区&dwOutBytes,             // 输出数据长度(LPOVERLAPPED)NULL);     // 用同步I/O    return (BYTE)buf[1];
}
// 将一个字节写到指定端口
// port: 端口
// data: 字节数据
void WritePortByte(WORD port, BYTE data)
{DWORD buf[2];            // 输入输出缓冲区            DWORD dwOutBytes;        // IOCTL输出数据长度buf[0] = port;           // 第一个DWORD是端口buf[1] = data;           // 第二个DWORD是数据// 用IOCTL_MYPORT_WRITE_BYTE写端口::DeviceIoControl(hMyPort,   // 设备句柄IOCTL_MYPORT_WRITE_BYTE, // 取设备属性信息buf, sizeof(buf),        // 输入数据缓冲区buf, sizeof(buf),        // 输出数据缓冲区&dwOutBytes,             // 输出数据长度(LPOVERLAPPED)NULL);     // 用同步I/O
}
有了ReadPortByte和WritePortByte这两个函数,我们就能很容易地操纵CMOS和speaker了(关于CMOS值的含义以及定时器寄存器定义,请参考相应的硬件资料):

// 0x70是CMOS索引端口(只写)
// 0x71是CMOS数据端口
BYTE ReadCmos(BYTE index)
{BYTE data;::WritePortByte(0x70, index);data = ::ReadPortByte(0x71);return data;
}// 0x61是speaker控制端口
// 0x43是8253/8254定时器控制端口
// 0x42是8253/8254定时器通道2的端口
void Sound(DWORD freq)
{BYTE data;if ((freq >= 20) && (freq <= 20000)){freq = 1193181 / freq;data = ::ReadPortByte(0x61);if ((data & 3) == 0){::WritePortByte(0x61, data | 3);::WritePortByte(0x43, 0xb6);}::WritePortByte(0x42, (BYTE)(freq % 256));::WritePortByte(0x42, (BYTE)(freq / 256));}
}void NoSound(void)
{BYTE data;data = ::ReadPortByte(0x61);::WritePortByte(0x61, data & 0xfc);
}
// 以下读出CMOS 128个字节for (int i = 0; i < 128; i++){BYTE data = ::ReadCmos(i);... ...}// 以下用C调演奏“多-来-米”// 1 = 262 Hz::Sound(262);::Sleep(200);::NoSound();// 2 = 288 Hz::Sound(288);::Sleep(200);::NoSound();// 3 = 320 Hz::Sound(320);::Sleep(200);::NoSound();
Q 就是个简单的端口I/O,这么麻烦才能实现,搞得俺头脑稀昏,有没有简洁明了的办法啊?

A 上面的例子,之所以从编写驱动程序,到安装驱动,到启动服务,到打开设备,到访问设备,一直到读写端口,这样一路下来,是为了揭示在NT/2000/XP中硬件访问技术的本质。假如将所有过程封装起来,只提供OpenMyPort, CloseMyPort, ReadPortByte, WritePortByte甚至更高层的ReadCmos、WriteCmos、Sound、NoSound给你调用,是不是会感觉清爽许多?

实际上,我们平常做的基于一定硬件的二次开发,一般会先安装驱动程序(DRV)和用户接口的运行库(DLL),然后在此基础上开发出我们的应用程序(APP)。DRV、DLL、APP三者分别运行在核心态、核心态/用户态联络带、用户态。比如买了一块图象采集卡,要先安装核心驱动,它的“Development Tool Kit”,提供类似于PCV_Initialize, PCV_Capture等的API,就是扮演核心态和用户态联络员的角色。我们根本不需要CreateFile、CloseHandle、 DeviceIOControl、ReadFile、WriteFile等较低层次的直接调用。

Yariv Kaplan写过一个WinIO的例子,能实现对物理端口和内存的访问,提供了DRV、DLL、APP三方面的源码,有兴趣的话可以深入研究一下。

MFC: DeviceIoControl 通过API访问设备驱动程序相关推荐

  1. 实战DeviceIoControl 之中的一个:通过API訪问设备驱动程序

    Q 在NT/2000/XP中,我想用VC编写应用程序訪问硬件设备,如获取磁盘參数.读写绝对扇区数据.測试光驱实际速度等,该从哪里入手呢? A 在NT/2000/XP中,应用程序能够通过API函数Dev ...

  2. usb扫描枪驱动下载 wince_wince下USB设备驱动程序

    随着USB设备的不断增加,我们这些开发人员也就多了对USB设备进行驱动程序开发的工作.但是对于很多初学者来说,存在以下三个困难: 一是对WinCE的驱动程序结构了解得太少,没办法得心应手的专注于驱动程 ...

  3. windows 编写的硬件驱动_哪个是PXI硬件合适的设备驱动程序?VISA还是IVI?

    理想的测试系统可以认为是其组成部分的总和,包括测量和激励硬件,信号切换,电缆以及可能 的大规模互连系统,UUT电源,外部PC或嵌入式控制器,操作系统(OS)和编程环境.每个部件根据诸如UUT测试参数, ...

  4. LINUX设备驱动程序的注意事项(两)建设和执行模块

             <一>:设置測试系统 首先准备好一个内核源代码树,构造一个新内核,然后安装到自己的系统中.           <二>:HelloWorld模块 #inclu ...

  5. linux设备驱动程序中的阻塞机制

    阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经常用到等待队列. 一.阻塞与非阻塞 阻塞调用是指调用结果返回之前,当前线程会被挂起,函数只有在得到结果之后才会返回. 非阻塞指不能立刻 ...

  6. Windows驱动——利用WinDriver开发PCI设备驱动程序

    摘要 WinDriver是Jungo公司出版的一个设备驱动程序开发组件,它可以大大加速PCI设备驱动程序的开发.作者在实际的项目中采用了WinDriver来开发设备驱动程序,取得了相当好的运行效果.从 ...

  7. ibm aix_IBM AIX设备驱动程序开发

    在传统的UNIX®中,术语" 设备"指的是硬件组件,例如磁盘驱动器,磁带驱动器,打印机,键盘,伪设备(例如控制台,错误特殊文件和null特殊文件)等等. 在AIX中,这些设备称为内 ...

  8. Linux 设备驱动程序(三)

    系列文章目录 Linux 内核设计与实现 深入理解 Linux 内核(一) 深入理解 Linux 内核(二) Linux 设备驱动程序(一) Linux 设备驱动程序(二) Linux 设备驱动程序( ...

  9. Linux 设备驱动程序(二)

    系列文章目录 Linux 内核设计与实现 深入理解 Linux 内核(一) 深入理解 Linux 内核(二) Linux 设备驱动程序(一) Linux 设备驱动程序(二) Linux 设备驱动程序( ...

最新文章

  1. 深度学习必备:随机梯度下降(SGD)优化算法及可视化
  2. python爬虫教程 百度云-如何使用python编程【python爬虫教程 百度云】
  3. 【专题】多角度深入解析开放原子开源基金会
  4. HDMI高清光端机产品特点及应用场合介绍
  5. css hover变成手_web前端入门到实战:彻底掌握css动画「transition」
  6. java调用Linux mahout,Mahout算法调用展示平台2.1
  7. 基于 HTML5 WebGL 的民航客机飞行监控系统
  8. c语言 ++ --运算符_了解C ++中的删除运算符
  9. visio保存后公式变形_固体力学中的变形分析
  10. 基于高频18000-3M3技术的RFID智能书架方案
  11. 全国OA系统下载地址(全)
  12. php呼叫平台,php – Twilio呼叫转发
  13. linux sort命令优化提高速度
  14. Android 判断是否安装应用宝,并跳到应用中去
  15. Extreme DAX中文第1章 商业智能中的DAX
  16. Xms Xmx PermSize MaxPermSize的含义
  17. ArcGIS空间数据查询与处理
  18. 3c计算机通讯消费类电子产品,什么是3c产品?3c产品具体包括哪些
  19. jquery $.fn $.fx是什么意思
  20. (转)Android Jetpack Compose 最全上手指南

热门文章

  1. 小科普:什么是屏幕分辨率
  2. ⊱静心抄经,是对抗这个浮躁社会的最好武器
  3. 关机状态下启动微型计算机叫做,湖南省计算机等级考试题库
  4. 刷脸支付商用之火真正出现燎原的苗头
  5. python 英语分词_自然语言处理 | NLTK英文分词尝试
  6. 我的雷电游戏(重力感应控制)
  7. python能不能互动执行_细思恐极,插上U盘就开始执行Python代码
  8. 上海天文台实习的一个项目-根据卫星数据绘制南极星空分布图
  9. Java项目:医院管理系统(java+Springboot+ssm+mysql+maven)
  10. [JZOJ6355] 【NOIP2019模拟】普