基于Qt5和hidapi的USB HID调试助手开发实战
简介基于Qt5与hidapi库编写的一款USB调试助手面向Windows 10环境下的嵌入式、上位机开发者帮助快速测试与调试支持HID协议的USB设备。项目以Qt5搭建交互界面并处理事件hidapi提供跨平台设备访问接口配合mingw工具链完成编译涵盖设备枚举、VID/PID信息读取、数据收发及错误处理等核心功能。压缩包共19个文件以C头文件与源文件.h/.cpp为主另含Qt工程配置.pro/.pri、主窗口界面描述.ui、Win平台运行所需的.dll/.lib库及.pdb调试符号整体大小约1004KB结构清晰便于对照学习。已有554人学习下载。资料中可直接参考主窗口类的UI编写方式、hidapi接口的调用流程以及设备通信的异常处理思路适合用来搭建自己的USB调试工具或理解Qt5与原生库的整合方法。1. 为什么说 HID 调试最需要的是一个你自己能改的上位机接手过 USB 外设联调的人都有这种体会厂商甩来一份协议文档剩下的事全靠自己折腾。上手 Qt5 和 hidapi 的组合在 Windows 10 上维护一个简单的 USB 调试助手是我每次联调新设备的固定动作。这个东西解决的是最具体的问题枚举出设备真实的 VID/PID、路径和使用页打开设备后处理 report ID稳定收发 Input/Output Report还能随时加日志、拼报文、做批量回归。现成工具不是只显示原始 hex就是过滤不了同型号多个设备改一次发送逻辑还得去问工具维护者。适合做嵌入式、驱动、硬件测试的工程师也适合拿到开发板想快速验证固件上报是否正确的开发者。它本质是一个上位机外壳核心价值是把 HID 这条链路完全变成你能掌控的代码。2. 选型为什么是 hidapi 而不是 Windows API 或 libusb2.1 在 Windows 10 上读 HID 的三种姿势先说结论在 Windows 10 上做 HID 设备的读写hidapi 几乎是 C/C 生态里的最优解。它不是图省事的妥协而是把其他两条路的痛点都拆干净了。第一种是直接调 Windows 原生 API。流程大致是用 HidD_GetHidGuid 拿到 HID 的 GUID走 SetupAPI 枚举设备路径再通过 HidD_GetAttributes 取 VID/PID然后用 CreateFile 打开设备最后 ReadFile/WriteFile 收发。这条路完全可行产出也很干净但工程量不小你要自己维护设备路径缓冲区处理 SetupDiGetClassDevs 返回的设备信息集还要面对 overlapped IO 和同步 IO 两套读取逻辑。而且 Windows 的 HID 栈会把 report ID、usage page、input report 长度这些信息拆散在 HIDP_CAPS、HIDP_Button_Caps 等结构里读起来非常繁琐。如果一个调试助手的核心逻辑只有三个函数却要花一半时间在 Windows 专属结构体上那这个选型的性价比就很低。第二种是 libusb。它的传输能力强但有一个关键矛盾Windows 10 上绝大多数 HID 设备默认被系统自带的 hidusb.sys 驱动接管libusb 想直接访问 HID 设备的 endpoint得先把设备驱动替换成 WinUSB。对键盘、鼠标这类标准 HID 设备换驱动之后系统功能就直接废掉对自定义的 HID 外设换驱动也意味着固件里的 HID 描述符可能不被系统完整识别。一个调试助手工具不应该要求使用方去动设备驱动这个成本太高也容易把设备搞坏。第三种就是 hidapi。它在 Windows 后端用的是 SetupAPI 加系统 HID 驱动不需要替换驱动上层 API 则非常干净枚举、打开、按路径打开、读写、feature report 全部封装成一组普通 C 函数。移植性也好同一套上层代码以后丢到 Linux 或者 macOS 上重新编译就能跑这对跨平台工具来说是很实际的好处。方案驱动要求枚举复杂度读写复杂度适合场景Windows SDKSetupAPI HID无高需处理 GUID 和设备信息集高overlapped IO 要自己写对系统 API 极熟、不打算换平台的维护者libusb需换 WinUSB 驱动中中非 HID 的 BULK/中断传输、或愿意处理驱动的场景hidapi无直接用系统 HID 驱动低低HID 设备的枚举、调试、上位机集成2.2 hidapi 的 API 构成与一次完整的「枚举 → 打开 → 读写 → 关闭」生命周期hidapi 的接口不多我把实际用到的列一下你会发现它基本就是 HID 设备的全部操作面hid_init / hid_exit库初始化和清理在进程生命周期里做一次就行。hid_enumerate / hid_free_enumeration按 VID/PID 枚举传 0 表示全部返回一条链表用完必须释放否则每次枚举都漏内存。hid_open / hid_open_path要么按 VID/PID 打开要么按枚举出来的路径精确打开。hid_write / hid_read_timeout / hid_read写 Output Report、读 Input Report、带超时读。hid_send_feature_report / hid_get_feature_report操作 Feature Report这一对函数在很多调试工具里被漏掉但对藏配置类命令的设备特别有用。hid_set_nonblocking切换非阻塞模式Windows 上我一般用超时读而不是非阻塞轮询原因后面避坑章会讲。hid_error取最后一次错误注意传入的设备句柄不能是空指针。最朴素的一次完整调用链是这样的#include hidapi.h int demoOnce(unsigned short vid, unsigned short pid) { if (hid_init() ! 0) { return -1; // 极少数机器上初始化会失败一般和系统中 HID 栈异常有关 } hid_device *dev hid_open(vid, pid, nullptr); if (!dev) { hid_exit(); return -2; } unsigned char send[2] {0x00, 0x5A}; // 首字节固定是 report ID0x00 表示设备没有 report ID hid_write(dev, send, sizeof(send)); unsigned char recv[65] {0}; int n hid_read_timeout(dev, recv, sizeof(recv), 200); // 最长等 200ms if (n 0) { // recv[0] 是 report IDrecv[1..n-1] 才是负载 } hid_close(dev); hid_exit(); return n; }这段代码里有几个细节必须展开讲。第一个是首字节hidapi 的读写缓冲区里第一个字节永远是 report ID哪怕设备没有定义 report ID也要在写入时填 0x00。这是新手最容易忽略的一点。第二个是接收缓冲区建议按「设备 Input Report 里声明的最大长度 1」来申请多一个字节是给 report ID 留的位置Windows 上如果缓冲区不够ReadFile 直接返回截断数据不报错这个问题非常阴险。第三个是 hid_read_timeout 的 200ms 超时这段时间线程会挂起所以任何生产级别的代码都不能在主线程里直接调它要放到工作线程里。hidapi 的双层设计在这里体现得很清楚底层对不同操作系统的差异做了统一处理上层不用关心自己被系统 HID 驱动接管之后具体发生了什么。2.3 什么时候不该用 hidapi这点看不清会翻车讲边界之前先说一个容易误导人的地方有些人把 hidapi 当成「USB 万能库」什么设备都拿它打开结果读不出数据就怀疑是库的问题。实际上 hidapi 只处理 HID 类设备一个外设如果枚举出来的接口类是 0x03HID才能走这条路如果是 USB-CDC虚拟串口、USB Mass Storage、或者自定义 BULK 传输的接口hidapi 帮不上忙。CDC 要用串口库BULK 传输要么换 WinUSB 直连要么上 libusb这是物理链路决定的不是库能突破的。另一种不该用的情况是设备已经被系统其他程序独占打开。Windows 的 HID 设备允许共享读但写事件和某些配置类操作可能会有冲突。hidapi 本身不处理设备占用仲裁真遇上打开失败第一反应应该是查占用而不是改库参数。还有一点是关于 Feature Report 的很多简化的 HID 调试工具只做了 Input/Output Report但不少设备把固件版本读取、参数校准、生产测试命令都放在 Feature Report 里。hidapi 对这类操作是有完整支持的选型的时候要确认自己需要的是哪一层的报文类型免得搭好界面才发现发送函数根本没有覆盖 feature report。3. 工程搭建CMake 工程、设备枚举与收发线程的完整骨架3.1 依赖引入vcpkg 安装 hidapi 与 CMake 链接搭建工程我最常用的路径是 vcpkg 装库。在 Windows 10 上打开 PowerShell执行安装命令# 用 vcpkg 安装 hidapi会自动拉取源码编译当前平台的库 vcpkg install hidapi安装完成后CMake 里通过 toolchain 机制把 vcpkg 的包信息引进来。这里最容易踩的坑是只设置了 CMAKE_PREFIX_PATH 指向 Qt导致 find_package(hidapi) 找不到库。正确做法是配置时指定 vcpkg 的 toolchain 文件cmake_minimum_required(VERSION 3.16) project(usb_debug_helper LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 通过 toolchain 引入 vcpkg 后hidapi 和 Qt5 都能被正确找到 find_package(Qt5 COMPONENTS Widgets REQUIRED) find_package(hidapi CONFIG REQUIRED) add_executable(usb_debug_helper main.cpp mainwindow.cpp mainwindow.h ) target_link_libraries(usb_debug_helper PRIVATE Qt5::Widgets hidapi::hidapi )这里的 target_link_libraries 里 Qt5::Widgets 是 Qt5 的 CMake 目标名hidapi::hidapi 是 hidapi 官方 CMake 包导出的目标名。如果你不想用 vcpkg也可以直接把 hidapi 源码里的 Windows 实现文件hid.c 和 hidapi.h拷进你的工程然后用 target_link_libraries 补上 setupapi、hid 这两个系统库效果一样只是更新上游代码时要自己维护。我一般会优先 vcpkg因为后续换 hidapi 新版本时只需重新 install不用手工改源码。3.2 枚举模块把 VID/PID、设备路径、usage 信息拉出来枚举是所有后续逻辑的地基也是排查设备问题最重要的入口。我建议第一步先做一个独立的枚举函数把所有字段拷贝到自建的设备信息结构体里。要特别注意hid_device_info 返回的链表里 path、product_string 这些指针指向的内存会在调用 hid_free_enumeration 之后立刻失效不能直接把指针存起来。#include hidapi.h #include QString #include QByteArray #include cstdio #include vector struct DeviceInfo { std::string path; // 设备路径Windows 上是 \?\hid#vid_xxxxpid_xxxx#... 形式 unsigned short vid 0; unsigned short pid 0; unsigned short usage_page 0; // HID 使用页过滤键盘/鼠标类设备时很关键 unsigned short usage 0; // HID 用法配合 usage_page 使用 QString serial; QString product; }; std::vectorDeviceInfo listHidDevices() { std::vectorDeviceInfo out; hid_device_info *devs hid_enumerate(0x0000, 0x0000); // 0/0 表示枚举全部 for (hid_device_info *cur devs; cur; cur cur-next) { DeviceInfo info; info.path cur-path ? cur-path : ; info.vid cur-vendor_id; info.pid cur-product_id; info.usage_page cur-usage_page; info.usage cur-usage; info.serial QString::fromWCharArray(cur-serial_number ? cur-serial_number : L); info.product QString::fromWCharArray(cur-product_string ? cur-product_string : L); out.push_back(std::move(info)); } hid_free_enumeration(devs); return out; }逻辑说明hid_enumerate(0x0000, 0x0000) 的两个参数分别表示 vendor_id 和 product_id传 0 是拉回所有 HID 设备。循环里我把原始指针转化为 QString 和 std::string这是为了规避 hid_free_enumeration 之后指针失效的问题。返回的 vector 可以安全地在 Qt 界面里用来填充下拉框也可以打印到日志窗口里。枚举结果类似这样path: \?\hid#vid_1234pid_0007#728f46cb900000#{4d1e55b2-f16f-11cf-88cb-001111000030} vid/pid: 1234:0007 usage_page/usage: 0001:0006 product: USB HID Example3.3 打开与读写report ID、超时参数与读取线程打开设备要区分两种场景只插了一个设备时按 VID/PID 打开就够了插了多个同型号设备时必须用 path 来精确定位。hidapi 提供 hid_open_path 函数Windows 上它接收的就是我们在枚举里存下来的 path 字符串。#include hidapi.h #include vector #include cstring #include cstdio hid_device *openDevice(unsigned short vid, unsigned short pid) { hid_device *dev hid_open(vid, pid, nullptr); if (!dev) { // dev 是空指针时不要调用 hid_error(dev)Windows 上拿不到有效信息反而可能崩溃 std::fprintf(stderr, open failed, check device driver and occupancy\n); return nullptr; } return dev; } bool sendOutputReport(hid_device *dev, unsigned char reportId, const unsigned char *payload, int payloadLen) { std::vectorunsigned char buf(payloadLen 1); buf[0] reportId; // 首字节固定是 report ID std::memcpy(buf.data() 1, payload, payloadLen); int ret hid_write(dev, buf.data(), buf.size()); if (ret 0) { std::fprintf(stderr, hid_write error\n); return false; } // ret 是实际写入的字节数正常应该等于 buf.size() return true; }参数说明sendOutputReport 的 reportId 是 Output Report 的 ID。如果设备只有一种 Output Report很多固件会约定 report ID 为 0x00这种情况下调用时传 0x00 即可。hid_write 的第二个参数是数据指针第三个参数是长度返回值小于 0 表示失败。注意 hidapi 不会替你做 retryWe在调试阶段宁可让它失败返回也不要在这里加重发逻辑否则日志里看到的报文顺序会失真。接收侧是容易写出死循环的地方。最稳的结构是独立线程里跑超时读#include QThread #include QByteArray #include atomic class ReadWorker : public QThread { Q_OBJECT public: ReadWorker(hid_device *dev, QObject *parent nullptr) : QThread(parent), m_dev(dev) {} void stop() { m_running false; } signals: void dataReady(unsigned char reportId, QByteArray payload); protected: void run() override { unsigned char buf[65] {0}; while (m_running.load()) { int n hid_read_timeout(m_dev, buf, sizeof(buf), 100); if (n 0) { m_running false; // 设备被拔出或底层 IO 错误必须退出循环 break; } else if (n 0) { unsigned char rid buf[0]; QByteArray payload(reinterpret_castconst char *(buf 1), n - 1); emit dataReady(rid, payload); } } } private: hid_device *m_dev; std::atomicbool m_running{true}; };这个类的关键点是超时间隔 100ms。它身兼两个职责平时是读取节奏控制设备一有数据最多延迟 100ms 就能上报退出时是止损闸门stop() 置位后循环在下一个超时点上一定会发现标志位被清掉然后自动跳出 run()。hid_read_timeout 的第三个参数单位是毫秒传 0 等同于非阻塞读传负数等同于无限阻塞。无限阻塞在退出时是噩梦因为 Windows 的 hidapi 实现没有跨平台统一的取消机制关设备也没法保证打断阻塞读所以超时轮询是 Windows 10 上最安全的退出方案。3.4 界面骨架Qt5 Widgets 最简单的主窗口整个界面不需要很复杂核心就四个控件设备下拉框、打开按钮、十六进制输入框、发送按钮、日志显示区。控件装配逻辑如下#include QComboBox #include QPushButton #include QLineEdit #include QTextEdit #include QFormLayout #include QHBoxLayout #include QVBoxLayout // 构造函数里装配控件 m_deviceCombo new QComboBox(this); m_openBtn new QPushButton(QStringLiteral(打开设备), this); m_sendBtn new QPushButton(QStringLiteral(发送), this); m_input new QLineEdit(this); m_input-setPlaceholderText(QStringLiteral(十六进制如 01 02 03 04)); m_log new QTextEdit(this); m_log-setReadOnly(true); auto *form new QFormLayout; form-addRow(QStringLiteral(设备), m_deviceCombo); form-addRow(m_openBtn); auto *sendRow new QHBoxLayout; sendRow-addWidget(m_input, 1); sendRow-addWidget(m_sendBtn); auto *mainLayout new QVBoxLayout(this); mainLayout-addLayout(form); mainLayout-addLayout(sendRow); mainLayout-addWidget(m_log, 1);发送前要把输入的十六进制文本解析成字节数组。这里有一个容易被忽略的坑用户输入可能是带空格的字符串也可能有人手滑输入了「0x」前缀解析函数要能容忍这些情况#include QStringList QByteArray parseHex(const QString text) { QByteArray out; const QStringList tokens text.trimmed().split( , Qt::SkipEmptyParts); for (const QString token : tokens) { QString clean token; if (clean.startsWith(0x, Qt::CaseInsensitive)) { clean clean.mid(2); // 去掉用户手输的 0x 前缀 } bool ok false; out.append(static_castchar(clean.toUInt(ok, 16))); if (!ok) { return QByteArray(); // 解析失败返回空数组让调用方提示用户 } } return out; }注意 Qt::SkipEmptyParts 在比较新的 Qt5 里是推荐写法如果你手里的 Qt5 版本比较旧可以改成 QString::SkipEmptyParts效果一样。发送按钮的 connect 里先 openDevice 拿到句柄再把 parseHex 的结果塞进 sendOutputReport日志区输出实际发送的字节。这样一个能用的调试助手已经跑起来了。下一步要做的就是把它放进真实场景里接受毒打。4. 避坑清单Windows 10 下 HID 调试的五个常见翻车点这部分内容全部来自我在 Windows 10 上反复踩过的教训每一条都是按「现象 → 原因 → 解决」记录的。你是新手也好熟手也罢先看一遍至少能省掉半天网上搜答案的时间。4.1 枚举不到设备或打开返回失败现象设备管理器里明明能看到设备hid_enumerate 却枚举不到或者枚举到了hid_open 返回空指针。原因分两类。一类是设备被系统 HID 驱动外的其他程序占用Windows 上 HID 设备允许共享读但某些工具以独占方式打开设备句柄会导致后续打开失败。另一类是驱动还没完全就绪比如设备刚插入、蓝牙 HID 还在配对、或者是复合设备里多个 interface 都在枚举过程中此时列表里能看到设备但路径不可用。解决先用 PowerShell 确认设备站在系统层面是否正常。在 Windows 10 的 PowerShell 里执行# 输出系统当前所有 HID 设备的友好名称和实例路径 Get-PnpDevice -PresentOnly | Where-Object { $_.Class -eq HID } | Format-List FriendlyName, InstanceId对照 InstanceId 和程序打印出来的 path能看到 VID/PID 段是不是一致。如果系统里正常而程序枚举不到把设备禁用再启用一次或者干脆拔插一次。如果 hid_open 失败先把所有可能占用的调试工具关掉再用管理员权限跑一次你的程序。管理员权限能避开一部分权限拒问题虽然不是根治但能快速确认问题方向。4.2 hid_read_timeout 超时返回 0 之后一直读不到数据现象设备明明在周期上报但 hid_read_timeout 连续多次返回 0偶尔能读到一次之后又变成 0。原因Windows 后端的 hidapi 读取是基于 overlapped IO 实现的超时参数为 0 时它会频繁发起非阻塞读内部的事件状态在高速轮询下容易产生「读完成但没数据可取」的假象。另外一部分设备驱动上报节奏比较特殊比如只在收到命令后回一条这种设备配上非阻塞轮询读不到是常态不是设备坏了。解决我的经验是读取线程里不用 0 超时也不用忙轮询统一用 100ms 左右的超时阻塞读。如果设备是「请求-应答」式的确保每次发送后在读取线程里留至少一个超时周期的等待不要发完立刻读。如果改完超时还是读不到重新打开一次设备句柄。Windows 上有些 HID 驱动状态在超时后不会自动恢复重开是成本最低的后悔药。4.3 report ID 第一字节翻车现象hid_write 返回正数看起来写入成功设备却毫无反应或者设备把收到的数据当成乱码处理。原因hidapi 的读写缓冲约定是「第一字节必须是 report ID」。问题出在两种设备上一种是设备没有定义 report ID此时第一字节要填 0x00很多人直接填了第一个 payload 字节导致整个报文错位另一种是设备定义了多个 report ID上层代码忘记把 report ID 拼进缓冲区固件按错误的 ID 解析自然没有响应。解决发送函数里统一保留第一字节payload 从 index 1 开始填充。像上面 sendOutputReport 那样把 reportId 显式作为参数传入不允许调用方自由构造完整缓冲区。这样即使填错日志里也能一眼看出来。另外打开设备后先读一次设备的 HID 描述符用 hid_get_feature_report 或者让厂商提供报告描述符文档确认每个 report 的 ID 和长度不要在没确认的情况下用 0x00 去试探。4.4 多个相同 VID/PID 设备串台现象插了两个同型号 USB 外设hid_open(vid, pid) 打开的总是第一个拔掉第一个之后第二个又能正常收到数据程序日志里的 path 却指向了已拔掉的设备。原因hid_open 只按 VID/PID 匹配不带任何路径信息Windows 枚举顺序不保证多设备场景下这种行为是必然的。同理序列号为空或者固件没写序列号时hid_open 的第三个参数 serial_number 也帮不上忙。解决枚举阶段就把 path 和 serial_number 全部缓存打开设备时用 hid_open_path 按路径精确打开。如果想实现“记住上次打开的设备”把上次成功打开设备的 path 存到配置文件里下一次启动直接按 path 打开能绕开 VID/PID 撞车。界面上下拉框里不要只显示 product 名称建议把 VID/PID 和 path 尾段也拼进去方便区分。4.5 线程退出卡死设备句柄永远关不掉现象程序关闭时窗口消失了但进程还在任务管理器里能看到进程占用或者在析构函数里调用 hid_close 时程序卡住不动。原因读取线程还在 hid_read_timeout 的阻塞等待里或者已经在 hid_read_timeout 的内部 IO 等待中此时主线程直接 wait() 它不退出接着调用 hid_closeWindows 底层的事件句柄可能被无效释放导致整个进程挂起。这个问题的本质是关闭顺序线程没停句柄不能关。解决按严格的顺序收尾。第一步调用 stop() 置位退出标志第二步 worker-wait() 等待线程跳出读取循环因为超时是 100ms大多数场景下 wait 会在 100ms 内返回第三步才调用 hid_close 关闭设备句柄最后 hid_exit。三步顺序不能乱任何提前 close 的动作都会把 Windows 后端的内部状态打乱。我当时在这里耗掉了一整个下午之后凡是涉及 hidapi 读取的线程我都强制在代码注释里写明三阶段关闭顺序。5. 进阶把调试助手改造成半自动验证工具5.1 数据监视与日志落盘当设备联调稳定之后调试助手的价值开始转向验证。最简单的升级是在 dataReady 信号处理里加时间戳、按设备细分文件。我的做法是每次打开设备时创建一个以「VID_PID_日期」命名的日志文件每一条接收记录写成「时间戳 reportID payloadHex」的格式。这个格式有个额外好处可以直接 diff 两轮固件的收发记录回归测试时不用人肉盯屏幕。有了带 report ID 的结构化日志你还可以在界面里加一个过滤条件比如只显示某个 report ID 的报文或者把某种特定长度的包高亮。这一步投入很小但对排查「设备时好时坏」的间歇性问题非常有效。5.2 用脚本驱动批量命令调试久了你会发现联调现场最耗时间的不是写代码而是反复手输同样一串命令去验证设备状态。把命令序列写成文本脚本让助手按行解析执行这是把调试工具变成验证工具的关键一步。# 每条命令三个字段延时时长(毫秒) reportID 十六进制负载 100 01 00 00 00 00 00 00 00 200 01 01 00 00 00 00 00 00 500 02 00 00 00 00 00 00 00配套的解析脚本不复杂我用 Python 写一个最小的示例# 按行读脚本前面数字是延时后面是报文 import time with open(case1.txt, encodingutf-8) as f: for line in f: line line.strip() if not line or line.startswith(#): continue parts line.split() delay_ms int(parts[0]) report [int(x, 16) for x in parts[1:]] time.sleep(delay_ms / 1000.0) write_report(report[0], report[1:]) # 复用同一个 hidapi 网关把这个思路再往前推一步就是在脚本里加断言列把期望回包的 report ID 和关键字节写进去跑一遍脚本后自动比对日志。我一般给每条命令后面追加一段期望值格式是「延时 发送报文 期望应答报文」执结后统计成功/失败条数。这样固件每改一版我把整个验证流程重新跑一遍结果一目了然。这个功能做完这个调试助手就已经不是「简单工具」而是你手头最顺手的回归验证台了。那次之后我也养成了一个习惯每次接一个新设备不急着写业务逻辑先跑一遍枚举、打开、读一次 feature report、打印完整的 path 和 report ID确认链路真实可用再动手。这套流程帮我避开了很多「看起来是库的问题、其实是对协议一知半解」的黑匣子时刻。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

