Windows XP驱动程序编写方法 ——Step by Step

Slides:



Advertisements
Similar presentations
LSF系统介绍 张焕杰 中国科学技术大学网络信息中心
Advertisements

Windows98/2000驱动程序编写方法 (上) 杨全胜.
在PHP和MYSQL中实现完美的中文显示
J2EE与中间件技术 ——Lab.
Kvm异步缺页中断 浙江大学计算机体系结构实验室 徐浩.
LSF系统介绍 张焕杰 中国科学技术大学网络信息中心
Android开发入门 -----Android 开发“Ready go!” 主讲:李晓蕾
Windows 8 more simple more powerful more free.
Windows98/2000驱动程序编写方法 (下) 杨全胜.
一个端口访问器的编写 — Windows XP驱动程序举例 VS.NET+WIN XP DDK+DriverStudio3.2开发环境版
第二讲 搭建Java Web开发环境 主讲人:孙娜
学习前的准备工作 讲师:burning.
第八章 菜单设计 §8.1 Visual FoxPro 系统菜单 §8.2 为自己的程序添加菜单 §8.3 创建快捷菜单.
把COM口设置到没有使用的 COM1 – COM4
大学计算机基础 典型案例之一 构建FPT服务器.
SVN服务器的搭建(Windows) 柳峰
走进编程 程序的顺序结构(二).
辅导课程六.
第11章:一些著名开源软件介绍 第12章:服务安装和配置 本章教学目标: 了解当前一些应用最广泛的开源软件项目 搭建一个网站服务器
DM81X 视频采集处理 ——简单采集显示例程讲解 广州创龙电子科技有限公司
SPI驱动 广州创龙电子科技有限公司 Guangzhou Tronlong Electronic Technology Co., Ltd.
PostgreSQL 8.3 安装要点 四川大学计算机学院 段 磊
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
Windows网络操作系统管理 ——Windows Server 2008 R2.
第十章 IDL访问数据库 10.1 数据库与数据库访问 1、数据库 数据库中数据的组织由低到高分为四级:字段、记录、表、数据库四种。
逆向工程-汇编语言
China’s Software Industry August 2006 Instructor: Hengming Zou, Ph.D.
Windows 7 的系统设置.
VSS使用简介 王树升
用event class 从input的root文件中,由DmpDataBuffer::ReadObject读取数据的问题
宁波市高校慕课联盟课程 与 进行交互 Linux 系统管理.
Gzip编译及调试 曹益华
宁波市高校慕课联盟课程 与 进行交互 Linux 系统管理.
三:基于Eclipse的集成开发环境搭建与使用
程序设计工具实习 Software Program Tool
录制回放工具使用说明 鲁晓宇
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
内容摘要 ■ 课程概述 ■ 教学安排 ■ 什么是操作系统? ■ 为什么学习操作系统? ■ 如何学习操作系统? ■ 操作系统实例
C语言程序设计 主讲教师:陆幼利.
简单介绍 用C++实现简单的模板数据结构 ArrayList(数组, 类似std::vector)
本节内容 随机读取 视频提供:昆山爱达人信息技术有限公司.
第四章 团队音乐会序幕: 团队协作平台的快速创建
VisComposer 2019/4/17.
Platform Builder使用介绍 WINCE系统应用开发流程说明 ACTION RDC 杨 涛 2005.Dec.3th
VB与Access数据库的连接.
实验七 安全FTP服务器实验 2019/4/28.
计算机网络与网页制作 Chapter 07:Dreamweaver CS5入门
<编程达人入门课程> 本节内容 内存的使用 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群: ,
本节内容 Win32 API中的宽字符 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
JSP实用教程 清华大学出版社 第2章 JSP运行环境和开发环境 教学目标 教学重点 教学过程 2019年5月7日.
iSIGHT 基本培训 使用 Excel的栅栏问题
《手把手教你学STM32-STemWin》 主讲人 :正点原子团队 硬件平台:正点原子STM32开发板 版权所有:广州市星翼电子科技有限公司
LOGIX500软件入门 西安华光信息技术有限公司 2008年7月11日.
Windows内核安全编程实践之路
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
魏新宇 MATLAB/Simulink 与控制系统仿真 魏新宇
本节内容 C语言的汇编表示 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
Python 环境搭建 基于Anaconda和VSCode.
C++语言程序设计 C++语言程序设计 第一章 C++语言概述 第十一组 C++语言程序设计.
本节内容 动态链接库 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
本节内容 如何调试驱动程序? 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
本节内容 进程 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
使用ADO访问数据库 李宝智 BonizLee 课程 10564A
FVX1100介绍 法视特(上海)图像科技有限公司 施 俊.
创建、启动和关闭Activity 本讲大纲: 1、创建Activity 2、配置Activity 3、启动和关闭Activity
使用Fragment 本讲大纲: 1、创建Fragment 2、在Activity中添加Fragment
DEV310 Microsoft Visual Studio 2005托管代码调试
《手把手教你学STM32-STemWin》 主讲人 :正点原子团队 硬件平台:正点原子STM32开发板 版权所有:广州市星翼电子科技有限公司
Presentation transcript:

Windows XP驱动程序编写方法 ——Step by Step VS.NET+WIN XP DDK+DriverStudio3.2开发环境版 东南大学计算机科学与工程学院 杨全胜

1. 驱动程序的开发环境 本电子讲义可以作为几年前本人所写的《驱动开发》上、下电子讲义的后续篇,主要是将开发平台从Windows98/2000,DriverStudio 2.7升级到以下环境: Windows XP SP2 Visual Studio .NET(VC++.NET 2002) 简体中文版* Windows XP DDK** Driver Studio 3.2 以上四项中,前3项为Microsoft公司产品,可以只用2,3来开发驱动程序。为了方便起见,也可以使用第三方的开发工具Driver Studio ,它将DDK的内容封装成类,而且提供一个快速方便地生成驱动框架的工具。3.2版本可能是Compuware公司推出的最后一个版本。

