# CommModifyKit 设计文档 ## 1. 项目概述 CommModifyKit 是一个串口监控与数据拦截套件,由**用户态 DLL** 和**内核态驱动**两部分组成。它以透明过滤驱动的方式挂载到串口设备栈上,实时捕获串口的读写数据,并通过回调函数将事件上报给上层应用(CommModifyService)。 ### 核心能力 - **透明拦截**:作为串口 Upper Filter 驱动,无需修改应用程序即可捕获串口数据 - **实时回调**:捕获的读/写事件通过 `TOnData` 回调实时上抛 - **双向读写**:支持向串口写入数据、发起异步读请求 - **多端口并行**:支持同时监控多个 COM 端口,每端口独立环形缓冲 ### 项目结构 ``` CommModifyKit/ ├── CommModifyKit.sln # VS 解决方案 ├── common/ # 用户态/内核态共享定义 │ ├── CommKitEvents.h # 事件类型常量 │ └── CommKitIoctl.h # IOCTL 码 + 共享数据结构 ├── dll/ # 用户态 DLL 项目 │ ├── CommModifyKit.vcxproj # VS 项目文件 │ ├── CommModifyKit.def # 导出定义(无修饰名) │ ├── dllmain.cpp # DLL 入口(最小化) │ ├── exports.cpp # 7 个 __stdcall 导出函数 │ ├── MonitorManager.h/cpp # 核心管理器(单例) │ ├── DriverClient.h/cpp # 驱动 IOCTL 通信封装 │ ├── EventLoop.h/cpp # 事件循环工作线程 │ ├── ErrorStore.h/cpp # 线程局部错误码 │ ├── Logger.h/cpp # 日志系统 │ └── pch.h # 预编译头 ├── driver/ # 内核态驱动项目 │ ├── CommModifyKitDriver.vcxproj # VS 项目文件(WDK) │ ├── CommModifyKit.inf # 驱动安装 INF │ ├── DriverEntry.cpp # KMDF 驱动入口 │ ├── ClientConnection.h/cpp # 用户态连接管理 │ ├── QueueCallback.h/cpp # I/O Queue IOCTL 处理 │ ├── SerialFilter.h/cpp # 串口 IRP 拦截 │ ├── EventRingBuffer.h/cpp # 内核环形缓冲区 │ └── DeviceContext.h # WDF 设备上下文 └── installer/ # 安装/卸载脚本 ├── install.ps1 # 安装脚本(管理员) └── uninstall.ps1 # 卸载脚本(管理员) ``` --- ## 2. 架构设计 ### 2.1 整体架构 ``` ┌─────────────────────────────────────────────────────┐ │ CommModifyService (上层应用) │ │ 通过 GetProcAddress 调用 DLL 导出函数 │ └────────────────────────┬────────────────────────────┘ │ __stdcall 导出接口 ┌────────────────────────▼────────────────────────────┐ │ CommModifyKit.dll (用户态) │ │ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ │ │MonitorManager│──│ DriverClient │──│ EventLoop │ │ │ │ (单例协调) │ │ (IOCTL 封装) │ │(回调线程) │ │ │ └──────────────┘ └──────┬───────┘ └─────┬──────┘ │ │ │ │ │ │ │ ┌──────▼──────┐ ┌─────▼─────┐ ┌──────▼──────┐ │ │ │ ErrorStore │ │DeviceIoCtl│ │ TOnData回调 │ │ │ │(线程局部错误)│ │ (系统API) │ │ (上报上层) │ │ │ └─────────────┘ └─────┬─────┘ └─────────────┘ │ └────────────────────────────┼─────────────────────────┘ │ IOCTL ┌────────────────────────────▼─────────────────────────┐ │ CommModifyKit.sys (内核态 KMDF 驱动) │ │ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ │ │QueueCallback │──│ClientConnection│──│SerialFilter│ │ │ │ (IOCTL 分发) │ │ (端口/缓冲管理)│ │(IRP 拦截) │ │ │ └──────────────┘ └──────┬───────┘ └─────┬──────┘ │ │ │ │ │ │ ┌──────▼──────┐ ┌──────▼──────┐ │ │ │EventRingBuf │ │下层串口设备栈│ │ │ │(每端口环形缓冲)│ │(真实串口驱动)│ │ │ └─────────────┘ └─────────────┘ │ └──────────────────────────────────────────────────────┘ ``` ### 2.2 数据流 #### 监控捕获流程(读/写事件上抛) ``` 应用读/写串口 → IRP 到达过滤驱动 → SerialFilter 拦截 → IRP 透传到下层串口 → 下层完成 IRP → 完成例程回调 → CaptureIrpData() 提取数据 → Push() 写入环形缓冲 → DLL EventLoop 线程 ReadEvents IOCTL → 批量弹出事件 → TOnData 回调 → 上层应用收到事件 ``` #### 写数据流程(应用主动写入) ``` 上层调用 SetWrite_Data → MonitorManager.WriteData() → DriverClient.WritePort() → IOCTL_COMMKIT_WRITE_PORT → 驱动 HandleWritePort() → 构造 IRP 发给下层串口 ``` --- ## 3. 模块详细设计 ### 3.1 用户态 DLL #### 3.1.1 导出接口(exports.cpp) DLL 导出 7 个 `__stdcall` 函数,通过 `.def` 文件确保导出名无修饰: | 序号 | 函数名 | 参数 | 返回值 | 说明 | |------|--------|------|--------|------| | 1 | `InitMonitor` | `wchar_t* cKey, TOnData aOnData` | BOOL | 初始化监控:校验 Key、启动驱动、注册回调 | | 2 | `Monitor` | `DWORD dwNumber` | BOOL | 开始监控指定串口(1=COM1) | | 3 | `Stop` | `DWORD dwNumber` | BOOL | 停止监控指定串口 | | 4 | `SetWrite_Data` | `DWORD dwNumber, char* lpBuffer, int Len` | BOOL | 向串口写数据 | | 5 | `SetRead_Data` | `DWORD dwNumber, char* lpBuffer, int Len` | BOOL | 发起异步读请求(结果通过回调返回) | | 6 | `FreeMonitor` | 无 | void | 释放所有资源 | | 7 | `get_LastErrror` | 无 | int | 获取最近错误码(注意 3 个 r) | **回调函数类型定义**: ```cpp typedef LONG(CALLBACK* TOnData)( int Sequence, // 递增序列号 double dTime, // OLE Automation date 时间戳 DWORD ComNumber, // 串口编号(COM n 的 n) DWORD Irp, // 事件类型(1=OPEN, 2=READ, 3=WRITE, 4=CLOSE) DWORD dwSize, // 数据字节数 char* lpData // 数据指针(只读,不应释放) ); ``` #### 3.1.2 MonitorManager(核心管理器) **职责**:协调 DriverClient 与 EventLoop,提供 7 个导出函数的底层实现。 **设计要点**: - **全局单例**:`GetMonitorManager()` 返回静态实例,导出函数通过它统一入口 - **互斥保护**:所有公开方法使用 `std::mutex` 保护,保证线程安全 - **延迟初始化**:不在 DllMain 中初始化,避免 Loader Lock 死锁 - **幂等操作**:重复 InitMonitor / Monitor 已有端口均视为成功 **InitMonitor 流程**: ``` 1. 校验回调非空、Key 非空 2. EnsureDriverReady() ├── 检查管理员权限(否则返回 -254) ├── 打开 SCM,检查/创建驱动服务 ├── 检查驱动 .sys 文件存在性 └── 启动驱动服务(若未运行) 3. DriverClient.Open() → CreateFile("\\\.\\CommModifyKit") 4. DriverClient.RegisterCallback() → IOCTL_COMMKIT_REGISTER_CALLBACK 5. EventLoop.Start() → 启动事件循环工作线程 ``` **错误码表**: | 错误码 | 含义 | |--------|------| | 0 | 成功 | | -1 | 未初始化 / 回调为空 | | 3 | 串口读取失败 | | 4 | 串口写入失败 | | -241 | 安装驱动失败(SCM 操作失败) | | -243 | 其他错误(注册回调/事件循环失败) | | -244 | 驱动文件不存在或设备打开失败 | | -252 | Key 无效 | | -253 | 启动驱动服务失败 | | -254 | 需要管理员权限 | | -255 | 绑定串口失败 | #### 3.1.3 DriverClient(驱动通信封装) **职责**:封装 `CreateFile` + `DeviceIoControl`,提供同步 IOCTL 调用接口。 | 方法 | 对应 IOCTL | 说明 | |------|-----------|------| | `Open()` | CreateFile | 打开 `\\.\CommModifyKit` 设备 | | `RegisterCallback()` | IOCTL_COMMKIT_REGISTER_CALLBACK | 注册回调管道 | | `AttachPort(com)` | IOCTL_COMMKIT_ATTACH_PORT | 绑定串口 | | `DetachPort(com)` | IOCTL_COMMKIT_DETACH_PORT | 解绑串口 | | `WritePort(com,data,len)` | IOCTL_COMMKIT_WRITE_PORT | 写数据(METHOD_IN_DIRECT) | | `ReadPort(com,data,len)` | IOCTL_COMMKIT_READ_PORT | 读请求(METHOD_IN_DIRECT) | | `ReadEvents(buf,max)` | IOCTL_COMMKIT_READ_EVENTS | 批量读取事件 | | `FreeAll()` | IOCTL_COMMKIT_FREE_ALL | 释放所有 | **WritePort/ReadPort 数据组装**:使用 `COMMKIT_DATA_REQUEST` 变长结构,实际大小 = `sizeof(COMMKIT_DATA_REQUEST) - 1 + len`(Data 字段为 1 字节占位)。 #### 3.1.4 EventLoop(事件循环线程) **职责**:独立工作线程循环从驱动读取事件,调用 `TOnData` 回调上报上层。 **工作流程**: ``` while (running_) { // 阻塞式 IOCTL:驱动无事件时 IRP pending DeviceIoControl(IOCTL_COMMKIT_READ_EVENTS, batch, ...); // 处理返回事件 for (event in batch) { callback_(event.Sequence, event.TimeStamp, ...); } } ``` **设计要点**: - **阻塞式读取**:IOCTL 在驱动端 pending,有事件时才返回,避免空转 - **批量读取**:单次最多读取 `COMMKIT_EVENT_BATCH`(16) 条事件 - **优雅停止**:`Stop()` 设置 `running_=false`,关闭驱动句柄使阻塞 IOCTL 返回错误,线程自然退出 - **错误重试**:非致命错误 sleep 50ms 后重试,避免空转 #### 3.1.5 ErrorStore(线程局部错误码) 使用 `thread_local int` 存储,与 Win32 `GetLastError()` 语义一致:每线程独立,记录最近一次操作结果。 #### 3.1.6 Logger(日志系统) - **输出方式**:`OutputDebugStringW`(DebugView 可捕获)+ 可选文件输出(`%TEMP%\CommModifyKit.log`) - **级别**:Debug / Info / Warning / Error,默认 Info - **格式**:`[2026-07-21 10:00:00][INF] message` - **独立实现**:不依赖 CommModifyService 的 Logger #### 3.1.7 DllMain(DLL 入口) **关键约束**:DllMain 在 Loader Lock 内执行,严禁: - `LoadLibrary` / `LoadLibraryEx`(递归加载死锁) - 复杂 STL 操作(`std::thread`、`std::mutex` 动态初始化) 因此 DllMain 仅执行 `DisableThreadLibraryCalls`,所有初始化延迟到首次导出函数调用。 --- ### 3.2 内核态驱动 #### 3.2.1 DriverEntry(KMDF 入口) - 初始化 WDF 驱动对象 - 调用 `CreateControlDevice()` 创建控制设备 `\\.\CommModifyKit` - 注册 `EvtDeviceAdd`(PnP 过滤设备创建)和 `EvtDriverUnload` #### 3.2.2 ClientConnection(连接管理) **职责**:维护 DLL 与驱动之间的连接状态和端口监控列表。 - **端口表**:`ports_table_[256]`,最多支持 256 个 COM 端口 - **环形缓冲**:每个端口首次 ATTACH 时分配 `EventRingBuffer`(非分页内存) - **IOCTL 处理**:HandleXxx 系列方法由 QueueCallback 调度 - **同步**:`KSPIN_LOCK` 保护端口表(IRQL 安全) **HandleAttachPort 流程**: 1. 查找端口上下文,不存在返回 `STATUS_DEVICE_DOES_NOT_EXIST` 2. 若端口无环形缓冲,分配并初始化 3. 推入 `OP_OPEN` 事件 4. 设置 `MonitoringEnabled = TRUE` **HandleReadEvents 流程**: 1. 遍历所有已启用端口的环形缓冲 2. 逐端口 `PopBatch()` 弹出事件到用户缓冲 3. 返回总字节数 #### 3.2.3 QueueCallback(IOCTL 分发) 基于 WDF I/O Queue 的 Sequential 派发模式,将 IOCTL 码路由到 `ClientConnection` 对应方法。 **控制设备创建流程**: ``` WdfControlDeviceInitAllocate(SDDL_DEVOBJ_SYS_ONLY_ADM) // 仅管理员可访问 → WdfDeviceInitAssignName("\Device\CommModifyKit") → WdfDeviceCreate() → WdfDeviceCreateSymbolicLink("\DosDevices\CommModifyKit") → WdfIoQueueCreate(Sequential) → WdfControlFinishInitializing() ``` #### 3.2.4 SerialFilter(串口 IRP 拦截) **职责**:作为上层过滤驱动 attach 到串口设备栈,拦截读写 IRP。 | 派遣例程 | 行为 | |---------|------| | `DispatchRead` | 标记 IRP Pending → 设置 `OnReadComplete` 完成例程 → `IoCallDriver` 下层 | | `DispatchWrite` | 标记 IRP Pending → 设置 `OnWriteComplete` 完成例程 → `IoCallDriver` 下层 | | `DispatchDeviceControl` | `IoSkipCurrentIrpStackLocation` → `IoCallDriver` 透传 | **CaptureIrpData**(IRP 完成后提取数据): - 从 IRP 的 `MdlAddress` 或 `SystemBuffer` 获取数据指针 - 从 `IoStatus.Information` 获取实际数据长度 - 调用 `EventRingBuffer.Push()` 写入环形缓冲 #### 3.2.5 EventRingBuffer(环形缓冲区) - **容量**:`COMMKIT_RING_CAPACITY` = 256 条/端口 - **同步**:`KSPIN_LOCK`(IRQL <= DISPATCH_LEVEL 安全) - **溢出策略**:满了覆盖最旧事件(tail 跟随 head),不阻塞 IRP 完成例程 - **内存**:非分页池分配(`ExAllocatePool2` + `POOL_FLAG_NON_PAGED`) - **批量弹出**:`PopBatch()` 一次弹出多条,减少锁竞争 #### 3.2.6 DeviceContext(设备上下文) 每个串口过滤设备关联的上下文结构: | 字段 | 类型 | 说明 | |------|------|------| | `ComNumber` | ULONG | COM 编号(0=未知) | | `MonitoringEnabled` | BOOLEAN | 是否已通过 ATTACH_PORT 启用 | | `RingBuffer` | PVOID | EventRingBuffer 实例指针 | | `LowerDevice` | PDEVICE_OBJECT | 下层真实串口设备 | | `WdfDevice` | WDFDEVICE | WDF 设备句柄 | --- ### 3.3 共享定义(common/) #### CommKitEvents.h — 事件类型常量 | 常量 | 值 | 含义 | |------|---|------| | `COMMKIT_OP_NONE` | 0 | 无事件 | | `COMMKIT_OP_OPEN` | 1 | 串口打开/开始监控 | | `COMMKIT_OP_READ` | 2 | 读取到数据 | | `COMMKIT_OP_WRITE` | 3 | 写入数据 | | `COMMKIT_OP_CLOSE` | 4 | 串口关闭/停止监控 | #### CommKitIoctl.h — IOCTL 码与数据结构 | IOCTL 码 | 功能号 | 方法 | 说明 | |----------|--------|------|------| | `IOCTL_COMMKIT_REGISTER_CALLBACK` | 0x800 | METHOD_BUFFERED | 注册回调 | | `IOCTL_COMMKIT_ATTACH_PORT` | 0x801 | METHOD_BUFFERED | 绑定串口 | | `IOCTL_COMMKIT_DETACH_PORT` | 0x802 | METHOD_BUFFERED | 解绑串口 | | `IOCTL_COMMKIT_WRITE_PORT` | 0x803 | METHOD_IN_DIRECT | 写数据 | | `IOCTL_COMMKIT_READ_PORT` | 0x804 | METHOD_IN_DIRECT | 读请求 | | `IOCTL_COMMKIT_READ_EVENTS` | 0x805 | METHOD_OUT_DIRECT | 批量读事件 | | `IOCTL_COMMKIT_FREE_ALL` | 0x806 | METHOD_BUFFERED | 释放全部 | **关键数据结构**(均 `#pragma pack(1)` 对齐): ```cpp // 事件结构(内核 → 用户态) struct COMMKIT_EVENT { INT32 Sequence; // 递增序列号 double TimeStamp; // OLE Automation date UINT32 ComNumber; // 串口编号 UINT32 EventType; // 事件类型 UINT32 DataSize; // 数据字节数(≤ 4096) CHAR Data[4096]; // 内联数据缓冲 }; // 端口请求(ATTACH/DETACH 输入) struct COMMKIT_PORT_REQUEST { UINT32 ComNumber; }; // 数据请求(WRITE/READ 输入,变长) struct COMMKIT_DATA_REQUEST { UINT32 ComNumber; UINT32 DataLen; CHAR Data[1]; // 变长数据起始 }; ``` **常量**: | 常量 | 值 | 说明 | |------|---|------| | `COMMKIT_MAX_DATA` | 4096 | 单条事件最大数据字节 | | `COMMKIT_EVENT_BATCH` | 16 | 单次 IOCTL 最多返回事件数 | | `COMMKIT_RING_CAPACITY` | 256 | 每端口环形缓冲容量 | --- ## 4. 关键设计决策 ### 4.1 Loader Lock 安全 DllMain 仅执行 `DisableThreadLibraryCalls`,所有初始化延迟到 `InitMonitor` 首次调用。PROCESS_DETACH 也不调用 `FreeAll`,由上层 `FreeMonitor()` 显式释放。 ### 4.2 SEH 异常保护 WorkerThread(EventLoop::Run)使用 `__try/__except` 包裹,防止 SEH 异常导致线程静默崩溃和初始化超时。 ### 4.3 超时处理使用 detach() 当 WorkerThread 超时未完成时,使用 `detach()` 而非 `join()`,避免主线程无限阻塞。 ### 4.4 环形缓冲溢出策略 满了覆盖最旧事件而非阻塞 IRP 完成例程,保证串口 I/O 性能不受监控影响。 ### 4.5 驱动服务常驻 `FreeMonitor` 不卸载驱动服务(不调用 `DeleteService`),驱动在系统生命周期内常驻,避免反复安装/卸载的开销和权限问题。 ### 4.6 1 字节 pack 对齐 所有跨边界(内核态↔用户态,WOW64)传递的结构使用 `#pragma pack(1)`,避免对齐差异导致数据损坏。 --- ## 5. 编译与构建 ### 5.1 用户态 DLL - **工具链**:MSVC v142(VS2019) - **平台**:Win32(x86)为主,x64 可选 - **配置**:Release / Debug - **输出**:`build\Release\Win32\CommModifyKit.dll` ### 5.2 内核态驱动 - **工具链**:WDK + MSVC(KMDF) - **平台**:x64(内核驱动仅 64 位) - **输出**:`build\Release\x64\CommModifyKit.sys` ### 5.3 解决方案配置 | 项目 | Win32 配置 | x64 配置 | |------|-----------|---------| | CommModifyKit (DLL) | Win32 | x64 | | CommModifyKitDriver | 映射到 x64 | x64 | --- ## 6. 与 CommModifyService 的集成 CommModifyService 通过 `GetProcAddress` 动态加载 DLL 的 7 个导出函数: ```cpp // CommModifyService/DllWrapper.cpp 伪代码 HMODULE hDll = LoadLibrary("CommModifyKit.dll"); auto pInitMonitor = (BOOL(__stdcall*)(wchar_t*, TOnData)) GetProcAddress(hDll, "InitMonitor"); auto pMonitor = (BOOL(__stdcall*)(DWORD)) GetProcAddress(hDll, "Monitor"); // ... 其余 5 个函数同理 ``` **调用时序**: ``` 1. InitMonitor(key, callback) // 初始化 2. Monitor(com_number) // 监控各端口 3. [循环] SetWrite_Data / SetRead_Data // 读写操作 4. [回调] TOnData // 事件到达 5. Stop(com_number) // 停止某端口 6. FreeMonitor() // 全部释放 ```