RtlInitUnicodeString、IoCreateDevice、IoCreateSymbolicLink、IoDeleteDevice 四个 API 驱动函数的使用
要解释“驱动对象”,就得先从 DriverEntry() 说起:
做过C语言开发的都知道程序是从 main() 函数开始执行。在进行 Windows 驱动程序开发的时候没有 main() 函数作为函数入口,取而代之的是 DriverEntry().
DriverEntry() 的原型如下:
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath)
{
}
前面的 extern “C”大概的意思就是调用C编译器对函数进行编译,实现C++和C的混合编程。
DriverEntry
描述:初始化驱动程序,定位和申请硬件资源,创建内核对象
参数:
pDriverObject
从I/O管理器中传来的驱动对象
pRegistryPath
驱动程序在注册表中的路径
返回值: 返回初始化驱动状态
什么是 DRIVER_OBJECT 驱动对象,他的作用是什么?
每一个 驱动对象 代表着一个已经装载到内核模式下的驱动,以下例程的输入参数之一就都包含驱动对象:
DriverEntry, AddDevice, Reinitialize(可选例程),Unload(可选例程)
它们都包含指向驱动对象的指针。
驱动对象是一个半透明对象,驱动编写者必须熟悉它的某些成员对象,以实现驱动的初始化功能和卸载功能(如果该驱动能够卸载)。以下列出的是驱动对象中能被驱动访问的成员:
可访问成员
PDEVICE_OBJECT DeviceObject
指向一个由驱动创建的设备对象,当驱动程序调用IoCreateDevice成功时,该成员会自动更新。驱动程序可以利用该成员以及DEVICE_OBJECT对象中的NextDevice成员来实现对由该驱动创建的所有设备列表中设备的遍历。
PDRIVER_EXTENSION DriverExtension
驱动扩展对象指针,该对象唯一能访问的成员是DriverExtension-> AddDevice,对应的是驱动DriverEntry例程中的AddDevice例程。
PUNICODE_STRING HardwareDatabase
指向\Registry\Machine\Hardware,该路径指向的是注册表中包含该硬件的配置信息。
PFAST_IO_DISPATCH FastIoDispatch
指向快速I/O入口地址,该成员之用于FSD(文件系统驱动)已经网络传输驱动。
PDRIVER_INITIALIZE DriverInit
DriverEntry例程的入口点,由I\O管理器设置。
PDRIVER_STARTIO DriverStarIo
驱动程序中StartIo例程的入口地址(如果有的话),当驱动初始化时,DriverEntry例程负责设置它,如果驱动程序没有StartIo,该成员为NULL。
PDRIVER_UNLOAD DriverUnload
驱动程序中Unload例程的入口地址(如果有的话),当驱动初始化时,DriverEntry例程负责设置它,如果驱动程序没有StartIo,该成员为NULL。
PDRIVER_DISPATCHMajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]
派遣例程表,该表包含了驱动中DispatchXxx routines等所有派遣例程的入口地址。该数组的索引值为IRP_MJ_Xxx,该值代表每一个IRP的主功能函数代码(IRP major function code), 任何驱动都必须为IRP_MJ_Xxx请求设置入口地址。每一个DispatchXxx例程的定义如下所示:
NTSTATUS(*PDRIVER_DISPATCH) (IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp);
备注:
每一个内核模式的驱动的初始化例程(驱动入口)都必须命名为 DriverEntry,这样系统才可以自动将驱动加载进来。
如果例程的名字为其他,则驱动编写者必须为链接器指定对应的初始化例程的名字,否则,系统的加载器或者I/O管理器将无法找到驱动的传递地址。其他标准驱动的名字可以由驱动编写者自行决定。
一个驱动必须在驱动对象中设置 DispatchXxx 的入口地址,在驱动加载之后,该驱动对象将传递给 DriverEntry。一个设备的驱动必须为每一个 IRP_MJ_XXX 设置一个或者多个DispatchXxx 的入口地址,对同一类型的设备来说,该 IRP_MJ_XXX 都将被处理。一个上层的驱动必须为所有的 IRP_MJ_XXX 设置一个或者多个 DispatchXxx 入口点,这些IRP_MJ_XXX 将被发送到下一级的设备驱动,否则,驱动无法给没有设置 DispatchXxx 例程发送任何 IRP_MJ_XXX。
DriverEntry 例程设置了的驱动的 AddDevice, StartIO 以及 Unload 等函数的入口地址。
当驱动加载时,设备驱动可以利用 HardWareDatabase 字符串来从注册表中获取硬件的配置信息。该字符串对驱动来说只读。驱动对象中没有列出的成员是无法访问的。
什么是 DEVICE_OBJECT 驱动对象,它的作用是什么?
DEVICE_OBJECT 结构被操作系统用来表示为一个”设备对象 ”,这个设备对象可以代表逻辑的、虚拟的或者是物理设备。每一个设备对象都会有一个指针来指向下一个设备对象(如果有的话),形成一个设备链。设备链的第一个设备是在 DRIVER_OBJECT 驱动对象结构体中指明的。
驱动的 API 函数:
1. RtlInitUnicodeString
作用: 初始化设备名称指针。
VOID RtlInitUnicodeString
(
IN OUT UNICODE_STRING DestinationString,
IN PCWSTR SourceString
);
参数:
DestinationString
需要初始化的指针 UNICODE_STRING
SourceString
指向一个以空结尾的 Unicode 字符串常量,用这个字符串来初始化 DestinationString
如例:
代码1:
UNICODE_STRING US1; RtlInitUnicodeString(&US1,L"DDDD");
会动态分配一块指向“DDDD”的内存指针,赋值给US1.Buffer;
代码2:
wchar_t tmpstr[260]={0}; UNICODE_STRING US1; RtlInitUnicodeString(&US1,tmpstr);
这时 US1.Buffer 直接指向 tmpstr, 如果修改了 US1,也会同时修改 tmpstr。
另外此时 US1.MaximumLength=2;
要重新设定 MaximumLength=260*2,才能正常使用。
以前总是对符号链接不太明白,今天看到了一篇文章,讲的很好,记录一下。
Windows下的设备是以"\Device\[设备名]”形式命名的。例如磁盘分区的c盘,d盘的设备名就是 "\Device\HarddiskVolume1”,"\Device\HarddiskVolume2”, 当然也可以不指定设备名称。
如果 IoCreateDevice 中没有指定设备名称,那么I/O管理器会自动分配一个数字作为设备的名称。
例如"\Device\00000001"。
\Device\[设备名],不容易记忆,通常符号链接可以理解为设备的别名,更重要的是设备名,只能被内核模式下的其他驱动所识别,而别名可以被用户模式下的应用程序识别。
例如c盘,就是名为"c:"的符号链接,其真正设备对象是"\Device\HarddiskVolume1”,所以在写驱动时候,一般我们创建符号链接,即使驱动中没有用到,这也算是一个好的习惯吧。
驱动中符号链接名是这样写的 :
L"\\??\\HelloDDK" --->\??\HelloDDK
L"\\DosDevices\\HelloDDK" --->\DosDevices\HelloDDK
在应用程序中,符号链接名:
L"\\\\.\\HelloDDK" -->\\.\HelloDDK
winobj 和 DeviceTree 可以用来查看这些信息。
2. IoCreateDevice
作用: 此函数用于创建常规的设备对象.
NTSTATUS IoCreateDevice(
IN PDRIVER_OBJECT DriverObject,
IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceNameOPTIONAL,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
OUT PDEVICE_OBJECT *DeviceObject );
参数:
DriverObject
一个指向调用该函数的驱动程序对象.每一个驱动程序在它的DriverEntry过程里接收一个指向它的驱动程序对象. WDM功能和过滤驱动程序也在他们的AddDevice过程接受一个驱动程序对象的指针.
DeviceExtensionSize
DeviceName
(可选的参数)指向一个以零结尾的包含 Unicode 字符串的缓冲区,那是这个设备的名称,该字符串必须是一个完整的设备路径名. WDM功能驱动程序和过滤驱动程序它们设备对象没有名字.
注意:如果设备名未提供(即这个参数是NULL),IoCreateDevice创建的设备对象将不会有一个DACL与之相关联.
DeviceType
指定一个由一个系统定义的FILE_DEVICE_XXX常量,表明了这个设备的类型 (如FILE_DEVICE_DISK,FILE_DEVICE_KEYBOARD等),或供应商定义的一种新型设备的类型.
DeviceCharacteristics
指定一个或多个系统定义的常量,连接在一起,提供有关驱动程序的设备其他信息.对于可能的设备特征信息, 见DEVICE_OBJECT结构体.
Exclusive
如果指定设备是独占的,大部分驱动程序设置这个值为FALSE,如果是独占的话设置为TRUE,非独占设置为FALSE.
DeviceObject
一个指向 DEVICE_OBJECT 结构体指针的指针,这是一个指针的指针,指向的指针用来接收DEVICE_OBJECT结构体的指针.
返回值:
IoCreateDevice 函数成功时返回 STATUS_SUCCESS,失败时返回适当的 NTSTATUS 错误代码.这些错误代码是:
STATUS_INSUFFICIENT_RESOURCES //资源不足
STATUS_OBJECT_NAME_EXISTS //指定对象名存在
STATUS_OBJECT_NAME_COLLISION //对象名有冲突
调用要求: 包含文件( wdm.h,ntddk.h)
3. IoCreateSymbolicLink
作用: 此函数用于将设备与符号连接进行绑定。
NTSTATUS IoCreateSymbolicLink(
IN PUNICODE_STRING SymbolicLinkName,
IN PUNICODE_STRING DeviceName );
参数:
SymbolicLinkName
Unicode 字符串指针,是一个用户态可见的名称。
DeviceName
Unicode 字符串指针,是驱动程序创建的设备对象名称。
返回值:
如果符号链接创建成功 返回 STATUS_SUCCESS
4. IoDeleteDevice
作用: 此函数用于删除已建立的设备
VOID IoDeleteDevice(
IN PDEVICE_OBJECT DeviceObject );
参数:
DeviceObject
PDEVICE_OBJECT类型的指针,指向需要删除的设备对象
无返回值
5. IoDeleteSymbolicLink
作用: 此函数用于从系统中删除一个符号链接。
NTSTATUS IoDeleteSymbolicLink(
IN PUNICODE_STRING SymbolicLinkName );
参数:
SymbolicLinkName
PUNICODE_STRING类型的指针,指向一个缓冲 Unicode 字符串的用户可见链接符号名称
返回值:
如果符号链接删除成功 返回 STATUS_SUCCESS
添加驱动设备例程
用工具查看驱动及驱动设备
请看下面的实例:
首先创建一个头文件()
#pragma once
#ifdef _cplusplus
extern "C"
{
#endif
#include <ntddk.h> //引用内库的 ntddk.h 头文件,它提供函数、类、结构和各种接口
#ifdef _cplusplus
}
#endif static PDEVICE_OBJECT MyDevice; //定义一个全局变量来存储设备
static UNICODE_STRING SymbolicLinkName; //定义一个全局变量来存链接符 #define INITCODE cod_seg("INIT") //注意,是INITCODE cod_seg ,名称写错了会蓝屏
#pragma INITCODE
NTSTATUS CreateMyDevice(PDRIVER_OBJECT pDriverObject)
{
NTSTATUS Status; //定义一个局部变量来存储返回值
UNICODE_STRING DeviceName; // 定义一个局部变量来存储设备名称 RtlInitUnicodeString(&DeviceName, L"\\Device\\Myddk.Device"); //实例化设备名称变量,并给它赋值
RtlInitUnicodeString(&SymbolicLinkName, L\\Device\\DeviceSymbolLink); //实例化设备链接符名称变量,并给它赋值 //用 IoCreateDevice API 函数创建设备
Status = IoCreateDevice(pDriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, TRUE, &MyDevice);
if (!NT_SUCCESS(Status))
{
if (Status==STATUS_INSUFFICIENT_RESOURCES)
{
KdPrint(("资源不足"));
}
if (Status == STATUS_OBJECT_NAME_EXISTS)
{
KdPrint(("指定对象名称存在"));
}
if (Status == STATUS_OBJECT_NAME_COLLISION)
{
KdPrint(("对象名冲突"));
}
KdPrint(("创建设备失败!!"));
return Status;
} MyDevice->Flags |= DO_BUFFERED_IO; //设置读写方式,这里为缓冲区读写方式 //给创建的设备名称绑定链接符
Status = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);
if (!NT_SUCCESS(Status))
{
IoDeleteDevice(MyDevice);
KdPrint(("设备链接符绑定失败,设备已删除!!"));
return Status;
}
KdPrint(("设备创建完成,设备已成功加载"));
return Status;
}
#ifdef _cplusplus
extern "C"
{
#endif
#include "myddk.h"
#ifdef _cplusplus
}
#endif #define PAGEDCODE cod_seg("PAGE") //注意,是PAGEDCODE cod_seg ,名称写错了会蓝屏
#pragma PAGEDCODE
VOID Unload(PDRIVER_OBJECT pDriverObject);
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pUnicodeString)
{ CreateMyDevice(pDriverObject); //调用头文件里的创建设备的函数,来进行创建设备 //用 DriverUnload 宏来进行卸载,然后把卸载的过程赋给它,其实DriverUnload已经可以卸载驱动了,
//这里赋值的作用主要是让它能打印出消息
pDriverObject->DriverUnload = Unload;
KdPrint(("我的第一个驱动"));
return STATUS_SUCCESS;
} //创建一个卸载驱动设备的过程
VOID Unload(PDRIVER_OBJECT pDriverObject)
{
//卸载驱动对象,什么对象呢,DeviceObject设备对象,设备对象叫什么呢?叫 MyDevice
pDriverObject->DeviceObject = MyDevice; //用 IoDeleteDevice API函数卸载设备,参数为指定要卸载的设备对象,
//此参数是已在 myddk.h头文件中实例化的设备对象
IoDeleteDevice(MyDevice); //用 IoDeleteSymbolicLink API函数卸载链接符,参数为指定要卸载的链接符对象,
//此参数为已在 myddk.h头文件中实例化的 SymbolicLinkName链接符对象
IoDeleteSymbolicLink(&SymbolicLinkName);
KdPrint(("驱动和设备移除成功!!"));
}
然后,在源文件中,来加载与卸载这个设备!
#ifdef _cplusplus
extern "C"
{
#endif
#include "myddk.h"
#ifdef _cplusplus
}
#endif #define PAGEDCODE cod_seg("PAGE") //注意,是PAGEDCODE cod_seg ,名称写错了会蓝屏
#pragma PAGEDCODE
VOID Unload(PDRIVER_OBJECT pDriverObject);
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pUnicodeString)
{ CreateMyDevice(pDriverObject); //调用头文件里的创建设备的函数,来进行创建设备 //用 DriverUnload 宏来进行卸载,然后把卸载的过程赋给它,其实DriverUnload已经可以卸载驱动了,
//这里赋值的作用主要是让它能打印出消息
pDriverObject->DriverUnload = Unload;
KdPrint(("我的第一个驱动"));
return STATUS_SUCCESS;
} //创建一个卸载驱动设备的过程
VOID Unload(PDRIVER_OBJECT pDriverObject)
{
KdPrint(("驱动和设备移除成功!!"));
}
然后,我们用 DriverMonitor 工具来把生成的驱动加载到系统中,再用 Winobj 工具来查看设备,看是否创建了我们定义的名称为“Myddk.Device”的设备。
如图:
驱动加载成功!
在 Winobj 工具中的 Device 路径项中可以查看到,我们的设备 Myddk.Device 以创建成功。设备名称为 Myddk.Device;设备类型为 Device;可是设备的链接符没有??这是为什么,链接符也定义初始化也绑定了,如果绑定不成功,会删除设备啊!!
呵呵呵,我也不明白…这问题暂搁着,我问继续往下看。
现在我们用 DriverMonitor 工具卸载驱动看看!!
卸载成功!
什么情况??怎么设备还在呢??
但是驱动却不在了!!
哦,想起来了,我刚刚确实卸载的只是驱动,并没卸载什么设备…,晕,咋这么古板呢!!
好吧!看来我们还得用代码向系统内存发送卸载设备的指令!!
见下面一小段代码:
//创建一个卸载驱动设备的过程
VOID Unload(PDRIVER_OBJECT pDriverObject)
{
//卸载驱动对象,什么对象呢,DeviceObject设备对象,设备对象叫什么呢?叫 MyDevice
pDriverObject->DeviceObject = MyDevice; //用 IoDeleteDevice API函数卸载设备,参数为指定要卸载的设备对象,
//此参数是已在 myddk.h头文件中实例化的设备对象
IoDeleteDevice(MyDevice); //用 IoDeleteSymbolicLink API函数卸载链接符,参数为指定要卸载的链接符对象,
//此参数为已在 myddk.h头文件中实例化的 SymbolicLinkName链接符对象
IoDeleteSymbolicLink(&SymbolicLinkName);
KdPrint(("驱动和设备移除成功!!"));
}
再次测试…
果不其然,添加卸载设备的API后顺利卸载。
RtlInitUnicodeString、IoCreateDevice、IoCreateSymbolicLink、IoDeleteDevice 四个 API 驱动函数的使用的更多相关文章
- 朱晔的互联网架构实践心得S2E5:浅谈四种API设计风格(RPC、REST、GraphQL、服务端驱动)
Web API设计其实是一个挺重要的设计话题,许多公司都会有公司层面的Web API设计规范,几乎所有的项目在详细设计阶段都会进行API设计,项目开发后都会有一份API文档供测试和联调.本文尝试根据自 ...
- 【转】Android LCD(四):LCD驱动调试篇
关键词:android LCD TFTSN75LVDS83B TTL-LVDS LCD电压背光电压 平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台 ...
- 【转】android camera(四):camera 驱动 GT2005
关键词:android camera CMM 模组 camera参数 GT2005 摄像头常见问题 平台信息: 内核:linux系统:android 平台:S5PV310(samsung exyn ...
- 2017-2018-1 20155214&20155216 实验四:外设驱动程序设计
2017-2018-1 20155214&20155216 实验四:外设驱动程序设计 实验四外设驱动程序设计-1 实验要求: 学习资源中全课中的"hqyj.嵌入式Linux应用程序开 ...
- SVG 学习<四> 基础API
目录 SVG 学习<一>基础图形及线段 SVG 学习<二>进阶 SVG世界,视野,视窗 stroke属性 svg分组 SVG 学习<三>渐变 SVG 学习<四 ...
- Java阻塞队列四组API介绍
Java阻塞队列四组API介绍 通过前面几篇文章的学习,我们已经知道了Java中的队列分为阻塞队列和非阻塞队列以及常用的七个阻塞队列.如下图: 本文来源:凯哥Java(kaigejava)讲解Java ...
- JUC(5)BlockingQueue四组API
1.读写锁ReadWriteLock package com.readlock; import java.util.HashMap; import java.util.Map; /** * ReadW ...
- VC API常用函数简单例子大全(1-89)
第一个:FindWindow根据窗口类名或窗口标题名来获得窗口的句柄,该函数返回窗口的句柄 函数的定义:HWND WINAPI FindWindow(LPCSTR lpClassName ,LPCST ...
- 《Effective C#》:区别和认识四个判等函数
.Net有四个判等函数?不少人看到这个标题,会对此感到怀疑.事实上确是如此,.Net提供了ReferenceEquals.静态Equals,具体类型的Equals以及==操作符这四个判等函数.但是这四 ...
随机推荐
- ORACLE/MYSQL/DB2等不同数据库取前几条记录
选取数据库中记录的操作是最基础最频繁的,但往往实际应用中不会这么简单,会在选取记录的时候加上一些条件,比如取前几条记录,下面就总结了如何在ORACLE/MYSQL/DB2等一些热门数据库中执行取前几条 ...
- A Bit Of Knowledge
iOS推崇使用png格式的图片,说这样不会失帧 imageNamed 和 imageWithContentOfFile的区别 imageNamed会使用系统缓存,对重复加载的图片速度会快一些,效果好. ...
- GridView+ZedGraph【转】
edgraph图表控件的强大功能令人出乎意料,与OWC相比我想应该毫不逊色,近来需求要求作出相关数据统计,不想使用BI这类的强大东西,所以搜索到 了免费的开源的Zedgraph控件.使用起来也非常方便 ...
- C#控制条码打印机 纸张大小,间距,绘制内容(所有条码打印机通用)
其他条码知识 请访问:http://www.ybtiaoma.com ,本文仅供参考,请勿转载,谢谢 using System; using System.Drawing; using System. ...
- (转) class II
Overloading operators Classes, essentially, define new types to be used in C++ code. And types in ...
- mysql实现随机查询
一.随机查询一条数据 方法一:SELECT * FROM `table` ORDER BY RAND() limit 1 评价:不建议使用,效率非常低,官方文档中进行说明:Order By和RAND( ...
- 10--动作系统(四)动作类中的reverse方法
上一篇文章在使用持续动作过程中遇到不少问题,以获取动作类的反系动作尤为突出.所以今天把动作类找了个遍,先将大部分动作类是否实现reverse方法总结如下: T表示实现F表示没有实现. 观察可以发现带T ...
- 宽带连接工具[bat]
功能概述: 本工具使用批处理编写,提供自动判断网络状态以决定断开或是连上网络,本月已用宽带时长,到月初自动清零.提供联网日志功能,可以记录下所有的连接或断开网络记录.如果连接失败,自动提示输入密码,特 ...
- MVC中一般为什么用IQueryable而不是用IList?用IQueryable比IList好在哪?
IList(IList<T>)会立即在内存里创建持久数据,这就没有实现"延期执行(deferred execution)",如果被加载的实体有关联实体(associat ...
- Mac OSX下面的博客客户端Marsedit使用
在windows下面,有一个很好用的博客客户端,叫做windows live writer,不得不感叹,其所见即所得的方面真的是很方便,特别是还可以方便的把word上的内容直接帖上去,包括文件中 ...