*本电子讲义假设大家已经会VC++编程及熟悉VS IDE的使用。 **通常,开发不同操作系统下的驱动程序需要不同的DDK做支持: Windows 2000 DDK适合开发Windows 2000/98/Me的WDM驱动程序,Windows 2000下NT4型驱动程序。 Windows XP DDK适合开发IA64下的驱动程序或Windows XP/2000/Me的WDM驱动程序,Windows XP下NT4型驱动程序。 Windows 2003 DDK适合开发AMD64/IA64下的驱动程序或Windows 2003/XP/2000/Me的WDM驱动程序,Windows 2003/XP/2000下NT4型驱动程序。

2. 驱动程序开发工具包DriverStudio DriverWorks     DriverWorks提供针对Windows NT 4和Win32驱动模型(WDM)的设备驱动程序开发的完全支持。DriverWorks中包含一个非常完善的源代码生成工具(DriverWizard) 以及相应的类库和驱动程序样本,它提供了在C++下进行设备驱动程序开发的支持。它可以集成到msvc6和msvc.net中,还需要最新的Windows DDK的支持。 VToolsD     VToolsD 是一个用来开发针对Win9X (Windows 95 和 Windows 98)操作系统下设备驱动程序(VxD)的工具。VToolsD 中包括生成驱动程序源代码的工具,run-time 和 interface 库,以及一些可以用来作为各种类型的设备驱动程序基础的驱动程序样本。

DriverNetworks     DriverNetworks 是针对Windows网络驱动开发人员的一个模块。它的核心部分,是一个针对NDIS drivers 和 TDI clients (DriverSockets)的 C++ 的类库。DriverNetworks 中也有Quick Miniport Wizard 用来直接开始一个NDIS Miniport ,Intermediate 或协议驱动程序工程。它可以让你在采用DriverNetworks C++ 类库编写NDIS驱动程序的时候,快速的生成编译、安装和调试所需要的所有文件 。它可以集成到msvc6和msvc.net中,还需要最新的Windows DDK的支持。

SoftICE系列调试器     SoftICE 系列调试器包含了可以调试各种代码的多种工具。它可以调试诸如BIOS代码、中断例程以及系统I/O。这些工具与强大的硬件调试板一起支持符号级调试,可以显示源码、全局或局部数据。其中: SoftICE 是单机调试器,调试本机代码。 Visual SoftICE是双机调试器,支持64位和32位平台上的微软操作系统。

DriverMonitor     DriverMonitor不仅可以显示WDM和VxD在操作系统核心层次输出的调试语句,还可以装载和卸载VxD驱动和NT4系统的驱动程序。 EZDriverInstaller     这是一个无需经过设备管理器或“添加新硬件”功能就能为Windows 2000/XP动态加载和卸载WDM驱动程序的小实用程序。 SetDDKGo     用来设置设备驱动程序创建的环境。当我们用Visual Studio(VC++)编译驱动程序源程序的时候,需要用SetDDKGo来设置环境变量,之后SetDDKGo会自动启动Visual Studio(VC++)编译环境。

DriverWorkbench     这是DriverStudio以及用户工具的集成环境和宿主。DS的大多数工具全部被集成到这个开发环境中。 BoundsChecker Driver Edition    它提供了参数验证和系统测试来检测和跟踪不同的设备驱动程序与其他操作系统模块之间的交互。配置 TrueTime Driver Edition     这是一个能让Windows NT/2000/XP设备驱动程序的编写者确定驱动程序性能瓶颈的性能分析工具。对于编写设备驱动程序或核心代码的程序员,这很有用。 TrueCoverage Driver Edition     它能帮助程序员检测其代码的哪部分被测试过,哪部分还需要测试。可帮助程序员提高程序的稳定性。

2.2 DriverStudio 3.2的安装 安装需要的软硬件环境 Intel x86兼容系统或X64系统(包含IA64和AMD64以及Itanium) Windows XP 内存: 最少256MB,推荐使用512 MB 硬盘:完全安装需要大约182 MB 针对 SoftICE的远程调试: NE2000-兼容网卡或 3Com 网卡 针对 DriverWorks: Microsoft XP DDK, Visual C++.NET

安装步骤: 在安装DriverWorks之前,首先要保证你的计算机上已经安装了Microsoft Visual C++.NET以及Windows XP DDK。 所有这些包括DriverStudio的安装都必须以系统管理员身份启动系统。并且要按照下面的顺序安装。

第一步: 安装Visual Studio C++.NET 第二步: 安装Windows XP DDK(Driver Development Kits)。 注意: 1)在安装DDK的时候请选择完全安装。 2)安装中,不需要安装64BIT IA64Binaries 3)安装好后,对于 XP DDK不需要手动配置环境变量,只需在开始菜单中点击Checked Build Envirment 则DDK会自动调用setenv配置环境变量,并监测相应的SDK以及Visual Studio.NET IDE 第三步: 安装DriverStudio 3.2(按照安装提示安装)。

Driver Studio 3.2支持单机调试或双机调试两种模式。在安装的时候也有Host和Target两种模式。

DriverStudio安装后的设置: 1)使用DDK Build Setting工具定义BASEDIR环境变量并启动MSVC.NET,

2)打开下列地址上的建立库文件工程VdwLibs2002.sln 如果是VS.NET2003, 则打开 VdwLibs2003.sln

3)选择“生成->批生成”,打开下面的窗口,从中选则需要编译的配置。 Checked是调试版本,Free是发布版本

