Gogs il y a 3 semaines
Parent
commit
a0992d376e

+ 7 - 3
common/CommKitIoctl.h

@@ -34,12 +34,16 @@
     CTL_CODE(COMMKIT_DEVICE_TYPE, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
 
 // SetWrite_Data 调用:向指定串口下发写 IRP(数据由驱动透传给下层串口)
+// 使用 METHOD_BUFFERED:COMMKIT_DATA_REQUEST + 变长 Data 在同一个 SystemBuffer 中传递,
+// 驱动通过 WdfRequestRetrieveInputBuffer 即可获取完整数据(含柔性数组 Data[])
 #define IOCTL_COMMKIT_WRITE_PORT \
-    CTL_CODE(COMMKIT_DEVICE_TYPE, 0x803, METHOD_IN_DIRECT, FILE_WRITE_DATA)
+    CTL_CODE(COMMKIT_DEVICE_TYPE, 0x803, METHOD_BUFFERED, FILE_WRITE_DATA)
 
 // SetRead_Data 调用:从指定串口发起异步读请求(结果通过回调 OP_READ 上抛)
+// 使用 METHOD_BUFFERED:与驱动访问方式一致,避免 METHOD_IN_DIRECT 语义下
+// SystemBuffer 不复制回用户缓冲导致数据丢失
 #define IOCTL_COMMKIT_READ_PORT \
-    CTL_CODE(COMMKIT_DEVICE_TYPE, 0x804, METHOD_IN_DIRECT, FILE_READ_DATA)
+    CTL_CODE(COMMKIT_DEVICE_TYPE, 0x804, METHOD_BUFFERED, FILE_READ_DATA)
 
 // DLL 工作线程调用:从驱动批量读取捕获的事件(阻塞或返回 0 条)
 #define IOCTL_COMMKIT_READ_EVENTS \
@@ -76,7 +80,7 @@ typedef struct _COMMKIT_PORT_REQUEST {
 } COMMKIT_PORT_REQUEST, *PCOMMKIT_PORT_REQUEST;
 #pragma pack(pop)
 
-// IOCTL_COMMKIT_WRITE_PORT / READ_PORT 输入参数(数据通过 METHOD_IN_DIRECT 的 pInputBuffer 传递
+// IOCTL_COMMKIT_WRITE_PORT / READ_PORT 输入参数(METHOD_BUFFERED,Data 为变长柔性数组
 #pragma pack(push, 1)
 typedef struct _COMMKIT_DATA_REQUEST {
     UINT32 ComNumber;                    // 串口编号

+ 2 - 2
dll/DriverClient.cpp

@@ -95,7 +95,7 @@ bool DriverClient::WritePort(DWORD com_number, const void* data, int len) {
         return false;
     }
 
-    // METHOD_IN_DIRECT:输入缓冲为 COMMKIT_DATA_REQUEST + 数据
+    // METHOD_BUFFERED:输入缓冲为 COMMKIT_DATA_REQUEST + 数据
     // 总长度 = 固定头部 + 数据长度(Data 字段为 1,已含 1 字节)
     DWORD total_size = sizeof(COMMKIT_DATA_REQUEST) - 1 + static_cast<DWORD>(len);
     std::vector<BYTE> buf(total_size);
@@ -115,7 +115,7 @@ bool DriverClient::ReadPort(DWORD com_number, const void* data, int len) {
         return false;
     }
 
-    // METHOD_IN_DIRECT:发起读请求,data 仅作为附加参数(当前实现忽略)
+    // METHOD_BUFFERED:发起读请求,数据通过 OP_READ 事件回调上抛
     DWORD total_size = sizeof(COMMKIT_DATA_REQUEST) - 1 + static_cast<DWORD>(len);
     std::vector<BYTE> buf(total_size);
 

+ 68 - 6
driver/ClientConnection.cpp

@@ -3,6 +3,7 @@
 #include <wdf.h>
 #include "ClientConnection.h"
 #include "EventRingBuffer.h"
+#include "SerialFilter.h"  // g_sequence_counter
 #include "../common/CommKitIoctl.h"
 #include "../common/CommKitEvents.h"
 
@@ -112,10 +113,28 @@ NTSTATUS ClientConnection::HandleRegisterCallback() {
 }
 
 NTSTATUS ClientConnection::HandleAttachPort(ULONG com_number) {
+    // 拒绝 com_number==0:EvtDevicePrepareHardware 解析失败时 ComNumber 保持 0,
+    // 若允许 attach 会误匹配第一个未解析出编号的设备
+    if (com_number == 0) {
+        DbgPrint("[CommModifyKit] HandleAttachPort: com_number=0 rejected\n");
+        return STATUS_INVALID_PARAMETER;
+    }
+
     PDEVICE_CONTEXT ctx = FindPortContext(com_number);
     if (!ctx) {
+        DbgPrint("[CommModifyKit] HandleAttachPort: COM%u NOT in ports_table (count=%u)\n",
+                 com_number, ports_count_);
+        // 打印 ports_table_ 里所有已注册的 COM 编号,便于诊断
+        KIRQL old_irql;
+        KeAcquireSpinLock(&ports_lock_, &old_irql);
+        for (ULONG i = 0; i < ports_count_; ++i) {
+            DbgPrint("[CommModifyKit]   ports_table[%u].ComNumber=%u\n",
+                     i, ports_table_[i].ComNumber);
+        }
+        KeReleaseSpinLock(&ports_lock_, old_irql);
         return STATUS_DEVICE_DOES_NOT_EXIST;
     }
+    DbgPrint("[CommModifyKit] HandleAttachPort: COM%u found in ports_table\n", com_number);
 
     // 如果未分配环形缓冲,先分配
     if (!ctx->RingBuffer) {
@@ -135,10 +154,9 @@ NTSTATUS ClientConnection::HandleAttachPort(ULONG com_number) {
         ctx->RingBuffer = ring;
     }
 
-    // 推入 OP_OPEN 事件(序列号由全局计数器递增)
+    // 推入 OP_OPEN 事件(使用全局共享序列号,保证所有事件类型 Sequence 唯一递增)
     EventRingBuffer* ring = (EventRingBuffer*)ctx->RingBuffer;
-    static LONG seq = 0;
-    LONG cur = InterlockedIncrement(&seq);
+    LONG cur = InterlockedIncrement(&g_sequence_counter);
     UINT64 timestamp = KeQueryInterruptTime();  // 100ns 单位
     ring->Push((ULONG)cur, timestamp, com_number, COMMKIT_OP_OPEN, 0, nullptr);
 
@@ -154,11 +172,10 @@ NTSTATUS ClientConnection::HandleDetachPort(ULONG com_number) {
 
     ctx->MonitoringEnabled = FALSE;
 
-    // 推入 OP_CLOSE 事件
+    // 推入 OP_CLOSE 事件(使用全局共享序列号)
     if (ctx->RingBuffer) {
         EventRingBuffer* ring = (EventRingBuffer*)ctx->RingBuffer;
-        static LONG seq2 = 0;
-        LONG cur = InterlockedIncrement(&seq2);
+        LONG cur = InterlockedIncrement(&g_sequence_counter);
         UINT64 timestamp = KeQueryInterruptTime();
         ring->Push((ULONG)cur, timestamp, com_number, COMMKIT_OP_CLOSE, 0, nullptr);
     }
@@ -188,6 +205,20 @@ NTSTATUS ClientConnection::HandleWritePort(ULONG com_number, PVOID data, ULONG l
         status = io_status.Status;
     }
 
+    // 注:此 IRP 直接发给 LowerDevice,绕过 SerialFilter::DispatchWrite,
+    // 不会触发 OnWriteComplete 捕获。需手动将写入数据 Push 为 OP_WRITE 事件,
+    // 否则监控端看不到注入的写流量(监控数据不完整)
+    if (NT_SUCCESS(status) && ctx->MonitoringEnabled && ctx->RingBuffer) {
+        ULONG written = (ULONG)io_status.Information;
+        if (written > 0 && data) {
+            LONG seq = InterlockedIncrement(&g_sequence_counter);
+            UINT64 timestamp = KeQueryInterruptTime();
+            EventRingBuffer* ring = (EventRingBuffer*)ctx->RingBuffer;
+            ring->Push((ULONG)seq, timestamp, com_number, COMMKIT_OP_WRITE,
+                        written, data);
+        }
+    }
+
     return status;
 }
 
@@ -214,6 +245,20 @@ NTSTATUS ClientConnection::HandleReadPort(ULONG com_number, PVOID data, ULONG le
         status = io_status.Status;
     }
 
+    // 注:此 IRP 直接发给 LowerDevice,绕过 SerialFilter::DispatchRead,
+    // 不会触发 OnReadComplete 捕获。需手动将读取数据 Push 为 OP_READ 事件,
+    // 否则读取的数据既不返回调用者(METHOD_BUFFERED + info=0)也不进 RingBuffer,完全丢失
+    if (NT_SUCCESS(status) && ctx->MonitoringEnabled && ctx->RingBuffer) {
+        ULONG read_bytes = (ULONG)io_status.Information;
+        if (read_bytes > 0 && data) {
+            LONG seq = InterlockedIncrement(&g_sequence_counter);
+            UINT64 timestamp = KeQueryInterruptTime();
+            EventRingBuffer* ring = (EventRingBuffer*)ctx->RingBuffer;
+            ring->Push((ULONG)seq, timestamp, com_number, COMMKIT_OP_READ,
+                        read_bytes, data);
+        }
+    }
+
     return status;
 }
 
@@ -233,6 +278,7 @@ NTSTATUS ClientConnection::HandleReadEvents(PVOID out_buf, ULONG out_size, PULON
     KIRQL old_irql;
     KeAcquireSpinLock(&ports_lock_, &old_irql);
 
+    // 按端口顺序弹出事件到输出缓冲
     for (ULONG i = 0; i < ports_count_ && total_popped < max_count; ++i) {
         PDEVICE_CONTEXT ctx = ports_table_[i].Context;
         if (!ctx || !ctx->RingBuffer || !ctx->MonitoringEnabled) {
@@ -245,6 +291,22 @@ NTSTATUS ClientConnection::HandleReadEvents(PVOID out_buf, ULONG out_size, PULON
 
     KeReleaseSpinLock(&ports_lock_, old_irql);
 
+    // 弹出后按 Sequence 升序排序(锁外执行,不持自旋锁做 O(n²) 操作)
+    // 各端口 RingBuffer 内部 Sequence 是递增的,但跨端口弹出后顺序被打乱
+    // (如 port0 的 seq=100-109 可能排在 port1 的 seq=95-99 之前)
+    // 使用插入排序:事件批量小(通常 ≤16),O(n²) 开销可忽略
+    if (total_popped > 1) {
+        for (ULONG i = 1; i < total_popped; ++i) {
+            COMMKIT_EVENT key = events[i];
+            LONG j = (LONG)i - 1;
+            while (j >= 0 && events[j].Sequence > key.Sequence) {
+                events[j + 1] = events[j];
+                j--;
+            }
+            events[j + 1] = key;
+        }
+    }
+
     *returned = total_popped * sizeof(COMMKIT_EVENT);
     return STATUS_SUCCESS;
 }

+ 86 - 1
driver/DriverEntry.cpp

@@ -5,6 +5,8 @@
 //      当 PnP Manager 创建串口 PDO 时,调用 EvtDeviceAdd attach 过滤设备
 #include <ntddk.h>
 #include <wdf.h>
+#include <initguid.h>  // 必须在包含定义 GUID 的头文件之前
+#include <ntddser.h>   // GUID_DEVINTERFACE_COMPORT
 #include "DeviceContext.h"
 #include "QueueCallback.h"
 #include "ClientConnection.h"
@@ -94,7 +96,43 @@ extern "C" NTSTATUS EvtDeviceAdd(WDFDRIVER driver, PWDFDEVICE_INIT init) {
         return status;
     }
 
-    // 4. 创建 WDF 设备
+    // 注册 CREATE/CLOSE/CLEANUP 预处理回调(确保正确转发)
+    status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
+        init, commkit_driver::SerialFilter::DispatchCreate,
+        IRP_MJ_CREATE, nullptr, 0);
+    if (!NT_SUCCESS(status)) {
+        return status;
+    }
+
+    status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
+        init, commkit_driver::SerialFilter::DispatchClose,
+        IRP_MJ_CLOSE, nullptr, 0);
+    if (!NT_SUCCESS(status)) {
+        return status;
+    }
+
+    status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
+        init, commkit_driver::SerialFilter::DispatchCleanup,
+        IRP_MJ_CLEANUP, nullptr, 0);
+    if (!NT_SUCCESS(status)) {
+        return status;
+    }
+
+    // 4. 设置宽松 SDDL(匹配 serial.sys 行为)
+    // WDF 默认 SDDL 仅 System+Administrators,会导致非 elevated 进程
+    // 无法打开 \\.\COMx(即使 Administrator 账户在 filtered token 下也失败)
+    // AU=Authenticated Users, WD=Everyone 给读写权限
+    static DECLARE_CONST_UNICODE_STRING(
+        sddl_filter,
+        L"D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW;;;AU)(A;;GRGW;;;WD)");
+    status = WdfDeviceInitAssignSDDLString(init, &sddl_filter);
+    if (!NT_SUCCESS(status)) {
+        DbgPrint("[CommModifyKit] WdfDeviceInitAssignSDDLString failed 0x%08X\n", status);
+        return status;
+    }
+    DbgPrint("[CommModifyKit] SDDL assigned OK\n");
+
+    // 5. 创建 WDF 设备
     WDFDEVICE device;
     WDF_OBJECT_ATTRIBUTES attrs;
     WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attrs, DEVICE_CONTEXT);
@@ -102,8 +140,10 @@ extern "C" NTSTATUS EvtDeviceAdd(WDFDRIVER driver, PWDFDEVICE_INIT init) {
 
     status = WdfDeviceCreate(&init, &attrs, &device);
     if (!NT_SUCCESS(status)) {
+        DbgPrint("[CommModifyKit] WdfDeviceCreate failed 0x%08X\n", status);
         return status;
     }
+    DbgPrint("[CommModifyKit] WdfDeviceCreate OK\n");
 
     // 5. 初始化设备上下文
     PDEVICE_CONTEXT ctx = DeviceGetContext(device);
@@ -143,6 +183,8 @@ NTSTATUS EvtDevicePrepareHardware(WDFDEVICE device,
     if (NT_SUCCESS(status)) {
         PWCHAR name = (PWCHAR)WdfMemoryGetBuffer(memory, nullptr);
         if (name) {
+            // 打印 FriendlyName/DeviceDescription 前 40 字符用于诊断
+            DbgPrint("[CommModifyKit] FriendlyName='%ws'\n", name);
             // 解析 "COMx" 模式(可能出现在 "COMx (port)" 或 "通信端口 (COMx)" 等格式中)
             ULONG name_len = (ULONG)wcslen(name);
             for (ULONG i = 0; i + 3 <= name_len; i++) {
@@ -227,9 +269,52 @@ NTSTATUS EvtDevicePrepareHardware(WDFDEVICE device,
         }
     }
 
+    // 后备方案 2:查询本设备注册的 GUID_DEVINTERFACE_COMPORT 接口
+    // 虚拟串口可能不在 SERIALCOMM 中,但会注册 COMPORT 接口
+    // 注意:必须传入本设备 PDO,不能传 nullptr 枚举所有设备后用 IoGetDeviceObjectPointer 打开,
+    //       因为那会发送 IRP_MJ_CREATE 到正在初始化的设备栈,导致死锁
+    if (ctx->ComNumber == 0) {
+        PDEVICE_OBJECT currentPdo = WdfDeviceWdmGetPhysicalDevice(device);
+        if (currentPdo) {
+            PWSTR interfaceList = nullptr;
+            status = IoGetDeviceInterfaces(
+                &GUID_DEVINTERFACE_COMPORT,
+                currentPdo,  // 仅查询本设备的接口
+                DEVICE_INTERFACE_INCLUDE_NONACTIVE,
+                &interfaceList);
+            if (NT_SUCCESS(status) && interfaceList) {
+                PWCHAR p = interfaceList;
+                while (*p && ctx->ComNumber == 0) {
+                    ULONG len = (ULONG)wcslen(p);
+                    // 从符号链接路径解析 COM 编号(如 "\\?\COM12")
+                    for (ULONG i = 0; i + 3 <= len; i++) {
+                        if ((p[i] == L'C' || p[i] == L'c') &&
+                            (p[i+1] == L'O' || p[i+1] == L'o') &&
+                            (p[i+2] == L'M' || p[i+2] == L'm') &&
+                            p[i+3] >= L'0' && p[i+3] <= L'9') {
+                            ULONG num = 0;
+                            ULONG j = i + 3;
+                            while (j < len && p[j] >= L'0' && p[j] <= L'9') {
+                                num = num * 10 + (p[j] - L'0');
+                                j++;
+                            }
+                            if (num > 0 && num <= 256) {
+                                ctx->ComNumber = num;
+                                break;
+                            }
+                        }
+                    }
+                    p += len + 1;
+                }
+                ExFreePool(interfaceList);
+            }
+        }
+    }
+
     // 更新端口表中的 COM 编号
     commkit_driver::GetClientConnection().UpdatePortComNumber(device, ctx->ComNumber);
 
+    DbgPrint("[CommModifyKit] EvtDevicePrepareHardware OK, ComNumber=%u\n", ctx->ComNumber);
     return STATUS_SUCCESS;
 }
 

+ 76 - 15
driver/QueueCallback.cpp

@@ -11,9 +11,14 @@
 namespace commkit_driver {
 
 // SDDL: System and Administrators have full access
-static const UNICODE_STRING SddlSysAdminFullAccess = 
+static const UNICODE_STRING SddlSysAdminFullAccess =
     RTL_CONSTANT_STRING(L"D:P(A;;GA;;;SY)(A;;GA;;;BA)");
 
+// 专用 Parallel 队列句柄:用于 READ_EVENTS 转发
+// WdfDeviceConfigureRequestDispatching 只能按请求类型路由(如 WdfRequestTypeDeviceControl),
+// 不能按具体 IOCTL 码路由。改为在 EvtIoDeviceControl 中用 WdfRequestForwardToIoQueue 转发。
+static WDFQUEUE g_read_events_queue = nullptr;
+
 NTSTATUS CreateControlDevice(WDFDRIVER driver) {
     NTSTATUS status;
     PWDFDEVICE_INIT init = WdfControlDeviceInitAllocate(
@@ -51,6 +56,8 @@ NTSTATUS CreateControlDevice(WDFDRIVER driver) {
     }
 
     // 配置 I/O Queue 处理 IOCTL
+    // 主队列:Sequential 派发,处理除 READ_EVENTS 外的所有 IOCTL
+    // (ATTACH/DETACH/WRITE_PORT/READ_PORT/FREE_ALL 需要串行化避免竞争)
     WDF_IO_QUEUE_CONFIG queue_config;
     WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queue_config, WdfIoQueueDispatchSequential);
     queue_config.EvtIoDeviceControl = EvtIoDeviceControl;
@@ -63,6 +70,22 @@ NTSTATUS CreateControlDevice(WDFDRIVER driver) {
         return status;
     }
 
+    // 专用队列:Parallel 派发,仅处理 IOCTL_COMMKIT_READ_EVENTS
+    // 独立于主队列,避免 WRITE_PORT/READ_PORT 同步阻塞时 EventLoop 无法读取事件
+    // (否则高流量场景下环形缓冲溢出覆盖旧事件)
+    WDF_IO_QUEUE_CONFIG read_events_config;
+    WDF_IO_QUEUE_CONFIG_INIT(&read_events_config, WdfIoQueueDispatchParallel);
+    read_events_config.EvtIoDeviceControl = EvtIoReadEvents;
+
+    WDFQUEUE read_events_queue;
+    status = WdfIoQueueCreate(control_device, &read_events_config,
+                                WDF_NO_OBJECT_ATTRIBUTES, &read_events_queue);
+    if (!NT_SUCCESS(status)) {
+        return status;
+    }
+    // 保存队列句柄供 EvtIoDeviceControl 转发 READ_EVENTS 请求
+    g_read_events_queue = read_events_queue;
+
     // 标记控制设备已就绪
     WdfControlFinishInitializing(control_device);
     return STATUS_SUCCESS;
@@ -112,8 +135,15 @@ VOID EvtIoDeviceControl(WDFQUEUE queue, WDFREQUEST request,
         status = WdfRequestRetrieveInputBuffer(request, sizeof(*req),
                                                   (PVOID*)&req, &buf_len);
         if (NT_SUCCESS(status) && req) {
-            PVOID data = req->Data;
-            status = conn.HandleWritePort(req->ComNumber, data, req->DataLen);
+            // 校验 DataLen 不超过实际缓冲区容量,防止越界读取
+            // Data 字段起始偏移 = sizeof(UINT32) * 2 = 8
+            ULONG data_capacity = (buf_len > 8) ? (ULONG)(buf_len - 8) : 0;
+            if (req->DataLen > data_capacity) {
+                status = STATUS_INVALID_PARAMETER;
+            } else {
+                PVOID data = req->Data;
+                status = conn.HandleWritePort(req->ComNumber, data, req->DataLen);
+            }
         }
         break;
     }
@@ -124,24 +154,28 @@ VOID EvtIoDeviceControl(WDFQUEUE queue, WDFREQUEST request,
         status = WdfRequestRetrieveInputBuffer(request, sizeof(*req),
                                                   (PVOID*)&req, &buf_len);
         if (NT_SUCCESS(status) && req) {
-            PVOID data = req->Data;
-            status = conn.HandleReadPort(req->ComNumber, data, req->DataLen);
+            ULONG data_capacity = (buf_len > 8) ? (ULONG)(buf_len - 8) : 0;
+            if (req->DataLen > data_capacity) {
+                status = STATUS_INVALID_PARAMETER;
+            } else {
+                PVOID data = req->Data;
+                status = conn.HandleReadPort(req->ComNumber, data, req->DataLen);
+            }
         }
         break;
     }
 
-    case IOCTL_COMMKIT_READ_EVENTS: {
-        PVOID out_buf = nullptr;
-        size_t out_len = 0;
-        status = WdfRequestRetrieveOutputBuffer(request, 0,
-                                                   (PVOID*)&out_buf, &out_len);
-        if (NT_SUCCESS(status) && out_buf) {
-            ULONG returned = 0;
-            status = conn.HandleReadEvents(out_buf, (ULONG)out_len, &returned);
-            info = returned;
+    case IOCTL_COMMKIT_READ_EVENTS:
+        // 转发到专用 Parallel 队列,避免被 WRITE_PORT/READ_PORT 的同步阻塞拖慢
+        if (g_read_events_queue) {
+            NTSTATUS fwd = WdfRequestForwardToIoQueue(request, g_read_events_queue);
+            if (NT_SUCCESS(fwd)) {
+                return;  // 转发成功,请求已移至专用队列,不再完成
+            }
+            // 转发失败,按错误完成
         }
+        status = STATUS_INVALID_DEVICE_REQUEST;
         break;
-    }
 
     case IOCTL_COMMKIT_FREE_ALL:
         status = conn.HandleFreeAll();
@@ -155,6 +189,33 @@ VOID EvtIoDeviceControl(WDFQUEUE queue, WDFREQUEST request,
     WdfRequestCompleteWithInformation(request, status, info);
 }
 
+// 专用队列回调:处理 IOCTL_COMMKIT_READ_EVENTS
+// 独立于主队列,即使 WRITE_PORT/READ_PORT 阻塞,EventLoop 仍可读取事件
+VOID EvtIoReadEvents(WDFQUEUE queue, WDFREQUEST request,
+                       size_t output_buffer_length, size_t input_buffer_length,
+                       ULONG io_control_code) {
+    UNREFERENCED_PARAMETER(queue);
+    UNREFERENCED_PARAMETER(input_buffer_length);
+    UNREFERENCED_PARAMETER(output_buffer_length);
+
+    if (io_control_code != IOCTL_COMMKIT_READ_EVENTS) {
+        WdfRequestComplete(request, STATUS_INVALID_DEVICE_REQUEST);
+        return;
+    }
+
+    PVOID out_buf = nullptr;
+    size_t out_len = 0;
+    NTSTATUS status = WdfRequestRetrieveOutputBuffer(request, 0,
+                                                        (PVOID*)&out_buf, &out_len);
+    if (NT_SUCCESS(status) && out_buf) {
+        ULONG returned = 0;
+        status = GetClientConnection().HandleReadEvents(out_buf, (ULONG)out_len, &returned);
+        WdfRequestCompleteWithInformation(request, status, returned);
+    } else {
+        WdfRequestComplete(request, STATUS_INVALID_PARAMETER);
+    }
+}
+
 VOID EvtIoDefault(WDFQUEUE queue, WDFREQUEST request) {
     UNREFERENCED_PARAMETER(queue);
     WdfRequestComplete(request, STATUS_INVALID_DEVICE_REQUEST);

+ 8 - 1
driver/QueueCallback.h

@@ -12,11 +12,18 @@ namespace commkit_driver {
 // 创建控制设备并配置 I/O Queue
 NTSTATUS CreateControlDevice(WDFDRIVER driver);
 
-// EvtIoDeviceControl 回调:处理 IOCTL
+// EvtIoDeviceControl 回调:处理 IOCTL(主队列,Sequential 派发)
+// 处理除 IOCTL_COMMKIT_READ_EVENTS 外的所有 IOCTL
 VOID EvtIoDeviceControl(WDFQUEUE queue, WDFREQUEST request,
                           size_t output_buffer_length, size_t input_buffer_length,
                           ULONG io_control_code);
 
+// EvtIoReadEvents 回调:专用处理 IOCTL_COMMKIT_READ_EVENTS(Parallel 派发)
+// 独立队列避免被 WRITE_PORT/READ_PORT 的同步阻塞拖慢事件读取
+VOID EvtIoReadEvents(WDFQUEUE queue, WDFREQUEST request,
+                       size_t output_buffer_length, size_t input_buffer_length,
+                       ULONG io_control_code);
+
 // EvtIoDefault 回调(处理非 IOCTL 请求,仅完成)
 VOID EvtIoDefault(WDFQUEUE queue, WDFREQUEST request);
 

+ 18 - 2
driver/SerialFilter.cpp

@@ -11,8 +11,8 @@
 
 namespace commkit_driver {
 
-// 全局原子序列号(每个事件递增)
-static LONG g_sequence_counter = 0;
+// 全局原子序列号(所有事件类型共享,保证 Sequence 字段全局唯一递增)
+LONG g_sequence_counter = 0;
 
 // 获取当前时间戳(100ns 单位)
 static UINT64 GetCurrentTimeStamp() {
@@ -121,6 +121,22 @@ NTSTATUS SerialFilter::DispatchDeviceControl(WDFDEVICE dev, PIRP irp) {
     return WdfDeviceWdmDispatchPreprocessedIrp(dev, irp);
 }
 
+NTSTATUS SerialFilter::DispatchCreate(WDFDEVICE dev, PIRP irp) {
+    // CREATE/CLOSE/CLEANUP 直接转发,不做任何处理
+    DbgPrint("[CommModifyKit] DispatchCreate entered, IRP=%p\n", irp);
+    NTSTATUS status = WdfDeviceWdmDispatchPreprocessedIrp(dev, irp);
+    DbgPrint("[CommModifyKit] DispatchCreate returned 0x%08X\n", status);
+    return status;
+}
+
+NTSTATUS SerialFilter::DispatchClose(WDFDEVICE dev, PIRP irp) {
+    return WdfDeviceWdmDispatchPreprocessedIrp(dev, irp);
+}
+
+NTSTATUS SerialFilter::DispatchCleanup(WDFDEVICE dev, PIRP irp) {
+    return WdfDeviceWdmDispatchPreprocessedIrp(dev, irp);
+}
+
 NTSTATUS SerialFilter::CreateAndAttach(WDFDRIVER driver,
                                          PDEVICE_OBJECT serial_pdo,
                                          ULONG com_number,

+ 6 - 0
driver/SerialFilter.h

@@ -10,6 +10,9 @@
 
 namespace commkit_driver {
 
+// 全局原子序列号(所有事件类型共享,保证 Sequence 字段全局唯一递增)
+extern LONG g_sequence_counter;
+
 class SerialFilter {
 public:
     // 创建过滤设备并 attach 到串口设备栈
@@ -30,6 +33,9 @@ public:
     static NTSTATUS DispatchRead(WDFDEVICE dev, PIRP irp);
     static NTSTATUS DispatchWrite(WDFDEVICE dev, PIRP irp);
     static NTSTATUS DispatchDeviceControl(WDFDEVICE dev, PIRP irp);
+    static NTSTATUS DispatchCreate(WDFDEVICE dev, PIRP irp);
+    static NTSTATUS DispatchClose(WDFDEVICE dev, PIRP irp);
+    static NTSTATUS DispatchCleanup(WDFDEVICE dev, PIRP irp);
 
 private:
     // 从 IRP 中提取数据并推入环形缓冲

+ 297 - 57
installer/install.ps1

@@ -5,7 +5,7 @@
 #Requires -Version 5.1
 #Requires -RunAsAdministrator
 
-$ErrorActionPreference = 'Stop'
+$ErrorActionPreference = 'Continue'
 
 $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
 
@@ -41,9 +41,43 @@ $DllCandidates = @(
     Join-Path $ScriptDir "..\build\Debug\x64\CommModifyKit.dll"
 )
 
+# 检查 DLL 架构(32位服务必须使用32位DLL)
+function Test-PeArch {
+    param([string]$Path)
+    if (-not (Test-Path $Path)) { return 0 }
+    # 只读前 512 字节(PE 头足够)
+    try {
+        $fs = [System.IO.File]::OpenRead($Path)
+    } catch {
+        return 0  # 文件被锁定或无法访问
+    }
+    try {
+        $bytes = New-Object byte[] 512
+        $read = $fs.Read($bytes, 0, 512)
+        if ($read -lt 64) { return 0 }
+        $peOffset = [System.BitConverter]::ToInt32($bytes, 60)
+        if ($peOffset -eq 0 -or $peOffset + 6 -gt $read) { return 0 }
+        $machine = [System.BitConverter]::ToUInt16($bytes, $peOffset + 4)
+        return $machine  # 0x14c=i386(32bit), 0x8664=x64
+    } finally {
+        $fs.Close()
+    }
+}
+
 $DriverSys = $DriverSysCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
 $DriverInf = $DriverInfCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
-$DllPath = $DllCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
+
+# 查找 32 位 DLL(跳过 64 位)
+$DllPath = $null
+foreach ($candidate in $DllCandidates) {
+    if (Test-Path $candidate) {
+        $arch = Test-PeArch $candidate
+        if ($arch -eq 0x14c) {
+            $DllPath = $candidate
+            break
+        }
+    }
+}
 
 if (-not $DriverSys) {
     Write-Error "未找到 CommModifyKit.sys,请先用 WDK + VS2019 编译 driver 项目"
@@ -53,68 +87,282 @@ if (-not $DriverInf) {
     Write-Error "未找到 CommModifyKit.inf"
     exit 1
 }
-Write-Host "驱动 .sys: $DriverSys" -ForegroundColor Green
+
+# 校验驱动 .sys 必须为 64 位(x64),32 位驱动无法在 64 位内核加载
+$driverArch = Test-PeArch $DriverSys
+if ($driverArch -ne 0x8664) {
+    $archName = switch ($driverArch) {
+        0x14c { 'i386 (32-bit)' }
+        0      { 'unknown/unreadable' }
+        default { "0x$('{0:X4}' -f $driverArch)" }
+    }
+    Write-Error "CommModifyKit.sys 架构为 $archName,必须为 x64 (64-bit)。请检查 driver 项目平台配置。"
+    exit 1
+}
+
+Write-Host "驱动 .sys: $DriverSys (x64)" -ForegroundColor Green
 Write-Host "驱动 .inf: $DriverInf" -ForegroundColor Green
 if ($DllPath) {
-    Write-Host "用户态 DLL: $DllPath" -ForegroundColor Green
+    Write-Host "用户态 DLL: $DllPath (32-bit)" -ForegroundColor Green
 } else {
-    Write-Warning "未找到 CommModifyKit.dll(可后续单独编译 dll 项目)"
+    Write-Warning "未找到 32 位 CommModifyKit.dll,请编译 Win32 平台"
 }
 Write-Host ""
 
 # ============================================================================
 # 2. 拷贝驱动文件到 System32\drivers
 # ============================================================================
-$TargetDriverDir = Join-Path $env:windir "System32\drivers"
-$TargetDriverSys = Join-Path $TargetDriverDir "CommModifyKit.sys"
-$TargetDriverInf = Join-Path $TargetDriverDir "CommModifyKit.inf"
+# 检测 32 位 PowerShell(WoW64 会将 System32 重定向到 SysWOW64)
+$Is32bitPS = ([System.IntPtr]::Size -eq 4)
+
+# 文件操作路径(32 位 PowerShell 需用 Sysnative 绕过 WoW64 重定向)
+$FileDriverDir = if ($Is32bitPS) {
+    Write-Warning "检测到 32 位 PowerShell,文件操作使用 Sysnative 路径"
+    Join-Path $env:windir "Sysnative\drivers"
+} else {
+    Join-Path $env:windir "System32\drivers"
+}
+$TargetDriverSys = Join-Path $FileDriverDir "CommModifyKit.sys"
+$TargetDriverInf = Join-Path $FileDriverDir "CommModifyKit.inf"
 
-Write-Host "拷贝驱动文件到 $TargetDriverDir ..."
-Copy-Item -Force $DriverSys $TargetDriverSys
-Copy-Item -Force $DriverInf $TargetDriverInf
-Write-Host "  完成" -ForegroundColor Green
+# 服务 binPath 路径(始终 System32\drivers,SCM/内核为 64 位,不识别 Sysnative)
+$ServiceBinPath = Join-Path $env:windir "System32\drivers\CommModifyKit.sys"
+
+# 先停止已存在的服务,避免 .sys 文件被锁定
+$existingSvc = Get-Service -Name 'CommModifyKit' -ErrorAction SilentlyContinue
+if ($existingSvc -and $existingSvc.Status -eq 'Running') {
+    Write-Host "停止现有驱动服务..."
+    sc.exe stop CommModifyKit 2>&1 | Out-Null
+    Start-Sleep 2
+    # 检查是否真的停止了
+    $svcAfterStop = Get-Service -Name 'CommModifyKit' -ErrorAction SilentlyContinue
+    if ($svcAfterStop -and $svcAfterStop.Status -eq 'Running') {
+        Write-Warning "驱动服务无法停止(可能被设备栈引用),.sys 文件可能被锁定"
+        Write-Host "请先运行 uninstall.ps1 移除 UpperFilters,重启后再安装" -ForegroundColor Yellow
+    }
+}
+
+Write-Host "拷贝驱动文件到 $FileDriverDir ..."
+$copyOk = $true
+try {
+    Copy-Item -Force $DriverSys $TargetDriverSys -ErrorAction Stop
+    Copy-Item -Force $DriverInf $TargetDriverInf -ErrorAction Stop
+    Write-Host "  完成" -ForegroundColor Green
+} catch {
+    Write-Warning "拷贝失败: $_"
+    Write-Warning ".sys 文件被锁定,请先卸载驱动并重启"
+    $copyOk = $false
+}
+
+if (-not $copyOk) {
+    # 仍然继续尝试创建服务等步骤(可能旧 .sys 仍可用)
+    Write-Host "继续安装(使用现有 .sys 文件)..." -ForegroundColor Yellow
+}
+
+# ============================================================================
+# 2.5 注册驱动包到驱动存储区(关键步骤!)
+# ============================================================================
+# 先检查测试签名状态,pnputil 要求驱动有签名
+# bcdedit 输出 "testsigning    Yes" 或 "test signing    Yes"(不同本地化版本)
+$TestSigningEnabled = (bcdedit /enum | Select-String 'test\s*signing\s+Yes' -Quiet)
+if (-not $TestSigningEnabled) {
+    Write-Host ""
+    Write-Warning "测试签名未启用,驱动无法加载"
+    Write-Host "正在启用测试签名..." -ForegroundColor Yellow
+    bcdedit /set testsigning on | Out-Null
+    Write-Host "测试签名已启用" -ForegroundColor Green
+    Write-Warning "需要重启计算机才能生效!"
+    Write-Host ""
+    Write-Host "请重启后再次运行此脚本" -ForegroundColor Yellow
+    exit 0
+}
+
+# 对驱动进行测试签名(如果未签名)
+Write-Host "检查驱动签名状态..."
+$sigState = (Get-AuthenticodeSignature $TargetDriverSys).Status
+if ($sigState -ne 'Valid') {
+    Write-Host "驱动未签名,使用测试证书签名..." -ForegroundColor Yellow
+
+    # 创建或获取测试证书(用 Subject 匹配,检查未过期)
+    $testCert = Get-ChildItem -Path Cert:\LocalMachine\My -ErrorAction SilentlyContinue | Where-Object { $_.Subject -match "CommModifyKitTest" -and $_.NotAfter -gt (Get-Date) } | Select-Object -First 1
+    if (-not $testCert) {
+        # 清理过期的旧证书
+        Get-ChildItem -Path Cert:\LocalMachine\My -ErrorAction SilentlyContinue | Where-Object { $_.Subject -match "CommModifyKitTest" } | Remove-Item -Force -ErrorAction SilentlyContinue
+        Write-Host "创建测试证书(有效期 10 年)..."
+        $testCert = New-SelfSignedCertificate -Subject "CN=CommModifyKitTest" -CertStoreLocation "Cert:\LocalMachine\My" -Type CodeSigningCert -KeyExportPolicy Exportable -KeyLength 2048 -KeyAlgorithm RSA -KeyUsage DigitalSignature -NotAfter (Get-Date).AddYears(10)
+    }
+
+    # 将证书添加到 Trusted Root(测试签名模式下必须)
+    $rootCert = Get-ChildItem -Path Cert:\LocalMachine\Root -ErrorAction SilentlyContinue | Where-Object { $_.Subject -match "CommModifyKitTest" } | Select-Object -First 1
+    if (-not $rootCert) {
+        Write-Host "添加证书到受信任的根证书颁发机构..."
+        $certPath = Join-Path $env:TEMP "CommModifyKitTest.cer"
+        Export-Certificate -Cert $testCert -FilePath $certPath -Force | Out-Null
+
+        # 尝试方法 1: Import-Certificate
+        $importSuccess = $false
+        try {
+            Import-Certificate -FilePath $certPath -CertStoreLocation "Cert:\LocalMachine\Root" -ErrorAction Stop | Out-Null
+            $importSuccess = $true
+            Write-Host "  完成(Import-Certificate)" -ForegroundColor Green
+        } catch {
+            Write-Warning "Import-Certificate 失败: $($_.Exception.Message)"
+
+            # 尝试方法 2: certutil.exe
+            Write-Host "  尝试使用 certutil..."
+            $certutilResult = & certutil.exe -addstore -f "Root" $certPath 2>&1
+            if ($LASTEXITCODE -eq 0) {
+                $importSuccess = $true
+                Write-Host "  完成(certutil)" -ForegroundColor Green
+            } else {
+                Write-Warning "certutil 导入失败: $certutilResult"
+            }
+        }
+
+        # 清理临时证书文件(仅在成功导入后)
+        if ($importSuccess) {
+            Remove-Item $certPath -Force -ErrorAction SilentlyContinue
+        } else {
+            Write-Warning "无法将测试证书添加到受信任的根证书颁发机构"
+            Write-Host "证书文件已保留: $certPath" -ForegroundColor Yellow
+            Write-Host "请手动导入证书:" -ForegroundColor Yellow
+            Write-Host "  1. 双击 $certPath" -ForegroundColor Yellow
+            Write-Host "  2. 选择 '安装证书' → '本地计算机' → '将所有的证书都放入下列存储' → '受信任的根证书颁发机构'" -ForegroundColor Yellow
+        }
+    }
+
+    # 签名驱动:先尝试带时间戳,失败则不带时间戳,再失败用 signtool
+    Write-Host "签名驱动..."
+    $signOk = $false
+
+    # 尝试 1: Set-AuthenticodeSignature 带时间戳
+    $signResult = Set-AuthenticodeSignature -FilePath $TargetDriverSys -Certificate $testCert -TimeStampServer "http://timestamp.digicert.com" -HashAlgorithm SHA256
+    if ($signResult.Status -eq 'Valid' -or $signResult.Status -eq 'UnknownError') {
+        Write-Host "  完成(带时间戳)" -ForegroundColor Green
+        $signOk = $true
+    }
+
+    # 尝试 2: Set-AuthenticodeSignature 不带时间戳
+    if (-not $signOk) {
+        Write-Host "  时间戳服务器不可达,尝试不带时间戳..."
+        $signResult = Set-AuthenticodeSignature -FilePath $TargetDriverSys -Certificate $testCert -HashAlgorithm SHA256
+        if ($signResult.Status -eq 'Valid' -or $signResult.Status -eq 'UnknownError') {
+            Write-Host "  完成(无时间戳)" -ForegroundColor Green
+            $signOk = $true
+        }
+    }
+
+    # 尝试 3: signtool(如果安装了 Windows SDK)
+    if (-not $signOk) {
+        $signtoolPaths = @(
+            "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe",
+            "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.22000.0\x64\signtool.exe",
+            "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe"
+        )
+        $signtool = $signtoolPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
+        # 通配符后备搜索
+        if (-not $signtool) {
+            $signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\signtool.exe" -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName
+        }
+        if ($signtool) {
+            Write-Host "  尝试 signtool..."
+            # 用 /sha1 指纹精确指定证书,避免 /n 子串匹配选到过期的同名证书
+            & $signtool sign /s MY /sha1 "$($testCert.Thumbprint)" /fd SHA256 $TargetDriverSys 2>&1 | Out-Null
+            if ($LASTEXITCODE -eq 0) {
+                Write-Host "  完成(signtool)" -ForegroundColor Green
+                $signOk = $true
+            }
+        }
+    }
+
+    if (-not $signOk) {
+        Write-Warning "签名失败: $($signResult.Status) - $($signResult.StatusMessage)"
+        Write-Warning "驱动可能无法加载,请确认测试签名已启用并重启"
+    }
+} else {
+    Write-Host "驱动已签名" -ForegroundColor Green
+}
+
+# pnputil /add-driver 需要 .cat 目录文件,开发阶段通常没有
+# 失败后通过 sc.exe create 直接加载驱动(测试签名模式)
+Write-Host "尝试注册驱动包到驱动存储区..."
+try {
+    $result = pnputil /add-driver $DriverInf /install 2>&1
+    if ($LASTEXITCODE -eq 0) {
+        Write-Host "  完成" -ForegroundColor Green
+        if ($result -match '(oem\d+\.inf)') {
+            Write-Host "  驱动包: $($matches[1])" -ForegroundColor Green
+        }
+    } else {
+        Write-Host "  pnputil 注册失败(无 .cat 目录文件,属正常),将通过 sc.exe 直接加载" -ForegroundColor Yellow
+    }
+} catch {
+    Write-Host "  pnputil 不可用,将通过 sc.exe 直接加载" -ForegroundColor Yellow
+}
 
 # ============================================================================
 # 3. 创建并启动驱动服务
 # ============================================================================
 $ServiceName = 'CommModifyKit'
-$ExistingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
 
+# 先删除旧服务(可能配置不一致)
+$ExistingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
 if ($ExistingService) {
-    Write-Host "服务 $ServiceName 已存在,状态: $($ExistingService.Status)"
-    if ($ExistingService.Status -ne 'Running') {
-        Write-Host "启动服务..."
-        Start-Service -Name $ServiceName
-        Write-Host "  完成" -ForegroundColor Green
+    if ($ExistingService.Status -eq 'Running') {
+        sc.exe stop $ServiceName 2>&1 | Out-Null
+        Start-Sleep 2
     }
-} else {
-    Write-Host "创建服务 $ServiceName ..."
-    sc.exe create $ServiceName binPath= $TargetDriverSys type= kernel start= demand error= normal | Out-Null
+    sc.exe delete $ServiceName 2>&1 | Out-Null
+    Start-Sleep 1
+}
+
+Write-Host "创建服务 $ServiceName ..."
+sc.exe create $ServiceName binPath= $ServiceBinPath type= kernel start= demand error= normal | Out-Null
+if ($LASTEXITCODE -eq 0) {
     Write-Host "  完成" -ForegroundColor Green
     Write-Host "启动服务..."
-    sc.exe start $ServiceName | Out-Null
-    Write-Host "  完成" -ForegroundColor Green
+    sc.exe start $ServiceName 2>&1 | Out-Null
+    if ($LASTEXITCODE -eq 0) {
+        Write-Host "  完成" -ForegroundColor Green
+    } else {
+        Write-Warning "  启动失败(可能需要重启后由 PnP 自动加载)"
+    }
+} else {
+    Write-Warning "  创建服务失败"
 }
 
 # ============================================================================
 # 3.5 注册为 Ports 类 UpperFilters
+# 注意:仅当驱动服务确认运行中时才注册 UpperFilters,否则会导致 PnP 尝试
+#       attach 一个不存在的过滤器,使所有 Ports 类设备启动失败(mode 显示
+#       "访问设备失败")。
 # ============================================================================
-$PortsClassGuid = "{4D36E978-E325-11CE-BFC1-08002BE10318}"
-$ClassKey = "HKLM:\SYSTEM\CurrentControlSet\Control\Class\$PortsClassGuid"
-$currentFilters = @()
-$existingFilters = (Get-ItemProperty -Path $ClassKey -Name "UpperFilters" -ErrorAction SilentlyContinue)
-if ($existingFilters) {
-    $currentFilters = @($existingFilters.UpperFilters)
-}
-if ($currentFilters -notcontains "CommModifyKit") {
-    $newFilters = @($currentFilters) + "CommModifyKit"
-    Set-ItemProperty -Path $ClassKey -Name "UpperFilters" -Value $newFilters -Type MultiString
-    Write-Host "已将 CommModifyKit 添加到 Ports 类 UpperFilters" -ForegroundColor Green
-    # 重新枚举设备使 UpperFilter 生效
-    pnputil /scan-devices | Out-Null
-    Write-Host "已触发设备重新枚举" -ForegroundColor Green
+# 先确认服务确实在运行(避免驱动加载失败时污染 UpperFilters)
+$svcState = (Get-Service -Name $ServiceName -ErrorAction SilentlyContinue).Status
+if ($svcState -ne 'Running') {
+    Write-Warning "驱动服务未运行(状态: $svcState),跳过 UpperFilters 注册"
+    Write-Host "请检查驱动签名/测试签名模式,重启后再次运行此脚本" -ForegroundColor Yellow
+    Write-Host "未注册 UpperFilters,串口不会被过滤,但驱动也未生效" -ForegroundColor Yellow
 } else {
-    Write-Host "CommModifyKit 已在 UpperFilters 中" -ForegroundColor Green
+    $PortsClassGuid = "{4D36E978-E325-11CE-BFC1-08002BE10318}"
+    $ClassKey = "HKLM:\SYSTEM\CurrentControlSet\Control\Class\$PortsClassGuid"
+    # 读取现有 UpperFilters,过滤掉空字符串(历史遗留的空字符串会导致
+    # PnP 加载空名称过滤器,使所有 Ports 类设备启动失败)
+    $currentFilters = @()
+    $existingFilters = (Get-ItemProperty -Path $ClassKey -Name "UpperFilters" -ErrorAction SilentlyContinue)
+    if ($existingFilters -and $existingFilters.UpperFilters) {
+        $currentFilters = @($existingFilters.UpperFilters | Where-Object { $_ -and $_.Trim() -ne '' })
+    }
+    if ($currentFilters -notcontains "CommModifyKit") {
+        $newFilters = @($currentFilters) + "CommModifyKit"
+        Set-ItemProperty -Path $ClassKey -Name "UpperFilters" -Value $newFilters -Type MultiString
+        Write-Host "已将 CommModifyKit 添加到 Ports 类 UpperFilters" -ForegroundColor Green
+        # 重新枚举设备使 UpperFilter 生效
+        pnputil /scan-devices | Out-Null
+        Write-Host "已触发设备重新枚举" -ForegroundColor Green
+    } else {
+        Write-Host "CommModifyKit 已在 UpperFilters 中" -ForegroundColor Green
+    }
 }
 
 # ============================================================================
@@ -132,7 +380,9 @@ if ($DllPath) {
     if ($ServiceExe) {
         $ServiceDir = Split-Path -Parent $ServiceExe
         $TargetDll = Join-Path $ServiceDir "CommModifyKit.dll"
-        if ((Resolve-Path $DllPath -ErrorAction SilentlyContinue).Path -eq (Resolve-Path $TargetDll -ErrorAction SilentlyContinue).Path) {
+        $srcReal = (Resolve-Path $DllPath -ErrorAction SilentlyContinue).Path
+        $dstReal = if (Test-Path $TargetDll) { (Resolve-Path $TargetDll).Path } else { $null }
+        if ($srcReal -and $dstReal -and $srcReal -eq $dstReal) {
             Write-Host "DLL 已在目标目录,跳过拷贝" -ForegroundColor Green
         } else {
             Write-Host "拷贝 DLL 到 $TargetDll ..."
@@ -144,23 +394,13 @@ if ($DllPath) {
     }
 }
 
-# ============================================================================
-# 5. 启用测试签名(开发期需要)
-# ============================================================================
-$TestSigningEnabled = (bcdedit /enum | Select-String 'testsigning' -Quiet)
-if (-not $TestSigningEnabled) {
-    Write-Host ""
-    Write-Warning "检测到测试签名未启用,驱动可能无法加载"
-    Write-Host "执行: bcdedit /set testsigning on" -ForegroundColor Yellow
-    bcdedit /set testsigning on | Out-Null
-    Write-Warning "测试签名已启用,需要重启计算机才能生效"
-} else {
-    Write-Host "测试签名已启用" -ForegroundColor Green
-}
-
 Write-Host ""
 Write-Host '=== 安装完成 ===' -ForegroundColor Cyan
-Write-Host "请确认:"
-Write-Host "  1. 计算机已重启(启用测试签名后)"
-Write-Host "  2. CommModifyKit.dll 已拷贝到 CommModifyService.exe 所在目录"
-Write-Host "  3. config.json 中 'comm.use_dll' 设为 true"
+Write-Host ""
+Write-Host "验证步骤:" -ForegroundColor Yellow
+Write-Host "  1. 确认驱动服务:    sc.exe query CommModifyKit"
+Write-Host "  2. 确认串口可用:    mode"
+Write-Host "  3. 确认 UpperFilters: reg query ""HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E978-E325-11CE-BFC1-08002BE10318}"" /v UpperFilters"
+Write-Host ""
+Write-Host "如果串口显示 '访问设备失败',请重启计算机使 UpperFilters 生效" -ForegroundColor Yellow
+Write-Host "  重启命令: shutdown /r /t 0" -ForegroundColor Yellow

+ 61 - 12
installer/uninstall.ps1

@@ -6,13 +6,22 @@ Write-Host ""
 Write-Host "通过 pnputil 删除驱动包..." -ForegroundColor Yellow
 try {
     $raw = pnputil /enum-drivers 2>&1
-    $lines = $raw -split "
-?
-"
+    # pnputil 输出格式:
+    # Published Name:     oem0.inf
+    # Original Name:      CommModifyKit.inf
+    # ...
+    # 需要找到 "Original Name: CommModifyKit.inf" 对应的 "Published Name: oem*.inf"
+    $lines = $raw -split "`r?`n"
     $infs = @()
+    $currentOem = $null
     for ($i=0; $i -lt $lines.Count; $i++) {
-        if ($lines[$i] -match '(oem\d+\.inf)' -and $i+1 -lt $lines.Count -and $lines[$i+1] -match 'CommModifyKit') {
-            $infs += $matches[1]
+        if ($lines[$i] -match 'Published Name:\s+(oem\d+\.inf)') {
+            $currentOem = $matches[1]
+        }
+        if ($lines[$i] -match 'Original Name:\s+CommModifyKit\.inf') {
+            if ($currentOem) {
+                $infs += $currentOem
+            }
         }
     }
     if ($infs.Count -gt 0) {
@@ -28,33 +37,73 @@ try {
 $srv = Get-Service -Name 'CommModifyKit' -ErrorAction SilentlyContinue
 if ($srv) {
     Write-Host ""
-    if ($srv.Status -eq 'Running') { sc.exe stop CommModifyKit | Out-Null; Start-Sleep 2 }
-    sc.exe delete CommModifyKit | Out-Null
+    if ($srv.Status -eq 'Running') {
+        sc.exe stop CommModifyKit 2>&1 | Out-Null
+        Start-Sleep 2
+        # 检查是否真的停止了
+        $svcAfter = Get-Service -Name 'CommModifyKit' -ErrorAction SilentlyContinue
+        if ($svcAfter -and $svcAfter.Status -eq 'Running') {
+            Write-Warning "驱动服务无法停止(设备栈引用中),需要重启后才能完全卸载"
+        }
+    }
+    sc.exe delete CommModifyKit 2>&1 | Out-Null
     Write-Host "服务已删除" -ForegroundColor Green
 }
 
 # 从 Ports 类 UpperFilters 移除 CommModifyKit
+# 同时过滤空字符串(历史遗留的空字符串会导致 PnP 加载空名称过滤器失败)
 $PortsClassGuid = "{4D36E978-E325-11CE-BFC1-08002BE10318}"
 $ClassKey = "HKLM:\SYSTEM\CurrentControlSet\Control\Class\$PortsClassGuid"
 $existingFilters = (Get-ItemProperty -Path $ClassKey -Name "UpperFilters" -ErrorAction SilentlyContinue)
-if ($existingFilters) {
-    $currentFilters = @($existingFilters.UpperFilters)
+if ($existingFilters -and $existingFilters.UpperFilters) {
+    $currentFilters = @($existingFilters.UpperFilters | Where-Object { $_ -and $_.Trim() -ne '' })
     if ($currentFilters -contains "CommModifyKit") {
+        # 移除 CommModifyKit,同时清理任何残留空字符串
         $newFilters = @($currentFilters | Where-Object { $_ -ne "CommModifyKit" })
         if ($newFilters.Count -eq 0) {
+            # 必须删除值,不能设为空字符串(空字符串会导致 PnP 加载空名称过滤器失败)
             Remove-ItemProperty -Path $ClassKey -Name "UpperFilters" -ErrorAction SilentlyContinue
         } else {
             Set-ItemProperty -Path $ClassKey -Name "UpperFilters" -Value $newFilters -Type MultiString
         }
         Write-Host "已从 Ports 类 UpperFilters 移除 CommModifyKit" -ForegroundColor Green
+        # 触发设备重枚举
+        pnputil /scan-devices 2>&1 | Out-Null
+        Write-Host "已触发设备重新枚举" -ForegroundColor Green
     }
 }
 
-$sys = Join-Path $env:windir "System32\drivers\CommModifyKit.sys"
-$inf = Join-Path $env:windir "System32\drivers\CommModifyKit.inf"
+# 驱动文件路径(32 位 PowerShell 需用 Sysnative 绕过 WoW64 重定向)
+$driverDir = if ([System.IntPtr]::Size -eq 4) {
+    Join-Path $env:windir "Sysnative\drivers"
+} else {
+    Join-Path $env:windir "System32\drivers"
+}
+$sys = Join-Path $driverDir "CommModifyKit.sys"
+$inf = Join-Path $driverDir "CommModifyKit.inf"
 Write-Host ""
 if (Test-Path $sys) { Remove-Item $sys -Force -ErrorAction SilentlyContinue }
 if (Test-Path $inf) { Remove-Item $inf -Force -ErrorAction SilentlyContinue }
 Write-Host "驱动文件已删除" -ForegroundColor Green
+
+# 删除测试证书
+$testCert = Get-ChildItem -Path Cert:\LocalMachine\My -ErrorAction SilentlyContinue | Where-Object { $_.Subject -match "CommModifyKitTest" }
+if ($testCert) {
+    $testCert | Remove-Item -Force -ErrorAction SilentlyContinue
+    Write-Host "测试证书已从 My 存储删除" -ForegroundColor Green
+}
+$rootCert = Get-ChildItem -Path Cert:\LocalMachine\Root -ErrorAction SilentlyContinue | Where-Object { $_.Subject -match "CommModifyKitTest" }
+if ($rootCert) {
+    $rootCert | Remove-Item -Force -ErrorAction SilentlyContinue
+    Write-Host "测试证书已从 Root 存储删除" -ForegroundColor Green
+}
+
+Write-Host ""
+Write-Host '=== 卸载完成 ===' -ForegroundColor Cyan
+Write-Host ""
+Write-Host "如果服务无法停止或串口仍不可用,请重启计算机" -ForegroundColor Yellow
+Write-Host "  重启命令: shutdown /r /t 0" -ForegroundColor Yellow
 Write-Host ""
-Write-Host '=== 卸载完成 ===' -ForegroundColor Cyan
+Write-Host "注意: 测试签名模式仍保持开启,如需关闭请执行:" -ForegroundColor Yellow
+Write-Host "  bcdedit /set testsigning off" -ForegroundColor Yellow
+Write-Host "  然后重启计算机" -ForegroundColor Yellow