树莓派4B安装ROS全攻略:从选型到实战的完整指南

树莓派4B安装ROS全攻略:从选型到实战的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 4:12:16 阅读更多 →
lite-code-editor:轻量级跨平台Markdown编辑器与算法可视化

lite-code-editor:轻量级跨平台Markdown编辑器与算法可视化

最近在整理编辑器的时候,我一直在纠结一件事:写代码有 IDE,写笔记有各种 Markdown 工具,但要把“笔记”和“演示”放在一起,却总得在两个窗口之间来回切。比如我记录快速排序的思路,文字写了一半&#xff0…

2026/10/16 4:11:15 阅读更多 →
Linux下ARP欺骗防御实战:从静态绑定到arptables加固

Linux下ARP欺骗防御实战:从静态绑定到arptables加固

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 4:11:15 阅读更多 →

最新新闻

GLSL mod() 取模函数深度解析:The Book of Shaders 中实现周期性平铺、索引与动画的基石

GLSL mod() 取模函数深度解析:The Book of Shaders 中实现周期性平铺、索引与动画的基石

【免费下载链接】thebookofshaders Step-by-step guide through the abstract and complex universe of Fragment Shaders. 项目地址: https://gitcode.com/gh_mirrors/th/thebookofshaders 点击查看 免费下载 导读 mod() 是 GLSL 中最基础也最高频的数学函数之一…

