Windows 驱动开发 新手入门(一)

article/2025/10/13 1:59:21

Windows 驱动开发 新手入门(一)

  • 引言
  • 驱动介绍
    • Win10 WDK
  • 建立一个驱动项目
    • 建立MyDriver.cpp
    • 理论知识
      • 驱动入口
      • 派遣函数 MajorFunction
      • Device和SymbolicLink
      • DeviceExtension
      • IRP

引言

首先祝朋友们新年快乐,然后呢,因为无聊,写2篇文章打发时间,而且太久没弄过windows的东西了,算是回顾了,本文是对Windows下的驱动开发有一个简单的介绍,我尽可能写的小白文一些,因为大多数的驱动开发书籍对新手来说还是过于难理解。

本篇文章通过WDM进行介绍,具体NT式驱动例子看这:
Windows 驱动开发 新手入门(二)

本系列所有文章
Windows 驱动开发 新手入门(三)
Windows 驱动开发 新手入门(四)

驱动介绍

在介绍驱动开发之前,先了解一下基础知识驱动是什么的?

驱动这个词是由Driver直译的,这和平常开发中的测试驱动开发(TDD)中的驱动并不是一个意思。
驱动是在内核下工作的,如果你了解过Windows的一些内核对象(如Event Mutex等),你也许会认为在Windows用户层也可以随意获取到内核对象。实际上虽然我们在用户层可以获取到,但这只是通过微软公开的API获取的。

驱动(Driver)我们可以理解为系统和硬件交互用的,我们不需要知道底层硬件是什么样子的,我们不需要为每个硬件单独写一份代码,因为我们可以通过系统和硬件交互,使用系统提供的API来和硬件交互,这些操作都是在系统内核中完成的。它是一套在Windows中的标准,我们不用关心硬件底层是如何实现的,这就像是DLL用户层模块驱动就是内核层模块

设备(Device),可以是关联的物理设备,也可以是我们在驱动中创建的虚拟设备,应用层(通过Symbolic Link找到Device)向Device发送IRP(I/O Request Packet) IO请求包,此时我们的驱动就可以处理这些请求。我们为了拥有更高的权限,一般会创建一个虚拟设备,仅仅只是为了让代码在内核中工作。

符号链接(Symbolic Link),Windows中可以通过mklink直接创建符号链接,它的作用你可以理解为Linux中的软连接,同样我们也可以通过代码创建一个符号链接,只是它指向的是一个Device,应用层通过符号链接找到设备,此时通过IRP就可以和驱动进行交互了。

Win10 WDK

Windows Driver Kit 用于开发、测试和部署 Windows 驱动程序(官网原话)

由于我的电脑在去年中旬做过一次系统,所以索性装了VS2019,所以请挑选对应版本的WDK进行安装。
Windows 10 版本 2004 VS 2019 https://docs.microsoft.com/zh-cn/windows-hardware/drivers/download-the-wdk

其他版本 https://docs.microsoft.com/zh-cn/windows-hardware/drivers/other-wdk-downloads

建立一个驱动项目

在安装完WDK后,新建项目选择Driver你会看见如下这些类型,VS2019中无法快速建立一个NT式驱动。
1
所以我们选择WDM驱动,我想先从WDM驱动介绍开始
在这里插入图片描述

可以清楚的看见只有一个inf文件,这个文件是之后驱动安装的文件,我们先跳过。
1

建立MyDriver.cpp

Source Files目录建立MyDriver.cpp,并且将下面的代码拷贝进去,不要急,代码我会挑出关键部分单讲,并且我尽可能写了每行代码的注释。

//由于我们建立的是CPP文件,所以引入头文件和入口函数需要extern "C"
extern "C" {
#include <wdm.h>
}#define DEVICE_NAME L"\\Device\\MyWdmDevice" //定义设备名称
#define LINK_NAME L"\\??\\MyWdmLink" //定义符号链接
NTSTATUS DriverAddDevice(PDRIVER_OBJECT pDriverObj, PDEVICE_OBJECT pPhysicalDeviceObject);
NTSTATUS DispatchRoutine(IN PDEVICE_OBJECT pDeviceObj, IN PIRP pIrp);
NTSTATUS WDMPNP(IN PDEVICE_OBJECT pDeviceObj, IN PIRP pIrp);
void DriverUnload(PDRIVER_OBJECT pDriverObj);extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegistryString)
{UNREFERENCED_PARAMETER(pRegistryString); //不用将编译警告等级设为3,不使用 就Unreferenced即可DbgPrint("驱动被加载\n");//这里和nt驱动的区别是,我们不要去直接给MajorFunction 派遣 IRP_MJ_CREATE之类的//这里我们需要用到扩展//在WDM驱动程序中DriverEntry不再负责创建设备,而是交由AddDevice例程去创建设备。pDriverObj->DriverExtension->AddDevice = DriverAddDevice;pDriverObj->DriverUnload = DriverUnload; //这里不是真实的卸载//IRP全部派遣到DisPatchRoutinefor (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++){pDriverObj->MajorFunction[i] = DispatchRoutine;}//重写PNP派遣pDriverObj->MajorFunction[IRP_MJ_PNP] = WDMPNP; //即插即用服务return STATUS_SUCCESS;
}typedef struct _DEVICE_EXTENSION
{PDEVICE_OBJECT PDeviceObject; ///< 设备对象PDEVICE_OBJECT PNextStackDevice; ///< 下层设备对象指针UNICODE_STRING DeviceName; ///< 设备名称UNICODE_STRING SymLinkName; ///< 符号链接名}DEVICE_EXTENSION, * PDEVICE_EXTENSION;NTSTATUS DriverAddDevice(PDRIVER_OBJECT pDriverObj, PDEVICE_OBJECT pPhysicalDeviceObject)
{DbgPrint("AddDevice\n");NTSTATUS status = STATUS_SUCCESS;UNICODE_STRING deviceName;UNICODE_STRING linkName;PDEVICE_OBJECT pDeviceObj = NULL; //创建设备对象PDEVICE_EXTENSION pDeviceExt = NULL; //设备扩展对象RtlInitUnicodeString(&deviceName, DEVICE_NAME); //初始化Unicode字符串 设备名称RtlInitUnicodeString(&linkName, LINK_NAME);//初始化Unicode字符串 符号链接名称//创建设备status = IoCreateDevice(pDriverObj, sizeof(PDEVICE_EXTENSION), &deviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObj); if (!NT_SUCCESS(status)){return status;}//让扩展对象指向刚刚创建的设备的扩展pDeviceExt = (PDEVICE_EXTENSION)(pDeviceObj->DeviceExtension);pDeviceExt->PDeviceObject = pDeviceObj;pDeviceExt->DeviceName = deviceName;pDeviceExt->SymLinkName = linkName;// 将设备对象挂接在设备堆栈上pDeviceExt->PNextStackDevice = IoAttachDeviceToDeviceStack(pDeviceObj, pPhysicalDeviceObject);//创建符号链接status = IoCreateSymbolicLink(&linkName, &deviceName);if (!NT_SUCCESS(status)){IoDeleteSymbolicLink(&pDeviceExt->SymLinkName);status = IoCreateSymbolicLink(&linkName, &deviceName);if (!NT_SUCCESS(status)){return status;}}pDeviceObj->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;pDeviceObj->Flags &= ~DO_DEVICE_INITIALIZING;DbgPrint("Leave AddDevice\n");return status;
}/// @brief 对默认IPR进行处理
NTSTATUS DispatchRoutine(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp)
{UNREFERENCED_PARAMETER(pDeviceObject);DbgPrint("Enter WDMDispatchRoutine\n");NTSTATUS status = STATUS_SUCCESS;pIrp->IoStatus.Status = status;pIrp->IoStatus.Information = 0;IoCompleteRequest(pIrp, IO_NO_INCREMENT);DbgPrint("Leave WDMDriverDispatchRoutine\n");return status;
}/// @brief PNP移除设备处理函数
/// @param[in] pDeviceExt 设备扩展对象
/// @param[in] pIrp 请求包
/// @return 状态
NTSTATUS PnpRemoveDevice(PDEVICE_EXTENSION pDeviceExt, PIRP pIrp)
{KdPrint(("Enter HandleRemoveDevice\n"));pIrp->IoStatus.Status = STATUS_SUCCESS;// 略过当前堆栈IoSkipCurrentIrpStackLocation(pIrp);// 用下层堆栈的驱动设备处理此IRPNTSTATUS status = IoCallDriver(pDeviceExt->PNextStackDevice, pIrp);// 删除符号链接IoDeleteSymbolicLink(&pDeviceExt->SymLinkName);//调用IoDetachDevice()把设备对象从设备栈中脱开:  if (pDeviceExt->PNextStackDevice != NULL)IoDetachDevice(pDeviceExt->PNextStackDevice);//删除设备对象:  IoDeleteDevice(pDeviceExt->PDeviceObject);DbgPrint("Leave HandleRemoveDevice\n");return status;
}NTSTATUS WDMPNP(IN PDEVICE_OBJECT pDeviceObj, IN PIRP pIrp)
{PDEVICE_EXTENSION pDeviceExt = (PDEVICE_EXTENSION)pDeviceObj->DeviceExtension;PIO_STACK_LOCATION pStackLoc = IoGetCurrentIrpStackLocation(pIrp);unsigned long func = pStackLoc->MinorFunction;DbgPrint("PNP Request (%u)\n", func);NTSTATUS status = STATUS_SUCCESS;switch (func){case IRP_MN_REMOVE_DEVICE:status = PnpRemoveDevice(pDeviceExt, pIrp);break;default:// 略过当前堆栈IoSkipCurrentIrpStackLocation(pIrp);// 用下层堆栈的驱动设备处理此IRPstatus = IoCallDriver(pDeviceExt->PNextStackDevice, pIrp);break;}DbgPrint("Leave HelloWDMPnp\n");return status;
}/// @brief 驱动程序卸载操作
void DriverUnload(PDRIVER_OBJECT pDriverObj)
{UNREFERENCED_PARAMETER(pDriverObj);DbgPrint("Enter WDMUnload\n");DbgPrint("Leave WDMUnload\n");
}

理论知识

驱动入口

在之前写程序时,程序入口函数为main,参数有argc和argv代表着命令行的参数个数及对应的字符串指针,驱动也有入口函数为DriverEntry,其返回类型为NTSTATUS

函数的第一个参数pDriverObj是刚被初始化的驱动对象指针
函数的第二个参数pRegistryString是驱动在注册表中的键值。

DriverUnload是驱动卸载的回调,如果我们不设置DriverUnload,那么此时我们将无法正常的卸载驱动,系统这么做的原因是为了保证系统的稳定性
比如我们在DriverEntry中添加了某些系统回调,此时我们没有DriverUnload,因此系统不知道什么时候该移除这些回调,如果暴力移除驱动,此时系统回调会出问题,系统回调表中存在了一个被移除掉的驱动的回调,当调用时系统蓝屏

派遣函数 MajorFunction

我们在驱动入口函数中看到了pDriverObj->MajorFunction数组,其作用是针对每一个的事件都有与之对应回调函数

因为WDM驱动不在DriverEntry入口中去创建设备和对应的符号链接,而是移到了DriverAddDevice函数中,所以pDriverObj->MajorFunction中我们并没有单独指定pDriverObj->MajorFunction[IRP_MJ_CREATE]pDriverObj->MajorFunction[IRP_MJ_CLOSE],而是将所有的派遣函数指向一个通用的回调函数DispatchRoutine

Device和SymbolicLink

其中设备名称在代码中被我们宏定义L"\\Device\\MyWdmDevice"符号链接被我们定义为L"\\??\\MyWdmLink"

Device是用来和应用层通信的,应用层程序可以通过SymbolicLink找到对应的设备。

IoCreateDevice就是创建设备的函数

  • 第一个参数为驱动对象指针
  • 第二个参数为设备扩展结构(DeviceExtension)大小
  • 第三个参数为设备名称,具体可以查看微软文档。

IoCreateSymbolicLink就是创建符号链接的函数

  • 第一个参数为符号链接名称
  • 第二个参数为设备名称

DeviceExtension

设备扩展结构,你可以将某些信息保存在扩展结构中,这样可以避免使用全局变量,应用层通信时,可以读取或写入的信息的结构,之后NT式驱动的例子中会用到。

IRP

在通用的派遣函数DispatchRoutine中,我们看到了一个参数pIrpIRP的含义是 I/O Request Packet缩写,是在驱动中IO请求的结构体,其具体作用,我决定放在NT式驱动中细说。


http://chatgpt.dhexx.cn/article/PyvYZt16.shtml

相关文章

驱动开发学习

驱动 1、驱动开发环境 完成系统移植的三步&#xff1a;u-boot启动引导程序、内核镜像、文件系统&#xff0c;u-boot启动引导程序最好固化到开发板上&#xff0c;内核镜像通过tftp服务从ubuntu下载&#xff0c;文件系统通过nfs服务从ubuntu共享到开发板&#xff0c;开发板启动…

Linux设备驱动开发详解

Linux设备驱动开发详解 Linux设备驱动开发详解Linux设备驱动开发Linux设备驱动开发详解1. linux设备驱动概述及开发环境搭建2. 驱动设计的硬件基础3. linux内核及内核编程4. linux内核模块5. linux文件系统与设备文件6. 字符设备驱动7. linux设备驱动中的并发控制8. linux设备驱…

QT部件基类——QWidget与QDialog

一、默认基类 QT提供的默认部件基类包括QMainWindow、QWidget、和QDialog这三种&#xff0c;这三个部件基类也是用的最多的。 QWidget类是所有部件对象的基类&#xff0c;被称为基础窗口部件&#xff0c;继承关系详看QWidget类关系图。QWidget提供自我绘制和处理用户输入等基本…

qt 中 qwidget 嵌入 qml

以上是效果图&#xff0c; 整个窗口是 qml 窗口 核心代码 #ifndef WIDGETANCHOR_H #define WIDGETANCHOR_H#include <QObject> #include <QQuickItem> #include <QWidget> #include <QQmlApplicationEngine> #include <QQmlContext>class WgtAn…

QWidget尺寸限定

1、控件只能在最小和最大之间进行调整&#xff0c;不能超过范围。 ①直接宽高同时设置 window.setMinimumSize(200, 200)&#xff1b; window.setMaximumSize(500, 500) app QApplication(sys.argv)window QWidget()window.setWindowTitle("最小最大窗口")windo…

QWidget居中显示

须知 以下函数只要继承QWidget都可以使用.例如 QDialog, QPushButton( -v- 一个居中的”引爆按钮”)关于坐标问题: qt窗口坐标原点是在”左上角”的. 如图, (x2, y2)是我窗口的分辨率的一半无论目前我的窗口在什么位置,我只要把窗口原点设置为(x1, y1)就行了.所以目前我要获得…

QWidget事件消息

1、用户操作界面时会产生特定的事件消息。 API&#xff1a;显示和关闭事件、移动事件、调整大小、鼠标事件、键盘事件、焦点事件、拖拽事件、绘制事件、改变事件、右键菜单、输入法。 应用场景&#xff1a;当一个控件被触发了一个特定的行为时&#xff0c;就会调用特定的方法…

QWidget的isHidden和isVisible

文章目录 问题的出现QWidget的show()函数QWidget的isVisible和isHidden源码追溯 QWidget的isHidden和isVisible 问题的出现 最近在写代码的时候&#xff0c;出现了一个问题&#xff0c;我新建了两个类&#xff0c;分别是Chapter2和ConsecutiveCurtain // ConsecutiveCurta…

QWidget鼠标操作

1、设置鼠标形状&#xff1a;鼠标放置在不同控件上&#xff08;有不同功能时&#xff09;鼠标的形状是不一样的。 鼠标跟踪&#xff1a;鼠标在某一个控件上移动&#xff0c;则会向对应的控件对象发送一个消息&#xff0c;去调用具体的某一个方法&#xff0c;可以在方法里面监听…

QWidget旋转方法

参考链接&#xff1a;https://stackoverflow.com/questions/43388464/rotate-whole-qwidget-by-angle 说明&#xff1a; 本文实现方法本质是使用QGraphics三件套&#xff0c;即View、Scene、Item&#xff0c;将QWidget控件作为Item显示&#xff0c;从而实现角度控制的目的&…

QWidget设置背景图及圆角

在Qt开发过程中&#xff0c;QWidget是经常作为主窗体的父窗口&#xff0c;有时我们需要对主窗口设置背景&#xff0c;设置圆角以达到美观的效果&#xff0c;通常的有以下三种方法&#xff1a;qss, QPalette设置以及paintEvent绘制。下面介绍这三种方法。 背景设置介绍 方法一&a…

QWidget之adjustSize

from PyQt5.Qt import * import sys# 创建一个应用程序对象 app QApplication(sys.argv)window QWidget()label QLabel(window) label.setText(学无止境) label.move(100, 100) label.setStyleSheet(background-color:gray)def changeCao():tmp label.text()学无止境label.…

初识QWidget

初识QWidget 在Qt中QWidget是一个非常关键和重要的类&#xff0c;推荐初学Qt的同学们第一个学习此类 在Qt的帮助手册中我们搜索QWidget&#xff0c;可以看到下图的描述 通过帮助手册我们了解到如果想使用QWidget这个类&#xff0c;需要包含QWidget这个头文件&#xff0c;Qt特…

QWidget继承

查看QWidget的继承于哪个类 方法一 随便写一个类继承自己QWidget 按住Ctrl鼠标单击QWidget即可 方法二 print(QWidget.__base__)方法三 print(QWidget.mro()) 链条式的继承展示 enjoy

QWidget(长文)

一、描述 1、QWidget 是用户界面的原子&#xff1a;它从窗口系统接收鼠标、键盘和其他事件&#xff0c;并在屏幕上绘制自己的表示。每个小部件都是矩形的&#xff0c;它们按Z顺序排序。小部件由其父部件和它前面的小部件剪裁。 2、未嵌入父窗口小部件的 QWidget 称为窗口。通…

QWidget的使用

一、QWidget介绍 QWidget是用户操作的原子接口&#xff0c;它从窗口系统中接收鼠标&#xff0c;键盘以及其他事件&#xff0c;并绘制图形界面。QT提供的默认窗口基类只有QMainWindow、QWidget、和QDialog这三种&#xff0c;QMainWindow是带有菜单栏和工具栏的主窗口类&#xf…

QWidget

QWidget QWidget是容器组件&#xff0c;继承自QObject类和QPaintDevice类。能够绘制自己和处理用户输入&#xff0c;是QT中所有窗口组件类的父类&#xff0c;是所有窗口组件的抽象&#xff0c;每个窗口组件都是一个QWidget&#xff0c;QWidget类对象常用作父组件或顶级组件使用…

QT学习总结之QWidget详解

1、说明 QWidget类是所有用户界面对象的基类。 QWidget是用户界面的原子类。它接收鼠标、键盘和来自系统的其他事件&#xff0c;并在屏幕上将它们绘制出来。每个Widget都是矩形的&#xff0c;并按照Z-order&#xff08;Z轴&#xff09;进行排序。一个Widget夹在它的Parent和它…

VS2019安装与使用教程

VS2019安装与使用教程 可能有很多小伙伴们&#xff0c;知道VS2019这个软件&#xff0c;但是不知道怎么安装与使用&#xff0c;下面我将具体介绍VS2019的安装方法与创建我们自己的C项目以及如何运行自己编写的代码&#xff01; Visual Studio 2019(VS2019)简介 Microsoft Visual…

vs2017初学c++环境配置及使用教程

作为一个计算机小白, 初学c的时候使用了vs2017, 配置环境如下 如图所示, 可以实现c的基本操作. 在vs2017的版本中, 取消了win32这个选项, 所以直接选择新建空项目. 在解决方案资源管理器中, 于源文件处新建.cpp文件, 即可执行操作. 如果出现闪现的情况, 则右键点击解决方案资…