4)点击“重新生成”编译所选择的库文件。 注意:库文件只需在安装完成后第一次使用前编译一次即可。以后要使用DriverWorks,只需通过SetDDKGo进入MSVC.NET即可。 或者直接从MSVC.NET中启动DriverWorks。

3.Driver Works的使用 1)生成简单框架(VS.NET中启动DriverWizard)

工程文件名 工程文件目录

选择驱动类型 选择框架类型

创建功能驱动程序 创建过滤器驱动程序

本例不驱动硬件 选择相应总线

选择需要处理的消息句柄 要测试的是IRP_MJ_DEVICE_CONTROL, IRP_MJ_READ和IRP_MJWRITE,所以要选择。 但要注意,IRP_MJCREATE一定要选择,否则APP程序无法用CreateFile函数打开设备对象。

添加和应用程序之间通信的控制代码

设备类 驱动类 队列管理类

驱动类文件 设备类文件 队列管理类 驱动安装指导文件 测试用的控制台程序文件

此时已经具备了一个驱动程序以及做测试用的应用程序的基本框架,我们可以在VC集成环境下修改有关程序,增加相关的具体操作代码,然后就可以编译和调试了。

该驱动程序框架包含了几个最基本的类,这些类是: class SampleDriver : public KDriver // 驱动程序类,用于初始化驱动程序 { SAFE_DESTRUCTORS public: // 以下成员函数注意和WDM中有关例程联系起来看 virtual NTSTATUS DriverEntry(PUNICODE_STRING RegistryPath); virtual NTSTATUS AddDevice(PDEVICE_OBJECT Pdo); virtual VOID Unload(VOID); void LoadRegistryParameters(PUNICODE_STRING RegistryPath); protected: // 成员数据 int m_Unit; };

class SampleDevice : public KPnpDevice // 是设备类KDvice的派生类,用于在WDM环境下支持即插即用设备 { // Constructors public: SAFE_DESTRUCTORS; SampleDevice(PDEVICE_OBJECT Pdo, ULONG Unit); ~SampleDevice(); VOID Invalidate(void); // Member Functions 注意和PNP的次功能代码联系起来看 DEVMEMBER_DISPATCHERS virtual NTSTATUS OnStartDevice(KIrp I); virtual NTSTATUS OnStopDevice(KIrp I); virtual NTSTATUS OnRemoveDevice(KIrp I); virtual NTSTATUS OnDevicePowerUp(KIrp I); virtual NTSTATUS OnDeviceSleep(KIrp I); virtual NTSTATUS DefaultPnp(KIrp I); virtual NTSTATUS DefaultPower(KIrp I); void LoadRegistryParameters(); // 取注册表信息

void SerialRead(KIrp I); void SerialWrite(KIrp I); NTSTATUS SAMPLE_IOCTL_Read_Handler(KIrp I); NTSTATUS SAMPLE_IOCTL_Write_Handler(KIrp I); NTSTATUS SAMPLE_IOCTL_ReadWrite_Handler(KIrp I); protected:// Member Data KPnpLowerDevice m_Lower; sampleQueue ReadQueue; // Driver managed IRP queue sampleQueue WriteQueue; // Driver managed IRP queue #ifdef __COMMENT_ONLY virtual NTSTATUS Create(KIrp I); // COMMENT_ONLY virtual NTSTATUS Close(KIrp I); // COMMENT_ONLY virtual NTSTATUS Read(KIrp I); // COMMENT_ONLY virtual NTSTATUS Write(KIrp I); // COMMENT_ONLY virtual NTSTATUS DeviceControl(KIrp I);// COMMENT_ONLY virtual NTSTATUS SystemControl(KIrp I);// COMMENT_ONLY #endif //__COMMENT_ONLY };

由于一个可能是DriverStudio 3.2中的BUG,所以及时生成的一个空工程项目也无法编译通过,需要对生成的工程文件做以下手工修改: 把sample项目中的sources文件中的:  TARGETLIBS=$ (DDK_LIB_PATH)\ntstrsafe.lib $ (DDK_LIB_PATH)\csq.lib   这一行去掉就可以编译通过了

下面我们讲解编译、执行和调试这个驱动程序。 先编译驱动程序工程 生成目标文件 在VS2002的集成环境中

确认生成的是驱动程序

再编译测试应用程序工程 生成目标文件 在VS2002的集成环境中

确认生成的是测试用应用程序

驱动程序监视,可实时看到驱动程序发出的调试输出语句 下面使用DriverStudio带的工具加载驱动程序和查看调试信息。 驱动程序监视,可实时看到驱动程序发出的调试输出语句 驱动程序装载器,可动态调用驱动程序

驱动程序监视器界面

为了防止其他驱动程序的干扰,在Filter Message对话框中设置消息过滤规则,只让有sample的消息通过。

驱动程序装载器界面

驱动程序已经加载并且启动

YANGQS

DriverStudio 3.2给出的驱动测试软件是一个Win32的窗口程序,而不是先前版本的控制台程序。为了在调试输出的时候有所区别我们将测试程序的调试输出语句的句头由原来的sample:改成sampleAPP: (在sampleAPP.cpp文件中) VOID sampleOutputText(LPCTSTR Format, ...) { TCHAR str[MAX_STRING_LENGTH]; va_list vaList; va_start(vaList, Format); _vstprintf(str, Format, vaList); OutputDebugString(_T("sampleAPP: ")); OutputDebugString(str); OutputDebugString(_T("\n")); va_end(vaList); return; }

运行编译好的sampleAPP.exe

如果在执行sampleAPP. exe之前,驱动程序sample 如果在执行sampleAPP.exe之前,驱动程序sample.sys还没有加载到内存中,则在DriverMonitor程序中就可以看到以下信息:

