如何编写应用程序控制基于KMDF的PNP驱动?PCIe驱动通信咨询
适配PNP KMDF驱动的上层应用开发方案
一、驱动端前置配置(必须先完成)
你需要先在KMDF驱动的EvtDriverDeviceAdd回调中注册自定义的设备接口GUID,只有注册后应用才能通过GUID找到你的设备,示例驱动代码逻辑:
// 自定义设备接口GUID,必须和应用端完全一致,可通过Visual Studio自带的GUID生成工具生成 DEFINE_GUID(GUID_DEVINTERFACE_MY_PCIE_CARD, 0x12345678, 0x1234, 0x1234, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34); NTSTATUS EvtDriverDeviceAdd(_In_ WDFDRIVER Driver, _Inout_ PWDFDEVICE_INIT DeviceInit) { // ... 省略创建设备的常规逻辑 ... WDFDEVICE device; NTSTATUS status = WdfDeviceCreate(&DeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &device); if (!NT_SUCCESS(status)) return status; // 注册设备接口,这一步是PNP驱动能被应用找到的核心 status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_MY_PCIE_CARD, NULL); if (!NT_SUCCESS(status)) return status; // ... 省略其他驱动初始化逻辑 ... return STATUS_SUCCESS; }
二、应用端核心修改点
你原有的非PNP适配代码存在两个核心问题:
- 用固定符号名调用
CreateFile:PNP驱动不会生成固定的符号链接,必须通过GUID动态获取设备路径 - 保留了SCM驱动管理逻辑:PNP驱动由系统即插即用管理器自动加载/卸载,无需应用通过服务控制管理器启停、卸载驱动,相关
ManageDriver/StartDriver/StopDriver/RemoveDriver逻辑可全部删除
三、完整PNP适配应用骨架
编译依赖
需要包含头文件setupapi.h,链接静态库setupapi.lib
完整代码
#include <windows.h> #include <stdio.h> #include <setupapi.h> #include <strsafe.h> // 和驱动端完全一致的设备接口GUID,替换成你自己的 DEFINE_GUID(GUID_DEVINTERFACE_MY_PCIE_CARD, 0x12345678, 0x1234, 0x1234, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34); // 你的IOCTL控制码,保持和驱动端一致 #define Spw_PCIe_IOCTL_IN_BUFFERED CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define Spw_PCIe_IOCTL_OUT_BUFFERED CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) // 通过GUID获取第一个匹配的设备路径 BOOL GetFirstDevicePath(_In_ LPCGUID InterfaceGuid, _Out_writes_(PathSize) PCHAR DevicePath, _In_ DWORD PathSize) { BOOL result = FALSE; HDEVINFO hDevInfo = SetupDiGetClassDevsA(InterfaceGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (hDevInfo == INVALID_HANDLE_VALUE) return FALSE; SP_DEVICE_INTERFACE_DETAIL_DATA_A *detailData = NULL; SP_DEVICE_INTERFACE_DATA interfaceData = {0}; interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); // 枚举第一个匹配的设备接口 if (SetupDiEnumDeviceInterfaces(hDevInfo, NULL, InterfaceGuid, 0, &interfaceData)) { DWORD requiredSize = 0; // 第一次调用获取所需缓冲区大小 SetupDiGetDeviceInterfaceDetailA(hDevInfo, &interfaceData, NULL, 0, &requiredSize, NULL); if (GetLastError() == ERROR_INSUFFICIENT_BUFFER && requiredSize <= PathSize) { detailData = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*)malloc(requiredSize); if (detailData) { detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); if (SetupDiGetDeviceInterfaceDetailA(hDevInfo, &interfaceData, detailData, requiredSize, NULL, NULL)) { StringCchCopyA(DevicePath, PathSize, detailData->DevicePath); result = TRUE; } free(detailData); } } } SetupDiDestroyDeviceInfoList(hDevInfo); return result; } // 你原有DoIoctls逻辑基本无需修改,直接复用即可 VOID DoIoctls(HANDLE hDevice) { char OutputBuffer[100]; char InputBuffer[200]; BOOL bRc; ULONG bytesReturned; printf("\nInputBuffer Pointer = %p, BufLength = %zu\n", InputBuffer, sizeof(InputBuffer)); printf("OutputBuffer Pointer = %p BufLength = %zu\n", OutputBuffer, sizeof(OutputBuffer)); // METHOD_IN_DIRECT测试 printf("\nCalling DeviceIoControl METHOD_IN_DIRECT\n"); if (FAILED(StringCchCopyA(InputBuffer, sizeof(InputBuffer), "this String is from User Application; using METHOD_IN_DIRECT"))) { return; } if (FAILED(StringCchCopyA(OutputBuffer, sizeof(OutputBuffer), "This String is from User Application in OutBuffer; using METHOD_IN_DIRECT"))) { return; } bRc = DeviceIoControl(hDevice, Spw_PCIe_IOCTL_IN_BUFFERED, InputBuffer, (DWORD)strlen(InputBuffer) + 1, OutputBuffer, sizeof(OutputBuffer), &bytesReturned, NULL ); if (!bRc) { printf("Error in METHOD_IN_DIRECT : %d \n", GetLastError()); return; } printf(" Number of bytes transfered: %d\n", bytesReturned); // METHOD_OUT_DIRECT测试 printf("\nCalling DeviceIoControl METHOD_OUT_DIRECT\n"); if (FAILED(StringCchCopyA(InputBuffer, sizeof(InputBuffer), "this String is from User Application; using METHOD_OUT_DIRECT"))) { return; } memset(OutputBuffer, 0, sizeof(OutputBuffer)); bRc = DeviceIoControl(hDevice, Spw_PCIe_IOCTL_OUT_BUFFERED, InputBuffer, (DWORD)strlen(InputBuffer) + 1, OutputBuffer, sizeof(OutputBuffer), &bytesReturned, NULL ); if (!bRc) { printf("Error in METHOD_OUT_DIRECT : %d", GetLastError()); return; } printf(" OutBuffer (%d): %s\n", bytesReturned, OutputBuffer); return; } int __cdecl main(_In_ ULONG argc, _In_reads_(argc) PCHAR argv[]) { CHAR devicePath[MAX_PATH] = {0}; HANDLE hDevice = INVALID_HANDLE_VALUE; printf("main start\n"); // 1. 通过GUID获取设备路径 if (!GetFirstDevicePath(&GUID_DEVINTERFACE_MY_PCIE_CARD, devicePath, MAX_PATH)) { printf("找不到设备,请确认驱动已正确安装且设备已插入,错误码:%d\n", GetLastError()); getchar(); return 1; } printf("找到设备,路径:%s\n", devicePath); // 2. 打开设备,注意打开参数用OPEN_EXISTING,不要用CREATE_ALWAYS hDevice = CreateFileA(devicePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hDevice == INVALID_HANDLE_VALUE) { printf("打开设备失败,错误码:%d\n", GetLastError()); getchar(); return 1; } printf("按下回车开始IOCTL测试\n"); getchar(); // 3. 调用IOCTL逻辑,和原有逻辑一致 DoIoctls(hDevice); // 4. 关闭设备句柄即可,无需操作驱动卸载 CloseHandle(hDevice); printf("测试完成,按下回车退出\n"); getchar(); return 0; }
四、常见问题排查
- 找不到设备:先确认驱动端已经正确注册设备接口,可通过设备管理器查看设备属性的"设备实例路径"确认GUID是否匹配
- 打开设备失败:检查设备路径是否正确,驱动是否对打开权限有特殊要求
- IOCTL失败:确认IOCTL控制码定义和驱动端完全一致,缓冲区大小符合驱动要求
内容的提问来源于stack exchange,提问作者pcie_driver
相关产品推荐
相关产品推荐