2026/10/16 5:52:44 阅读更多 →
ExplorerPatcher 配置指南:三套套餐把 Windows 11 任务栏与开始菜单改回 Win10 经典样式

ExplorerPatcher 配置指南:三套套餐把 Windows 11 任务栏与开始菜单改回 Win10 经典样式

ExplorerPatcher 配置指南:三套套餐把 Windows 11 任务栏与开始菜单改回 Win10 经典样式 【免费下载链接】ExplorerPatcher This project aims to enhance the working environment on Windows 项目地址: https://gitcode.com/GitHub_Trending/ex/ExplorerPatcher…

2026/10/16 5:52:44 阅读更多 →
qwerty-learner 自定义词库导入:从一份 JSON 文件到完整个人词库,10 分钟走通

qwerty-learner 自定义词库导入:从一份 JSON 文件到完整个人词库,10 分钟走通

qwerty-learner 自定义词库导入:从一份 JSON 文件到完整个人词库,10 分钟走通 【免费下载链接】qwerty-learner 为键盘工作者设计的单词记忆与英语肌肉记忆锻炼软件 / Words learning and English muscle memory training software designed for keyboar…

2026/10/16 5:52:44 阅读更多 →
AIRI Minecraft 集成:基于 Mineflayer 的 LLM 驱动 Bot 本地部署与配置全解

AIRI Minecraft 集成:基于 Mineflayer 的 LLM 驱动 Bot 本地部署与配置全解

人工智能AI AgentMCP 服务工具调用GUI 自动化语音数字人交互助手 【免费下载链接】airi 💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音聊天、Minecraft 和 F…

2026/10/16 5:52:44 阅读更多 →
Python yield机制详解:从生成器到协程的核心原理与实践

Python yield机制详解:从生成器到协程的核心原理与实践

很多Python开发者接触到yield,是先听说"生成器"这个词,然后背下"yield像return但能返回多次"这种解释。实际上这个理解非常危险,它把yield的真正价值完全掩盖了。yield不是"返回多次",而是把一段代…

2026/10/16 5:52:44 阅读更多 →
OpCore-Simplify 一键生成可用的 OpenCore EFI:黑苹果自动化配置五步指南

OpCore-Simplify 一键生成可用的 OpenCore EFI:黑苹果自动化配置五步指南

OpCore-Simplify 一键生成可用的 OpenCore EFI:黑苹果自动化配置五步指南 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 当你准备在一台旧…

2026/10/16 5:51:44 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →