You need to enable JavaScript to run this app. 如何编写应用程序控制基于KMDF的PNP驱动?PCIe驱动通信咨询
优惠活动
大模型
产品
解决方案
定价
更多

如何编写应用程序控制基于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;
}

四、常见问题排查

  1. 找不到设备:先确认驱动端已经正确注册设备接口,可通过设备管理器查看设备属性的"设备实例路径"确认GUID是否匹配
  2. 打开设备失败:检查设备路径是否正确,驱动是否对打开权限有特殊要求
  3. IOCTL失败:确认IOCTL控制码定义和驱动端完全一致,缓冲区大小符合驱动要求

内容的提问来源于stack exchange,提问作者pcie_driver

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.18 17:06:07