APP中打开与驱动程序联系

结束后一定要卸载驱动程序

驱动程序已经卸载

下面我们来修改有关代码,以便增加驱动程序和应用程序之间相互通信的内容。需要增加的内容包括: 2)完成应用程序和驱动程序之间的信息交换 下面我们来修改有关代码,以便增加驱动程序和应用程序之间相互通信的内容。需要增加的内容包括: 使用Read和Write方式分别从驱动程序读入字符和 向驱动程序写字符。 使用IO控制代码方式分别从驱动程序读入字符和 向驱动程序写字符。 使用IO控制代码方式向驱动程序写字符串再从驱动程序中读出该字符串,并返回反馈串信息。 注意:程序中暗红色显示的部分是我们添加或修改过的语句,其他是DriverWorks自动生成的。蓝色显示的部分是要删除的语句。省略号的部分是不变的。语句中T.Trace(TraceInfo, __FUNCTION__“xxxx”)这样的语句是向调试软件输出信息,该信息可在DriverMonitor或其他调试监视器中看到。

a.1 使用Read方式读 SampleDevice.cpp void SampleDevice::SerialRead(KIrp I) { T.Trace(TraceInfo, __FUNCTION__"++. IRP %p\n", I); NTSTATUS status = STATUS_SUCCESS; PUCHAR pBuffer = (PUCHAR) I.BufferedReadDest();//取得返回数据BUFF的指针 ULONG readSize = I.ReadSize( ); //获得应用程序希望读驱动程序信息的字节数。 ULONG bytesRead = 0; char buff[512]; int n =512, j = (n % 26); for (int i=0; i<n; i++, j=(j + 1)%26) { buff[i] = 'a' + j; } buff[readSize]='\0'; //指定串尾 strcpy((char *)pBuffer,buff); // 把给应用程序的数据拷贝给返回BUFF T.Trace(TraceInfo,__FUNCTION__ " The string you will read is %s\n", buff ); // 输出调试信息 bytesRead = strlen(buff); // Count of bytes read I.Information() = bytesRead; // 返回给应用程序的信息的字节个数 I.Status() = status; m_DriverManagedQueue.PnpNextIrp(I); }

控件IDC_OP_TYPE_COMBO及其选择项,我们这次选ReadFile

sampleIorw.cpp中有关读数据的代码: ULONG sampleExecuteIo(HWND hDlg) { PSAMPLE_LIST_ITEM ioItem; …… // 获得需要读的字节数 GetDlgItemText(hDlg, IDC_OUT_SIZE_EDIT, str, MAX_STRING_LENGTH); ioItem->OutSize = _ttol(str); // 设置控件IDC_OP_TYPE_COMBO的句柄 hWnd = GetDlgItem(hDlg, IDC_OP_TYPE_COMBO); // 获得当前被选中项目的索引 itemIndex = (DWORD)SendMessage(hWnd, CB_GETCURSEL, 0, 0); // 获得被选中的项目的字符串 SendMessage(hWnd, CB_GETLBTEXT, (WPARAM)itemIndex, (LPARAM)str); if ((!_tcscmp(str, _T(“ReadFile”))) && (ioItem->OutSize > 0)) // 如是读端口 // 设置读的缓冲区 ioItem->OutBuffer = (PCHAR)malloc(ioItem->OutSize);

// 从驱动程序读数据 if ((!ReadFile( g_hDevice, // 设备句柄 ioItem->OutBuffer, // 输入缓冲地址 ioItem->OutSize, // 缓冲大小(字节数) NULL, // 实际读的数据字节数 &ioItem->IoOverlapped)) && //覆盖结构指针,表示异步读 (GetLastError() != ERROR_IO_PENDING)) { //如果读出错,给出出错的调试信息 error = GetLastError(); sampleOutputText(_T("ReadFile failed with error (%d)"), error); break; } // 设置回调函数,因为是异步读,所以读出数据后回调函数会启动 ioItem->Callback = sampleReadCompleteCallback; …… } // if ((!_tcscmp(str, _T(“ReadFile”) ……

VOID sampleReadCompleteCallback(PVOID Context) { // 读驱动程序的回调函数 PSAMPLE_LIST_ITEM ioItem = (PSAMPLE_LIST_ITEM)Context; // 因VS.net隐含采用Unicode编码,每个字符16位,下面做8位到16位字符转换 char wstr[1025]; for(ULONG i=0;i<ioItem->OutSize;i++) { wstr[i*2]=ioItem->OutBuffer[i]; wstr[i*2+1]=0x0; } wstr[i*2]=wstr[i*2+1]='\0'; sampleOutputText( _T("Executed ReadFile: buffer size (%d), return length (%d) error (%d) The string I read is %s"), ioItem->OutSize, ioItem->ReturnLength, ioItem->Error, wstr); // 输出读到的字符串 sampleOutputBuffer(ioItem->OutBuffer, ioItem->ReturnLength); // 释放缓冲空间 free(ioItem->OutBuffer); // 关闭重叠事件句柄 CloseHandle(ioItem->IoOverlapped.hEvent); // 释放 ioItem 空间 free(ioItem); return; }

a.2 使用Write方式写 SampleDevice.cpp void SampleDevice::SerialWrite(KIrp I) { T.Trace(TraceInfo, __FUNCTION__"++. IRP %p\n", I); NTSTATUS status = STATUS_SUCCESS; PUCHAR pBuffer = (PUCHAR)I.BufferedWriteSource();//取得存放应用程序写给驱动程序的数据的BUFF的指针 ULONG writeSize = I.WriteSize( );// 获得应用程序写给驱动程序的信息的字节数。 ULONG bytesSent = 0; bytesSent = writeSize; char buff[512]; strcpy(buff, (char *)pBuffer); // 应用程序写给驱动程序的数据在I.BufferedWriteSource()返回的指针中。 buff[bytesSent] = '\0'; T.Trace(TraceInfo,__FUNCTION__ "Write to driver is %s\n", buff ); I.Information() = bytesSent; // 返回用户实际写的字节数 I.Status() = status; m_DriverManagedQueue.PnpNextIrp(I); }

控件IDC_OP_TYPE_COMBO及其选择项,我们这次选WriteFile

sampleIorw.cpp中有关写数据的代码: ULONG sampleExecuteIo(HWND hDlg) { PSAMPLE_LIST_ITEM ioItem; …… // 获得需要写的字节数 //GetDlgItemText(hDlg, IDC_IN_SIZE_EDIT, str, MAX_STRING_LENGTH); // ioItem->InSize = _ttol(str); // 获得要写的字符串和要写的字节数 GetDlgItemText(hDlg, IDC_IN_DATA_EDIT, str, MAX_STRING_LENGTH); // (VOID)_stscanf(str, _T("%x"), &inPattern); char instr[MAX_STRING_LENGTH]; //TCHAR转换成char* for(int i=0;i<MAX_STRING_LENGTH;i++) instr[i]=(char)str[i]; ioItem->InSize=strlen(instr); // 设置控件IDC_OP_TYPE_COMBO的句柄 hWnd = GetDlgItem(hDlg, IDC_OP_TYPE_COMBO); // 获得当前被选中项目的索引 itemIndex = (DWORD)SendMessage(hWnd, CB_GETCURSEL, 0, 0); // 获得被选中的项目的字符串 SendMessage(hWnd, CB_GETLBTEXT, (WPARAM)itemIndex, (LPARAM)str);

// Write data to driver if ((!_tcscmp(str, _T(“ReadFile”))) && (ioItem->OutSize > 0)) // 如是读端口 { // 设置写的缓冲区 ioItem->InBuffer = (PCHAR)malloc(ioItem->InSize); …… strcpy((char *)ioItem->InBuffer,instr); // for (ii = 0; ii < ioItem->InSize; ii += sizeof(DWORD)) // { // CopyMemory(ioItem->InBuffer + ii, &inPattern, // min(sizeof(DWORD), ioItem->InSize - ii)); // } if ((!WriteFile(g_hDevice,ioItem->InBuffer,ioItem->InSize,NULL, &ioItem->IoOverlapped)) &&(GetLastError() != ERROR_IO_PENDING)) {//如果读出错,给出出错的调试信息 error = GetLastError(); sampleOutputText(_T("WriteFile failed with error (%d)"), error); break; } // 设置回调函数,因为是异步读,所以读出数据后回调函数会启动 ioItem->Callback = sampleWriteCompleteCallback; } // if ((!_tcscmp(str, _T(“WriteFile”) ……

b.1 使用IO控制代码方式读 SampleDevice.cpp NTSTATUS sampleDevice::SAMPLE_IOCTL_Read_Handler(KIrp I) { T.Trace(TraceInfo, __FUNCTION__"++. IRP %p\n", I); NTSTATUS status = STATUS_SUCCESS; ULONG inputSize = I.IoctlInputBufferSize(); ULONG outputSize = I.IoctlOutputBufferSize(); char buff1[512]; ULONG fwLength=0; strcpy(buff1,“Welcome to driver!”); //这是应用程序将要读到的字符串 fwLength = strlen(buff1)+1; if (outputSize >= fwLength) {// 如果读入缓冲够长 strcpy((PCHAR)I.IoctlBuffer(),buff1); // 将信息拷给应用程序读入缓冲 I.Information() = fwLength; // 返回信息长度 } else { I.Information() = 0; // 否则信息长度为0 T.Trace(TraceInfo, __FUNCTION__"buff size too small\n");

// Buffered ioctl - using the same buffer so read the buffer before writing the buffer /* PVOID inputBuffer = I.IoctlBuffer(); PVOID outputBuffer = I.IoctlBuffer(); if (FALSE) { status = STATUS_INVALID_PARAMETER; I.Information() = 0; } else */ T.Trace(NT_SUCCESS(status)?TraceInfo:TraceWarning, __FUNCTION__"--. IRP %p, STATUS %x\n", I, status); return status;

sampleIorw.cpp中有关写数据的代码: ULONG sampleExecuteIo(HWND hDlg) { PSAMPLE_LIST_ITEM ioItem; …… if ((!_tcscmp(str, _T("SAMPLE_IOCTL_Read"))) && ((ioItem->OutSize > 0) || (ioItem->InSize > 0))) // Setup the out buffer if specified if (ioItem->OutSize > 0) ioItem->OutBuffer = (PCHAR)malloc(ioItem->OutSize); }

if ((!DeviceIoControl( g_hDevice, // 设备句柄 SAMPLE_IOCTL_Read, // IO控制命令 ioItem->InBuffer, //写缓冲 ioItem->InSize, //写缓冲大小 ioItem->OutBuffer, //读缓冲 ioItem->OutSize, //读缓冲大小 NULL, //实际读的字节数 &ioItem->IoOverlapped)) && //覆盖结构指针 (GetLastError() != ERROR_IO_PENDING)) { error = GetLastError(); sampleOutputText(_T("SAMPLE_IOCTL_Read failed with error (%d)"), error); break; } // Setup the entry ioItem->Callback = sampleSAMPLE_IOCTL_ReadCompleteCallback; …… } //if ((!_tcscmp(str, _T("SAMPLE_IOCTL_Read")……

VOID sampleSAMPLE_IOCTL_ReadCompleteCallback(PVOID Context) { PSAMPLE_LIST_ITEM ioItem = (PSAMPLE_LIST_ITEM)Context; char wstr[1025]; for(ULONG i=0;i<ioItem->OutSize;i++) wstr[i*2]=ioItem->OutBuffer[i]; wstr[i*2+1]=0x0; } wstr[i*2]=wstr[i*2+1]='\0'; sampleOutputText( _T(“Executed SAMPLE_IOCTL_Read request: in buffer size (%d), out buffer size(%d), \nreturn length (%d) error (%d) The string I readed is: %s"), ioItem->InSize, ioItem->OutSize, ioItem->ReturnLength, ioItem->Error, wstr); ……

b.2 使用IO控制代码方式写 SampleDevice.cpp NTSTATUS sampleDevice::SAMPLE_IOCTL_Write_Handler(KIrp I) { T.Trace(TraceInfo, __FUNCTION__"++. IRP %p\n", I); NTSTATUS status = STATUS_SUCCESS; ULONG inputSize = I.IoctlInputBufferSize(); ULONG outputSize = I.IoctlOutputBufferSize(); char buff[512]; strcpy(buff, (char *)I.IoctlBuffer()); // 应用程序写给驱动程序的数据在 I.BufferedWriteSource()返回的指针中。 buff[inputSize] = '\0'; T.Trace(TraceInfo,__FUNCTION__" Write to driver is %s\n", buff ); I.Information() = 0;

// Buffered ioctl - using the same buffer so read the buffer before writing the buffer // PVOID inputBuffer = I.IoctlBuffer(); // PVOID outputBuffer = I.IoctlBuffer(); /*if (FALSE) { status = STATUS_INVALID_PARAMETER; I.Information() = 0; } else T.Trace(NT_SUCCESS(status)?TraceInfo:TraceWarning, __FUNCTION__"--. IRP %p, STATUS %x\n", I, status); return status;

sampleIorw.cpp中有关写数据的代码: ULONG sampleExecuteIo(HWND hDlg) //本页的修改实际上前面Write时已经改好 { PSAMPLE_LIST_ITEM ioItem; …… // 获得需要写的字节数 //GetDlgItemText(hDlg, IDC_IN_SIZE_EDIT, str, MAX_STRING_LENGTH); // ioItem->InSize = _ttol(str); // 获得要写的字符串和要写的字节数 GetDlgItemText(hDlg, IDC_IN_DATA_EDIT, str, MAX_STRING_LENGTH); // (VOID)_stscanf(str, _T("%x"), &inPattern); char instr[MAX_STRING_LENGTH]; //TCHAR转换成char* for(int i=0;i<MAX_STRING_LENGTH;i++) instr[i]=(char)str[i]; ioItem->InSize=strlen(instr); // 设置控件IDC_OP_TYPE_COMBO的句柄 hWnd = GetDlgItem(hDlg, IDC_OP_TYPE_COMBO); // 获得当前被选中项目的索引 itemIndex = (DWORD)SendMessage(hWnd, CB_GETCURSEL, 0, 0); // 获得被选中的项目的字符串 SendMessage(hWnd, CB_GETLBTEXT, (WPARAM)itemIndex, (LPARAM)str);

if ((!_tcscmp(str, _T(“SAMPLEIOCTL_Write”))) && ((ioItem->OutSize > 0) || (ioItem->InSize > 0)))// 如是写端口,本页的部分需要修改 { // 设置写的缓冲区 ioItem->InBuffer = (PCHAR)malloc(ioItem->InSize); …… strcpy((char *)ioItem->InBuffer,instr); // for (ii = 0; ii < ioItem->InSize; ii += sizeof(DWORD)) // { // CopyMemory(ioItem->InBuffer + ii, &inPattern, // min(sizeof(DWORD), ioItem->InSize - ii)); // } if ((!DeviceIoControl(g_hDevice, SAMPLE_IOCTL_Write, ioItem->InBuffer, ioItem->InSize, ioItem->OutBuffer, ioItem->OutSize, NULL, &ioItem->IoOverlapped)) && (GetLastError() != ERROR_IO_PENDING)) { …… } // 设置回调函数,因为是异步读,所以读出数据后回调函数会启动 ioItem->Callback = sampleSAMPLEIOCTL_WriteCompleteCallback; } //if ((!_tcscmp(str, _T("SAMPLEIOCTL_Write"))) ……

c. 使用IO控制代码方式写并且读 SampleDevice.cpp NTSTATUS sampleDevice::IOCTL_ReadWrite_Handler(KIrp I) { Trace(TraceInfo, __FUNCTION__"++. IRP %p\n", I); NTSTATUS status = STATUS_SUCCESS; ULONG inputSize = I.IoctlInputBufferSize(); ULONG outputSize = I.IoctlOutputBufferSize(); // Buffered ioctl - using the same buffer so read the buffer before writing the buffer // PVOID inputBuffer = I.IoctlBuffer(); // PVOID outputBuffer = I.IoctlBuffer(); char buff[512]; strcpy(buff, (char *)I.IoctlBuffer()); // 取应用程序写给驱动程序的数据 buff[inputSize] = '\0'; T.Trace(TraceInfo,__FUNCTION__" Application write to driver is %s\n", buff ); char buff1[512]; ULONG fwLength=0; strcpy(buff1,"This is feedback from driver! Feedback string is "); strcat(buff1,buff); strcat(buff1,"\n"); fwLength = strlen(buff1)+1;

if (outputSize >= fwLength) { // 如果读入缓冲够长 strcpy((PCHAR)I.IoctlBuffer(),buff1); // 将信息拷给应用程序读入缓冲 I.Information() = fwLength; // 返回信息长度 } else { I.Information() = 0; // 否则信息长度为0 T.Trace(TraceInfo, __FUNCTION__"buff size too small\n"); /* if (FALSE) { status = STATUS_INVALID_PARAMETER; I.Information() = 0; else } */ T.Trace(NT_SUCCESS(status)?TraceInfo:TraceWarning, __FUNCTION__"--. IRP %p, STATUS %x\n", I, status); return status;

sampleIorw.cpp中有关读写数据的代码: ULONG sampleExecuteIo(HWND hDlg) //本页的修改实际上前面Write时已经改好 { PSAMPLE_LIST_ITEM ioItem; …… // 获得需要写的字节数 //GetDlgItemText(hDlg, IDC_IN_SIZE_EDIT, str, MAX_STRING_LENGTH); // ioItem->InSize = _ttol(str); // 获得要写的字符串和要写的字节数 GetDlgItemText(hDlg, IDC_IN_DATA_EDIT, str, MAX_STRING_LENGTH); // (VOID)_stscanf(str, _T("%x"), &inPattern); char instr[MAX_STRING_LENGTH]; //TCHAR转换成char* for(int i=0;i<MAX_STRING_LENGTH;i++) instr[i]=(char)str[i]; ioItem->InSize=strlen(instr); // 设置控件IDC_OP_TYPE_COMBO的句柄 hWnd = GetDlgItem(hDlg, IDC_OP_TYPE_COMBO); // 获得当前被选中项目的索引 itemIndex = (DWORD)SendMessage(hWnd, CB_GETCURSEL, 0, 0); // 获得被选中的项目的字符串 SendMessage(hWnd, CB_GETLBTEXT, (WPARAM)itemIndex, (LPARAM)str);

if ((!_tcscmp(str, _T(“IOCTL_ReadWrite”))) && ((ioItem->OutSize > 0) || (ioItem->InSize > 0))) //如是读写端口,本页的的内容需要对照着修改,注意if语句的条件 { // 设置写的缓冲区 ioItem->InBuffer = (PCHAR)malloc(ioItem->InSize); …… strcpy((char *)ioItem->InBuffer,instr); // for (ii = 0; ii < ioItem->InSize; ii += sizeof(DWORD)) // { // CopyMemory(ioItem->InBuffer + ii, &inPattern, // min(sizeof(DWORD), ioItem->InSize - ii)); // } if ((!DeviceIoControl(g_hDevice, IOCTL_ReadWrite, ioItem->InBuffer, ioItem->InSize, ioItem->OutBuffer, ioItem->OutSize, NULL, &ioItem->IoOverlapped)) && (GetLastError() != ERROR_IO_PENDING)) { …… } // 设置回调函数,因为是异步读,所以读出数据后回调函数会启动 ioItem->Callback = sampleIOCTL_ReadWriteCompleteCallback; } //if ((!_tcscmp(str, _T("SAMPLEIOCTL_Write"))) ……

VOID sampleIOCTL_ReadWriteCompleteCallback(PVOID Context) { PSAMPLE_LIST_ITEM ioItem = (PSAMPLE_LIST_ITEM)Context; // 因VS.net隐含采用Unicode编码,每个字符16位,下面做8位到16位字符转换 char wstr[1025]; for(ULONG i=0;i<ioItem->OutSize;i++) wstr[i*2]=ioItem->OutBuffer[i]; wstr[i*2+1]=0x0; } wstr[i*2]=wstr[i*2+1]='\0'; sampleOutputText( _T("Executed IOCTL_ReadWrite request: in buffer size (%d), out buffer size (%d), \nreturn length (%d) error (%d) The string I readed is: %s"), ioItem->InSize, ioItem->OutSize, ioItem->ReturnLength,ioItem->Error, wstr ); ……

3)直接对端口寄存器读写 DriverStudio提供了KIoRange类来将外部总线的I/O地址空间范围映射到处理器总线的地址空间范围。 该类的成员函数主要有 KIoRange 构造函数 (4 种格式) Initialize 初始化和重新初始化一个实例 (3种格式) ~KIoRange 析构函数 Invalidate 从已初始化状态删除该对象 IsValid 测试该对象是否已经初始化 inb 读一个或多个字节 (2 种形式) Outb 写一个或多个字节 (2 种形式) Inw 读一个或多个字 (2 种形式) Outw 写一个或多个字 (2 种形式) ind 读一个或多个双字 (2 种形式) outd 写一个或多个双字 (2 种形式)

KIoRange::KIoRange(只介绍WDM形式) FORM 3 (WDM): KIoRange(    ULONGLONG CpuPhysicalAddress, //转换成外围设备地址的CPU总线上的物理地址    BOOLEAN InCpuIoSpace, //如果IO范围是在CPU总线的IO空间中为TRUE,否则为FALSE    ULONG Count, //以字节计的区域的大小    BOOLEAN MapToSystemVirtual =TRUE//指定是否需要构造函数创建一个非页系统空间的地址空间映射,如果驱动程序读写设备中的数据,就需要这种映射 ); FORM 4 (WDM): (注意: 这种形式不被 DriverStudio 2.0支持。) KIoRange(    PCM_RESOURCE_LIST pTranslatedResourceList, //指向转换资源表的指针    ULONG Ordinal=0, //指定pTranslatedResourceList指向的资源列表中的一个特殊端口资源    BOOLEAN MapToSystemVirtual =TRUE ); FORM 5 (WDM): KIoRange(    PCM_RESOURCE_LIST pTranslatedResourceList, //可通过KIrp::TranslatedResources获得    PCM_RESOURCE_LIST pRawResourceList, //指向原始资源表的指针    ULONG Ordinal=0,    BOOLEAN MapToSystemVirtual =TRUE );构造 KIoRange类。

KIoRange::Initialize (只介绍WDM形式) FORM 2 (WDM): NTSTATUS Initialize(    ULONGLONG CpuPhysicalAddress,    BOOLEAN InCpuIoSpace,    ULONG Count,    BOOLEAN MapToSystemVirtual=TRUE ); FORM 3 (WDM): (注意: 这种形式不被 DriverStudio 2.0支持。) NTSTATUS Initialize(    PCM_RESOURCE_LIST pTranslatedResourceList,    ULONG Ordinal=0,    BOOLEAN MapToSystemVirtual =TRUE ); FORM 4 (WDM): Initialize(    PCM_RESOURCE_LIST pTranslatedResourceList,    PCM_RESOURCE_LIST pRawResourceList,    ULONG Ordinal=0,    BOOLEAN MapToSystemVirtual =TRUE ); 初始化或重新初始化KIoRange的实例。

KIoRange::inb FORM 1: UCHAR inb( ULONG ByteOffset ); FORM 2: VOID inb(    ULONG ByteOffset,    PUCHAR Buffer,    ULONG Count ); 从映射空间读一个或多个字节。

KIoRange::outb FORM 1: VOID outb(    ULONG ByteOffset,//以字节为单位的目标位置到IO空间开始位置的偏移值   UCHAR Data //要写的一个字节数据 ); FORM 2: VOID outb(    ULONG ByteOffset,    PUCHAR Buffer,//指向包含要写数据的缓冲的指针   ULONG Count //缓冲中要写数据的字节数 写一个或多个字节到映射的IO空间。

下面我们来访问CMOS的数据。 首先定义类KIoRange的一个实例,以定义相关地址空间。 KIoRange m_ParPortIos; 初始化实例(指定CMOS的端口首地址,并映射) status = m_ParPortIos.Initialize( 0x70, // CMOS端口首地址是70H TRUE, //在CPU I/O空间内 8, // 设备读写数据的字节宽度 TRUE // 映射到系统空间 ); 写端口( 索引信息, 地址70H)m_ParPortIos.outb(0,0x02); // 准备读分钟信息 读端口(读分钟信息,地址71H) UCHAR data = m_ParPortIos.inb(1);

4)截获中断和挂接中断服务例程 DriverStudio提供了KInterrupt类来截获和挂接中断。 该类的成员函数主要有 KInterrupt 构造函数(3种格式) Initialize 在无效状态下初始化一个对象 (3种格式) Connect 绑定ISR(中断服务例程)到中断 InitializeAndConnect 一步完成初始化与绑定工作,要用资源列表作为输入。 ~KInterrupt 析构函数 Invalidate 在初始化状态下删除对象 IsValid 检查对象是否初始化 Disconnect 使中断和ISR与中断分离 Synchronize 当得到一个中断自旋锁时请求同步功能

KInterrupt::KInterrupt(只介绍WDM形式) FORM 3: (WDM) KInterrupt(    KIRQL irql, //即插即用设备提供的IRQL值    ULONG vector, //即插即用设备提供的向量值    KINTERRUPT_MODE Mode,//LevelSensitive 或 Latched中选一.    BOOLEAN bShareVector=FALSE, //该向量是否被几个设备共享    KAFFINITY affinity=1, //this is the processor affinity mask.    BOOLEAN bSaveFloat =FALSE //是否需要在中断到来使保存浮点处理器上下文,X86平台下必须使FALSE ); 构造类Kinterrupt的实例。

KInterrupt::Initialize(只介绍WDM形式) FORM 2: (WDM) VOID    Initialize(    KIRQL irql,    ULONG vector,    KINTERRUPT_MODE Mode,    BOOLEAN bShareVector=FALSE,    KAFFINITY affinity=1,    BOOLEAN bSaveFloat=FALSE ); 初始化对象。只在对象没有初始化的时候使用。

KInterrupt::Connect FORM 1: NTSTATUS Connect(    PKSERVICE_ROUTINE Isr, //作为ISR服务的函数的地址    PVOID Context //当系统调用ISR的时候传递给他的无类型的参数 ); FORM 2: NTSTATUS Connect(    PKSERVICE_ROUTINE Isr,    PVOID Context,    PKSPIN_LOCK pSpin,    KIRQL SynchIrql ); 绑定一个中断到ISR(中断处理程序)。

KInterrupt::InitializeAndConnect NTSTATUS InitializeAndConnect(    PCM_RESOURCE_LIST pResourceList, //指向资源列表的指针    PKSERVICE_ROUTINE Isr,    PVOID IsrContext,    ULONG Ordinal=0,    BOOLEAN bSaveFloat=FALSE ); 初始化一个中断并绑定到一个ISR上。 对于 WDM 驱动程序,pResourceList 必须是一个转换资源表,例如是KIrp::TranslatedResources的返回值。

下面我们来举例说明。 首先定义类KInterrupt的一个实例 KInterrupt m_TheInterrupt; 在设备类中声明一个成员函数TheIsr作为中断服务例程ISR。 class SampleDevice : public KPnpDevice { …… public: MEMBER_ISR (SampleDevice, TheIsr); …… #ifdef _COMMENT_ONLY BOOLEAN TheIsr(void){ return TRUE ; }; #endif …… }

在OnStartDevice例程中获取包括中断的设备资源并初始化中断和挂接ISR SampleDevice ::OnStartDevice(KIrp I) { …… PCM_RESOURCE_LIST pResList = I.TranslatedResources(); //获取设备资源 //初始化中断并挂接中断服务例程TheIsr status = m_TheInterrupt.InitializeAndConnect( pResList, LinkTo(TheIsr), this ); }

本讲义参考了Driver Studio有关文档