当前位置: 首页 > news >正文

UMDF驱动开发入门:创建虚拟设备,从安装到I/O交互全解析

这篇博客详细介绍了UMDF驱动的基本概念、生命周期、代码实现和应用交互,适合初学者入门

• 目的:创建一个“虚拟设备”(软件模拟的设备),让用户模式应用程序(比如你的C#或C++程序)能与之“对话”。它不控制真实硬件(如USB设备),而是演示驱动的基本流程:加载、创建设备、处理请求。
• 为什么用UMDF? UMDF让驱动运行在用户模式(非内核),更安全稳定。内核驱动(如KMDF)风险高,UMDF适合简单任务。
• 示例功能:驱动加载后,注册一个设备接口。应用能打开设备、发送“控制请求”(如自定义命令),驱动简单响应(目前只返回成功)。你可以扩展它做监控、数据处理等。
• 文件结构(从ReadMe.txt):
• Device.c & Device.h:设备创建和上下文。

• Driver.c:驱动入口和全局回调。
• Queue.c:I/O请求队列处理。
• Public.h:共享定义(如设备接口GUID)。
• UMDFTest.inf:安装文件(定义硬件ID、服务等)。
• 其他:跟踪日志、项目配置。

驱动的生命周期(从加载到运行)

驱动像“服务”一样工作。安装后,它不直接运行,而是等系统调用。
• 入口点在哪里?
• 全局入口:DriverEntry 函数(在Driver.c)。这是驱动的“起点”,系统加载DLL时第一个调用它。
• 它初始化WDF框架、注册回调(如设备添加事件)、设置跟踪日志。
• 如果失败,驱动不加载。
• 设备入口:UMDFTestEvtDeviceAdd(也在Driver.c),PnP(即插即用)系统添加设备时调用。它调用UMDFTestCreateDevice(在Device.c)创建设备对象。
• 安装后:驱动作为UMDF服务运行在WUDFHost.exe进程中(用户模式主机)。系统启动它时,调用DriverEntry,然后创建设备。
• 安装过程(手动做的):
• 用pnputil /add-driver UMDFExample.inf /install安装INF文件。 //安装后出现在system devices下面名字叫做UMDFExampledevices
• 系统注册设备(硬件ID Root\UMDFExample),加载DLL到WUDFHost。
• 设备出现在设备管理器(“UMDFExample Device”),状态“正常”。 //没找到

  1. 其他程序怎么用这个驱动?入口点在哪里?
    驱动不是“程序”,它是系统服务。其他程序(应用)通过设备接口与之交互,像打开文件一样。
    • 入口点main(应用侧):
    • 设备接口GUID:GUID_DEVINTERFACE_UMDFExample(在Public.h定义,值是{8d385c4d-4fa8-413c-9307-a8fa3e768390})。这是“门牌号”,应用用它找到设备。
    • 打开设备:用CreateFile API(Windows函数),路径是\.\GUID字符串(如\.{8d385c4d-4fa8-413c-9307-a8fa3e768390})。成功返回句柄(handle),像文件句柄。
    • 发送消息: 使用DeviceIoControl API(windows函数),成功的话会返回DeviceIoControl succeeded 返回的实际内容如下:bytesReturned,

    BOOL result = DeviceIoControl(hDevice, // 1. 设备句柄
    IOCTL_UMDFTEST_HELLO,  // 2. 控制码
    (LPVOID)inputBuffer,   // 3. 输入缓冲区指针
    inputSize,             // 4. 输入缓冲区大小
    outputBuffer,          // 5. 输出缓冲区指针
    outputSize,            // 6. 输出缓冲区大小
    &bytesReturned,        // 7. 接收实际返回字节数的变量指针
    nullptr);              // 8. 用于异步操作,此处为同步
    

    3.驱动层先不细讲,在驱动里面UMDFTestEvtIoDeviceControl是一个回调函数,用于处理来自用户模式应用程序的 设备控制请求(IOCTL)。当你的应用调用 DeviceIoControl 时,UMDF 框架会自动调用这个函数来处理请求。再博客的最后会写个例子讲解这个函数详细的代码内容

    VOID UMDFTestEvtIoDeviceControl( _In_ WDFQUEUE Queue, // 队列句柄(框架管理的 I/O 队列) 
    _In_ WDFREQUEST Request, // 请求句柄(包含输入/输出缓冲区和请求详情) 
    _In_ size_t OutputBufferLength, // 输出缓冲区大小(字节) 
    _In_ size_t InputBufferLength, // 输入缓冲区大小(字节) 
    _In_ ULONG IoControlCode // IOCTL 控制码(唯一标识请求类型,如 IOCTL_UMDFTEST_HELLO)
    )
    

• 返回类型:VOID(无返回值,因为处理结果通过 Request 对象传递)。
• 参数:
• Queue:指向队列对象的句柄(你不需要直接操作它)。
• Request:核心对象,封装了请求的所有数据(输入缓冲区、输出缓冲区等)。这是你处理数据的入口。
• OutputBufferLength / InputBufferLength:缓冲区大小,用于安全检查(防止缓冲区溢出)。
• IoControlCode:一个数字,标识具体的 IOCTL 类型(例如,你的代码中定义的 IOCTL_UMDFTEST_HELLO)。
• In 注解:SAL (Source Annotation Language) 注解,表示这些参数是输入的(只读)。

检查是否还有 pnputil /enum-drivers | findstr /i UMDFTest

我们讲解一下exe要加载驱动的时候传入的参数和驱动的inf的对应关系

SW_DEVICE_CREATE_INFO createInfo = { 0 };
createInfo.cbSize = sizeof(SW_DEVICE_CREATE_INFO);
createInfo.pszInstanceId = L"umdftest";  // 实例ID   
createInfo.pszzHardwareIds = L"Root\\UMDFTest\0\0";    // 硬件ID,与INF文件匹配
createInfo.pszzCompatibleIds = nullptr;  // 兼容ID,无需设置
createInfo.pContainerId = nullptr;               // 容器ID
createInfo.CapabilityFlags = SWDeviceCapabilitiesDriverRequired;  // 能力标志,需要驱动程序
createInfo.pszDeviceDescription = L"UMDF Test Device";   // 设备描述
createInfo.pszDeviceLocation = nullptr;          // 设备位置
createInfo.pSecurityDescriptor = nullptr;        // 安全描述符HRESULT hr = SwDeviceCreate(L"umdftest", L"HTREE\\ROOT\\0", &createInfo, 0, nullptr, SwDeviceCreatedCallback, nullptr, &g_SwDevice);

• createInfo.pszInstanceId(实例ID,如L"umdfexample"):这不是直接对应INF中的某个字段,而是软件设备的唯一标识符,用于区分多个实例。它与INF中的硬件ID(Root\UMDFExample)配合使用,但INF本身不定义实例ID。你可以自定义,只要在代码中保持一致即可。
• SwDeviceCreate的第一个参数(pszEnumeratorName,如L"umdfexample"):这是枚举器名称,通常设置为与pszInstanceId相同的值,用于标识创建设备的枚举器。它也不直接对应INF中的特定字段,而是系统内部使用的标识符。

每次编译并修改代码后重复安装INF时,系统会将每个INF作为独立的驱动包添加,导致多个oemXX.inf文件(例如oem40.inf和oem54.inf)。即使硬件ID相同,pnputil也会创建新包,而不是覆盖旧的。这是因为驱动包是基于INF内容的哈希值管理的,如果内容不同(即使硬件ID相同),会被视为新包。
对于软件设备(如SWD\umdfexample\umdfexample),可能会出现多个设备实例或冲突。
正常更新驱动的处理步骤:

  1. 停止相关应用程序和服务:确保没有进程使用驱动。
  2. 删除旧驱动包:
    • 运行 pnputil /enum-drivers 查看所有驱动包。
    • 删除旧的:pnputil /delete-driver oem40.inf(替换为实际名称)。
  3. 卸载设备实例(如果存在):
    • 使用设备管理器:找到设备(例如“UMDF Test Device”),右键卸载。
    • 或使用命令:pnputil /remove-device "SWD\umdfexample\umdfexample"(替换为实际设备ID)。
  4. 安装新驱动:
    • 复制新编译的INF和DLL到目录。
    • 运行 pnputil /add-driver UMDFExample.inf /install。
  5. 重启系统(如果需要):有时软件设备需要重启才能生效。
  6. 测试:运行应用程序验证新代码。
    如果只想强制更新而不删除,可以尝试 pnputil /add-driver UMDFExample.inf /install /force,但这可能不总是可靠。建议始终删除旧包以避免冲突。

驱动层代码详细分析

1.INF安装阶段(驱动注册)

我们从编译完成之后的安装开始:

•     首先命令执行:pnputil /add-driver UMDFExample.inf /install 将INF文件添加到系统驱动存储中,生成一个oemXX.inf文件(例如oem54.inf)。

• 驱动代码经历:此时驱动代码(UMDFExample.dll)尚未加载。INF只是注册了驱动包,包括硬件ID(Root\UMDFExample)、服务名(UMDFExample)和DLL路径。系统知道“如果有匹配的硬件ID,就加载这个驱动”。
• 结果:驱动包已安装,但没有设备实例。驱动代码静止。

2.设备枚举与发现阶段(PnP触发)

• 触发条件:当应用程序运行 SwDeviceCreate(在UMDFConsole.cpp中)时,系统创建软件设备实例(SWD\umdfexample\umdfexample),硬件ID为Root\UMDFExample。
• PnP管理器动作:PnP检测到新设备,匹配INF中的硬件ID,决定加载对应的驱动。
• 驱动代码经历:WUDFHost进程启动(如果未运行)。系统启动WUDFHost.exe(用户模式主机进程),并加载UMDFExample.dll到该进程中。
• DriverEntry调用:这是驱动的入口点(在Driver.c中)。DriverEntry 初始化WDF框架,注册设备创建回调(UMDFExampleCreateDevice)。
• 此时,驱动开始“活起来”,但设备对象尚未创建。

3. 设备创建与初始化阶段

• UMDFExampleCreateDevice(在Device.c中):创建WDFDEVICE对象,初始化设备上下文(deviceContext->PrivateDeviceData = 0),并创建设备接口(WdfDeviceCreateDeviceInterface,使用GUID_DEVINTERFACE_UMDFExample)。这允许应用程序通过SetupAPI找到设备路径。
• UMDFExampleQueueInitialize(在Queue.c中):创建I/O队列(WDFQUEUE),配置并行处理,注册事件回调(如EvtIoDeviceControl和EvtIoStop)。
• 设备现在“可用”,应用程序可以打开设备句柄(CreateFile)。

4. I/O请求处理阶段(运行时)

• 触发条件:应用程序调用 DeviceIoControl 发送IOCTL请求(例如IOCTL_UMDFTEST_HELLO)。
• 驱动代码经历:
• 请求进入队列,调用 UMDFExampleEvtIoDeviceControl(在Queue.c中)。
• 检查IoControlCode(如果是IOCTL_UMDFTEST_HELLO)。
• 获取输入/输出缓冲区。
• 处理逻辑:拼接字符串("hi client this as drive,i received message as " + 输入),设置bytesReturned。
• 调用 WdfRequestCompleteWithInformation 完成请求,返回数据给应用程序。
• 如果不支持的IOCTL,调用 WdfRequestComplete 返回STATUS_INVALID_DEVICE_REQUEST。
• 其他事件如EvtIoStop在电源管理时调用(目前返回,无额外处理)。

5.设备移除与卸载阶段

• 触发条件:应用程序调用 SwDeviceClose,或系统重启/卸载驱动。
• 驱动代码经历:
• PnP调用设备移除回调(如果有),清理资源。
• WUDFHost进程卸载DLL,驱动对象销毁。
• 如果删除驱动包(pnputil /delete-driver),INF被移除,下次需要重新安装。

关键注意事项

• 用户模式特性:UMDF运行在WUDFHost中,崩溃不会蓝屏,但权限受限(不能直接访问硬件)。
• 调试:使用WDF Verifier或ETW跟踪(TraceEvents)查看日志。
• 错误处理:如果SwDeviceCreate失败(权限问题),驱动不会加载。确保管理员运行应用程序。
• 生命周期总结:从安装到卸载,驱动代码只在设备存在时活跃。软件设备依赖应用程序创建/销毁。

UMDFExampleEvtIoDeviceControl 函数的详细讲解

当应用程序(UMDFConsole.cpp)调用 DeviceIoControl 发送字符串 "Hello from App" 时,驱动端(UMDFExample.dll,在WUDFHost进程中运行)会经历以下步骤处理请求。假设IOCTL码为IOCTL_UMDFTEST_HELLO(匹配驱动中的检查):###

我自己的代码如下:

VOID
UMDFExampleEvtIoDeviceControl(_In_ WDFQUEUE Queue,_In_ WDFREQUEST Request,_In_ size_t OutputBufferLength,_In_ size_t InputBufferLength,_In_ ULONG IoControlCode)
/*++Routine Description:This event is invoked when the framework receives IRP_MJ_DEVICE_CONTROL request.Arguments:Queue -  Handle to the framework queue object that is associated with theI/O request.Request - Handle to a framework request object.OutputBufferLength - Size of the output buffer in bytesInputBufferLength - Size of the input buffer in bytesIoControlCode - I/O control code.Return Value:VOID--*/
{if (IoControlCode == IOCTL_UMDFTEST_HELLO) {  // 检查控制码PVOID inputBuffer = NULL;PVOID outputBuffer = NULL;size_t bytesReturned = 0;// 获取缓冲区if (NT_SUCCESS(WdfRequestRetrieveInputBuffer(Request, 0, &inputBuffer, NULL)) &&NT_SUCCESS(WdfRequestRetrieveOutputBuffer(Request, 0, &outputBuffer, NULL))) {// 处理:拼接字符串并返回const char* prefix = "hi client this as drive,i received message as ";size_t prefixLen = strlen(prefix);size_t inputLen = InputBufferLength > 0 ? InputBufferLength - 1 : 0; // 假设输入是null-terminated字符串size_t totalLen = prefixLen + inputLen + 1; // +1 for null terminatorif (OutputBufferLength >= totalLen) {memcpy(outputBuffer, prefix, prefixLen);if (inputLen > 0) {memcpy((char*)outputBuffer + prefixLen, inputBuffer, inputLen);}((char*)outputBuffer)[totalLen - 1] = '\0'; // null terminatebytesReturned = totalLen;} else {// 输出缓冲区太小,返回错误WdfRequestComplete(Request, STATUS_BUFFER_TOO_SMALL);return;}}TraceEvents(TRACE_LEVEL_INFORMATION,TRACE_QUEUE,"%!FUNC! Queue 0x%p, Request 0x%p OutputBufferLength %d InputBufferLength %d IoControlCode %d",Queue, Request, (int)OutputBufferLength, (int)InputBufferLength, IoControlCode);// 完成请求并指定返回字节数WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, bytesReturned);}else {WdfRequestComplete(Request, STATUS_INVALID_DEVICE_REQUEST);  // 不支持的 IOCTL}return;
}

1.请求到达驱动

• DeviceIoControl 通过设备句柄发送IRP(I/O Request Packet)到内核,内核转发给UMDF框架。
• UMDF将请求分发到I/O队列(在Queue.c的UMDFExampleQueueInitialize中创建)。
• 调用 UMDFExampleEvtIoDeviceControl 回调函数(在Queue.c中)。

2.检查IOCTL码

• 驱动检查 IoControlCode 是否等于 IOCTL_UMDFTEST_HELLO(定义在Public.h中)。
• 如果匹配,继续处理;否则,返回 STATUS_INVALID_DEVICE_REQUEST。

3.获取输入/输出缓冲区

• 调用 WdfRequestRetrieveInputBuffer 获取输入缓冲区指针(包含 "Hello from App",长度为 InputBufferLength,包括null终止符)。
• 调用 WdfRequestRetrieveOutputBuffer 获取输出缓冲区指针(应用程序提供的256字节缓冲区)。

4.处理逻辑(拼接字符串)

• 计算前缀:"hi client this as drive,i received message as "(长度约50字节)。
• 输入长度:InputBufferLength - 1(假设null-terminated,去掉null)。
• 总长度:前缀 + 输入 + 1(null终止符)。
• 检查输出缓冲区是否足够大(OutputBufferLength >= totalLen)。
• 如果足够:
• 使用 memcpy 复制前缀到输出缓冲区。
• 复制输入字符串到输出缓冲区后面。
• 添加null终止符。
• 设置 bytesReturned = totalLen。
• 如果不够,返回 STATUS_BUFFER_TOO_SMALL。

5.完成请求

• 调用 WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, bytesReturned),将拼接后的字符串(例如 "hi client this as drive,i received message as Hello from App")返回给应用程序。
• 应用程序在 outputBuffer 中接收数据,bytesReturned 指示实际返回字节数。

关键点

• 输入:"Hello from App"(字符串,驱动假设null-terminated)。
• 输出:拼接后的字符串,长度取决于输入。
• 错误处理:如果缓冲区小,返回错误;不支持IOCTL也返回错误。
• 日志:TraceEvents 记录调试信息(队列、请求、缓冲区大小等)。

http://www.hskmm.com/?act=detail&tid=35969

相关文章:

  • 2025年AI优化公司电话推荐:十家可验证服务商沟通备忘
  • 2025深圳离婚律所电话推荐:家理律所福田诺德中心25楼
  • 1242. 多线程网页爬虫
  • 使用SpringBoot + Thymeleaf + MyBatisPlus实现一个简单的书籍管理系统-demo2
  • 2025年深圳离婚律所电话推荐:家理福田诺德中心婚姻家事专线
  • 2025年超声波清洗机厂家电话推荐:广东洁泰超声设备有限公司
  • CentOS7源码安装erlang26没有默认安装JIT模块
  • 2025年超声波清洗机厂家电话推荐:广东洁泰设备技术沉淀深
  • 2025年AI优化公司电话推荐:十家可验证团队直连方式汇总
  • 2025年润滑油厂家权威推荐榜:工业润滑油,汽车润滑油,发动机润滑油,甲醇发动机润滑油,全合成润滑油,长效发动机润滑油品牌深度解析
  • 软件工程结对项目-小学四则运算题目生成与判题程序
  • 2025年上海装修公司电话推荐:极家与俞润本土双选参考
  • 2025上海装修公司电话推荐:极家与俞润实测对比。
  • 教科书上令人触动的话
  • 生日
  • 2025 年防淹门源头厂家最新推荐排行榜权威发布,含地铁 / 防洪 / 地下通道专用款,15 项专利 + 央视报道品牌领衔
  • 2025年防静电/劳保/国网/餐厅/工厂/电工/防酸碱/电力/车间/航空/员工广告衫,文化衫/t恤/polo衫/冲锋衣厂家推荐排行榜
  • 一文带你掌握Visual Studio中集成的git功能
  • 【往届已检索!稳定检索】2025年第二届人工智能、数字媒体技术与交互设计国际学术会议(ICADI 2025)
  • 苹果最折腾的功能!iPhone快捷指令分享
  • 单提交智能评审上线!用云效精准定位复杂 MR 代码问题
  • GitLab小坑:remote: GitLab: You are not allowed to create protected branches on this project.
  • ubuntu安装nvidia驱动 - Leonardo
  • 2025 年少儿英语品牌口碑排行榜最新发布:欧美外教 + 原版教材甄选,含最新推荐及靠谱选择指南
  • 2025年滑石粉厂家推荐排行榜,纳米级滑石粉,工业级滑石粉,黑色滑石粉,高白滑石粉,化妆品级滑石粉,食品级滑石粉,表面改性滑石粉,大片径比滑石粉,低收缩率滑石粉,高填充母粒滑石粉
  • 自动化智能体与测试用例生成
  • 设置某些网站不走代理
  • 2025 年试验箱厂家最新推荐排行榜:涵盖高低温 / 恒温恒湿 / 冷热冲击等设备,精选实力厂商助力企业选购
  • 2025年除尘设备厂家权威推荐榜:除尘器,脉冲除尘器,中央脉冲除尘器,工业除尘器源头企业综合测评与选购指南
  • 2025 年国内充电桩厂家最新推荐排行榜:技术、安全与服务全方位对比的优质供应商优选榜单