要解释“驱动对象”,就得先从 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 驱动函数的使用的更多相关文章

  1. 朱晔的互联网架构实践心得S2E5:浅谈四种API设计风格(RPC、REST、GraphQL、服务端驱动)

    Web API设计其实是一个挺重要的设计话题,许多公司都会有公司层面的Web API设计规范,几乎所有的项目在详细设计阶段都会进行API设计,项目开发后都会有一份API文档供测试和联调.本文尝试根据自 ...

  2. 【转】Android LCD(四):LCD驱动调试篇

    关键词:android LCD TFTSN75LVDS83B  TTL-LVDS LCD电压背光电压 平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台 ...

  3. 【转】android camera(四):camera 驱动 GT2005

    关键词:android  camera CMM 模组 camera参数  GT2005 摄像头常见问题 平台信息: 内核:linux系统:android 平台:S5PV310(samsung exyn ...

  4. 2017-2018-1 20155214&20155216 实验四:外设驱动程序设计

    2017-2018-1 20155214&20155216 实验四:外设驱动程序设计 实验四外设驱动程序设计-1 实验要求: 学习资源中全课中的"hqyj.嵌入式Linux应用程序开 ...

  5. SVG 学习<四> 基础API

    目录 SVG 学习<一>基础图形及线段 SVG 学习<二>进阶 SVG世界,视野,视窗 stroke属性 svg分组 SVG 学习<三>渐变 SVG 学习<四 ...

  6. Java阻塞队列四组API介绍

    Java阻塞队列四组API介绍 通过前面几篇文章的学习,我们已经知道了Java中的队列分为阻塞队列和非阻塞队列以及常用的七个阻塞队列.如下图: 本文来源:凯哥Java(kaigejava)讲解Java ...

  7. JUC(5)BlockingQueue四组API

    1.读写锁ReadWriteLock package com.readlock; import java.util.HashMap; import java.util.Map; /** * ReadW ...

  8. VC API常用函数简单例子大全(1-89)

    第一个:FindWindow根据窗口类名或窗口标题名来获得窗口的句柄,该函数返回窗口的句柄 函数的定义:HWND WINAPI FindWindow(LPCSTR lpClassName ,LPCST ...

  9. 《Effective C#》:区别和认识四个判等函数

    .Net有四个判等函数?不少人看到这个标题,会对此感到怀疑.事实上确是如此,.Net提供了ReferenceEquals.静态Equals,具体类型的Equals以及==操作符这四个判等函数.但是这四 ...

随机推荐

  1. 客户端调用web中js方法(C调B)跨域问题

    这几天遇到了个棘手问题(c调b),经过排错查出了问题. 一,问题描述如下: 1.客户端需要调用father.html中一个js方法,特殊之处在于 这个father.html中有个iframe嵌套了一个 ...

  2. 被sjy带刷题#1

        笔记[问题描述]给定一个长度为m的序列a,下标编号为1~m.序列的每个元素都是1~n的整数.定义序列的代价为 你现在可以选择两个数x和y,并将序列a中所有的x改成y.x可以与y相等.请求出序列 ...

  3. (转) Functions

    Functions Functions allow to structure programs in segments of code to perform individual tasks. In ...

  4. GTW likes math(BC 1001)

    GTW likes math Accepts: 472 Submissions: 2140 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 1 ...

  5. Hdu1089

    #include <stdio.h> int main() { int a,b; while(scanf("%d %d",&a,&b)!=EOF){ p ...

  6. 基于C#—WPF的扫雷游戏

    自学教材:<C#入门经典(第六版)>,1月28日购入,1月29日到2月9日学习了前十六章,由于有C语言基础,在语法阶段学习起来比较轻松,不过在接触到面向对象的时候遇到了一些困难,对于一些概 ...

  7. 段(SEGMENT)

    伪指令SEGMENT被用来声明一个普通段.一个再定位类型(relocation type)和一个地址分配类型(allocation type)可以按如下形式指明: segment SEGMENT cl ...

  8. Mysql.Data的连接驱动 .net 的源码竟然在git了

    如标题 上链接:https://github.com/mysql/mysql-connector-net

  9. 编译boost python模块遇到的错误:../../libraries/boost_1_44_0/boost/python/detail/wrap_python.hpp:75:24: fatal error: patchlevel.h: No such file or directory

    就是遇到类似标题上面的错误. 原因是没有安装对应python的python-dev依赖,不然编译到boost python模块的时候就会出错. 所以解决方案是sudo apt-get install ...

  10. easyui 表单验证validatetype——支持自定义验证

    easyui 的validatebox()提供了自定义验证的方法,为此我把一些常用的数据验证汇总了一下,代码如下: 代码 Code highlighting produced by Actipro C ...