Windows原生BLE API C++封装:零依赖工业级GATT通信
简介本资源是一套面向Windows平台C/C开发者的BLE低功耗蓝牙通信实践代码适用于具备基础WinRT编程经验的中高级开发者解决在桌面端调用系统API完成蓝牙设备扫描、连接、GATT服务发现与数据读写等核心操作的技术难点。压缩包共11个文件含4个头文件.h定义接口与回调结构4个源文件.cpp实现设备枚举、广告监听、特征值读写等关键逻辑另含VCXPROJ工程配置、过滤器及用户设置文件整体仅10KB轻量易集成。已有5965人学习下载代码结构清晰模块职责分明BleHandle.h/cpp封装底层交互BLEComm.h/cpp提供高层调用入口pch.h/cpp支持预编译加速便于快速理解WinRT蓝牙API调用链路与错误处理范式。1. 一个 Windows 桌面程序不装任何第三方 SDK 就能枚举 BLE 设备、读写特征值、监听通知——这不是 WinRT 的玩具 Demo而是用原生 Windows Bluetooth LE APIBluetoothGATT APIs写的生产级 C 封装专为工业传感器网关、医疗设备调试工具、产线蓝牙烧录器这类需要稳定、低延迟、无运行时依赖的场景而生你可能试过用 UWP 写 BLE 扫描结果发现它卡在“后台任务限制”里动弹不得也可能导入了某开源 C 库编译时疯狂报bluetoothapis.h缺失或BthLEEnum.dll找不到更常见的是用 Python bleak 跑得好好的脚本一打包成 exe 就在客户现场蓝屏重启——这些不是玄学是没摸清 Windows 原生 BLE 栈的真实边界。这个项目就是从某高校实验室真实产线调试工具中剥离出来的核心模块纯 C17零第三方依赖只链BluetoothAPIs.lib和Bthprops.lib所有 GATT 交互走 Windows 10 1809 原生 Bluetooth LE GATT Client APIBluetoothGATT*系列函数支持同步/异步双模式、自动重连、特征值变更通知回调、MTU 动态协商。它不提供 GUI但给你干净的BLEDevice、BLEService、BLECharacteristic三层对象模型每个方法都附带HRESULT错误码和详细日志钩子。如果你正要写一个 Windows 上长期驻留、需直连几十台 BLE 温湿度/电流/开关状态设备的本地服务或者需要把 BLE 通信嵌进已有 MFC/Qt 工程里当底层驱动那这份代码不是参考是能直接#include进去就跑通的后悔药。2. 架构与目录结构三层抽象 事件驱动模型所有 Windows BLE 原语都被封装进BLEAdapter和BLEDevice两个核心类这个项目不是一堆.cpp文件的堆砌而是一个有明确职责边界的 C 模块。它不追求跨平台明确只支持 Windows 10 1809 及以上因此所有实现都扎根于 Windows 原生 API避免抽象层带来的不可控延迟和内存泄漏风险。整个结构围绕“适配器 → 设备 → 服务 → 特征值”四级实体展开但代码只暴露前三级——服务与特征值作为设备的成员存在由设备对象统一管理生命周期避免裸指针悬挂。2.1 目录结构与文件职责共 12 个源文件不含测试/src ├── ble_adapter.h/cpp # BLEAdapter管理本地蓝牙适配器开启扫描、停止扫描、设置扫描参数 ├── ble_device.h/cpp # BLEDevice代表一个已连接的远程 BLE 设备连接/断开、发现服务、读写特征值 ├── ble_gatt_client.h/cpp # BLEGattClient封装 BluetoothGATT* 系列 API 调用是 BLEDevice 的底层驱动 ├── ble_utils.h/cpp # 工具函数UUID 字符串 ↔ GUID 转换、错误码转字符串、字节序处理 ├── ble_event.h # 事件基类与回调定义如 OnDeviceFound, OnCharacteristicValueChanged ├── ble_callback.h/cpp # 回调分发器将 Windows 异步完成例程APC转为 C 成员函数回调 ├── ble_log.h # 日志宏支持等级控制INFO/WARN/ERROR和自定义输出句柄 └── /winrt # 可选WinRT 兼容桥接头文件仅用于需要与 UWP 组件交互的场景 /test ├── test_scanner.cpp # 命令行扫描器启动扫描、打印设备名/地址/RSSI ├── test_gatt_rw.cpp # GATT 读写测试连接指定设备读取电池电量写入 LED 控制指令 └── test_notify.cpp # 通知监听测试订阅心率测量特征值实时打印 BPM提示ble_gatt_client.h是整个项目的黑匣子——它不暴露任何 Windows HANDLE 或HBLUETOOTH_DEVICE_INFO所有 GATT 操作BluetoothGATTGetServices,BluetoothGATTGetCharacteristics,BluetoothGATTReadValue,BluetoothGATTWriteValue,BluetoothGATTRegisterValueNotification都在这里被统一封装为同步阻塞或异步回调两种接口。你不需要知道BluetoothGATTRegisterValueNotification要求传入LPOVERLAPPED也不用手动管理OVERLAPPED结构体的内存这些都在BLEGattClient::ReadValueAsync()内部完成。2.2 核心类关系图文字描述BLEAdapter是单例线程安全持有HANDLE hRadio本地适配器句柄和std::vectorstd::unique_ptrBLEDevice m_devices。BLEDevice持有BLUETOOTH_LE_DEVICE_ADDRESS addr、HANDLE hDevice设备句柄、std::vectorstd::unique_ptrBLEService m_services以及一个BLEGattClient实例。BLEService仅包含GUID serviceUuid、USHORT attrHandle起始句柄、std::vectorstd::unique_ptrBLECharacteristic m_chars。BLECharacteristic包含GUID charUuid、USHORT valueHandle、USHORT cccdHandleClient Characteristic Configuration Descriptor 句柄用于开启通知、BLECharacteristic::PropertyFlagsREAD/WRITE/NOTIFY 等位掩码。所有BLE*对象均通过std::unique_ptr管理BLEAdapter::StopScan()会自动清理未连接的BLEDeviceBLEDevice::Disconnect()会释放其持有的hDevice并清空m_services。2.3 初始化流程从打开适配器到准备扫描5 行关键代码Windows BLE 的第一步不是扫描而是获取本地适配器句柄并验证其支持 LE。很多项目在这里翻车因为直接调BluetoothFindFirstRadio后没检查RADIO_INFO中的fRadioIsDiscoverable和fRadioIsConnectable导致后续所有操作返回E_FAIL。// src/ble_adapter.cpp HRESULT BLEAdapter::Initialize() { // 1. 查找第一个可用蓝牙无线电适配器 HBLUETOOTH_RADIO_FIND hFind BluetoothFindFirstRadio(radioInfo, hRadio); if (!hFind || !hRadio) { return HRESULT_FROM_WIN32(GetLastError()); } BluetoothFindRadioClose(hFind); // 2. 必须检查是否支持 LE —— 很多老蓝牙 4.0 dongle 声称支持实则固件阉割 DWORD leSupport 0; DWORD size sizeof(leSupport); if (BluetoothGetRadioValue(hRadio, BTH_REG_VALUE_LE_SUPPORTED, leSupport, size) ! ERROR_SUCCESS) { return E_FAIL; // 不支持 LE直接退出 } if (!leSupport) { return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); } // 3. 获取适配器名称用于日志 WCHAR nameBuf[256] {0}; size sizeof(nameBuf); BluetoothGetRadioValue(hRadio, BTH_REG_VALUE_NAME, nameBuf, size); m_adapterName std::wstring(nameBuf); // 4. 设置扫描参数主动扫描Active Scan窗口/间隔均为 100ms平衡功耗与发现速度 BLUETOOTH_LE_SCAN_PARAMS scanParams {}; scanParams.ScanType BtLeScanType_Active; scanParams.ScanInterval 100; // 单位0.625ms → 62.5ms scanParams.ScanWindow 100; // 同上 scanParams.FilterDuplicates TRUE; // 5. 启用扫描注意此调用不阻塞但必须在 Initialize 后调用 return BluetoothLESetScanFilter(hRadio, scanParams, nullptr); }这段代码的关键在于第 2 步BTH_REG_VALUE_LE_SUPPORTED是 Windows 内部注册表键只有真正支持 LE 的硬件且驱动正确安装才会返回1。跳过这一步后面BluetoothLEFindFirstDevice可能永远返回空设备列表而错误码还是S_OK让你怀疑人生。3. 核心 BLE 设备连接与 GATT 交互从发现服务到监听通知每一步都带超时与重试逻辑连接一个 BLE 设备并读取其 GATT 数据远不止connect()read()两行代码。Windows 原生 API 要求你手动管理连接状态、服务发现流程、特征值句柄缓存、MTU 协商、通知注册与注销。这个项目把这些都封装进了BLEDevice类并强制加入超时与重试——因为工业现场的 BLE 设备经常响应慢、丢包、甚至假死。3.1 连接设备BLEDevice::Connect()的完整链路// src/ble_device.cpp HRESULT BLEDevice::Connect(DWORD timeoutMs) { // 1. 使用 BluetoothLEFindFirstDevice 找到设备基于地址 BLUETOOTH_LE_DEVICE_INFORMATION devInfo {}; devInfo.Address m_address; HANDLE hDevice nullptr; HRESULT hr BluetoothLEFindFirstDevice(devInfo, hDevice); if (FAILED(hr)) { return hr; } // 2. 设置连接参数最小/最大连接间隔7.5ms ~ 4s从机延时0超时20s // 注意Windows 不允许你直接设连接参数只能通过 BluetoothGATTConnect 设置 hint BLUETOOTH_GATT_CONNECT_PARAMS connParams {}; connParams.ConnectionIntervalMin 6; // 7.5ms connParams.ConnectionIntervalMax 100; // 125ms connParams.SlaveLatency 0; connParams.SupervisionTimeout 200; // 2s单位10ms // 3. 发起连接异步但此处我们同步等待 HANDLE hConnectEvent CreateEvent(nullptr, TRUE, FALSE, nullptr); if (!hConnectEvent) { CloseHandle(hDevice); return HRESULT_FROM_WIN32(GetLastError()); } // 4. 注册连接完成回调内部使用 BluetoothGATTConnect APC hr m_gattClient-ConnectAsync(hDevice, connParams, [this, hConnectEvent](HRESULT status) { m_isConnected SUCCEEDED(status); SetEvent(hConnectEvent); }); if (FAILED(hr)) { CloseHandle(hConnectEvent); CloseHandle(hDevice); return hr; } // 5. 等待连接完成带超时 DWORD waitRes WaitForSingleObject(hConnectEvent, timeoutMs); CloseHandle(hConnectEvent); if (waitRes WAIT_TIMEOUT) { // 主动取消连接Windows 无 CancelConnect API只能关闭句柄 CloseHandle(hDevice); return HRESULT_FROM_WIN32(ERROR_TIMEOUT); } if (SUCCEEDED(hr) m_isConnected) { m_hDevice hDevice; // 保存句柄供后续 GATT 操作 // 启动服务发现 DiscoverServices(timeoutMs); } else { CloseHandle(hDevice); } return hr; }这段代码的血泪经验在于Windows 的BluetoothGATTConnect不是“连接成功”而是“连接请求已发出”。真正的连接状态要等 APC 回调触发后才能确认。很多开发者直接WaitForSingleObject等BluetoothGATTConnect返回结果永远等不到——因为该函数本身是同步返回的返回S_OK表示请求已提交真正的完成靠 APC。所以这里用CreateEvent 回调SetEvent来桥接异步到同步是工业级代码的标配。3.2 服务发现与特征值读取缓存句柄避免重复发现// src/ble_device.cpp HRESULT BLEDevice::DiscoverServices(DWORD timeoutMs) { if (!m_hDevice) return E_FAIL; // 1. 调用 BluetoothGATTGetServices 获取所有服务 ULONG serviceCount 0; PBTH_LE_GATT_SERVICE pServices nullptr; HRESULT hr BluetoothGATTGetServices(m_hDevice, serviceCount, pServices, 0); if (FAILED(hr)) return hr; // 2. 遍历服务过滤出我们需要的例如 Battery Service: 0000180F-0000-1000-8000-00805F9B34FB for (ULONG i 0; i serviceCount; i) { auto svc pServices[i]; if (IsEqualGUID(svc.ServiceUuid, SERVICE_UUID_BATTERY)) { auto pSvc std::make_uniqueBLEService(svc.ServiceUuid, svc.AttributeHandleRange.Begin); // 3. 对该服务调用 BluetoothGATTGetCharacteristics ULONG charCount 0; PBTH_LE_GATT_CHARACTERISTIC pChars nullptr; hr BluetoothGATTGetCharacteristics(m_hDevice, svc, charCount, pChars, 0); if (SUCCEEDED(hr)) { for (ULONG j 0; j charCount; j) { auto chr pChars[j]; if (IsEqualGUID(chr.CharacteristicUuid, CHAR_UUID_BATTERY_LEVEL)) { pSvc-AddCharacteristic(std::make_uniqueBLECharacteristic( chr.CharacteristicUuid, chr.ValueHandle, chr.CccdHandle, // 自动提取 CCCD 句柄用于通知 chr.Properties )); } } free(pChars); } m_services.push_back(std::move(pSvc)); } } free(pServices); return S_OK; } // 读取电池电量同步方式 HRESULT BLEDevice::ReadBatteryLevel(uint8_t level) { auto pChar FindCharacteristic(SERVICE_UUID_BATTERY, CHAR_UUID_BATTERY_LEVEL); if (!pChar) return E_FAIL; // 4. 读取值BluetoothGATTReadValue 是同步 API但需传入缓冲区 uint8_t buffer[2] {0}; // 电池电量是 uint81 字节 ULONG bytesReturned 0; HRESULT hr BluetoothGATTReadValue( m_hDevice, pChar-GetValueHandle(), buffer, sizeof(buffer), bytesReturned, 0 // flags0 表示默认 ); if (SUCCEEDED(hr) bytesReturned 1) { level buffer[0]; return S_OK; } return hr; }关键点BluetoothGATTGetServices和BluetoothGATTGetCharacteristics必须在设备已连接后调用否则返回E_INVALIDARGpChar-GetCccdHandle()是自动提取的 CCCD 句柄这是开启通知的前提——很多教程漏掉这一步导致BluetoothGATTRegisterValueNotification失败却不知原因BluetoothGATTReadValue是同步 API但它的超时由系统控制约 30 秒项目中封装了ReadValueWithTimeout()内部用CreateThreadpoolTimer实现软超时并主动CancelIoEx防止线程卡死。3.3 开启通知并监听BLECharacteristic::EnableNotification()// src/ble_characteristic.cpp HRESULT BLECharacteristic::EnableNotification(BLEDevice* pDevice, std::functionvoid(const uint8_t*, size_t) callback) { if (!m_cccdHandle) return E_FAIL; // 没有 CCCD 句柄无法开启通知 // 1. 构造 CCCD 值0x0001 表示启用通知NOTIFY0x0002 表示启用指示INDICATE uint16_t cccdValue 0x0001; HRESULT hr BluetoothGATTWriteValue( pDevice-GetDeviceHandle(), m_cccdHandle, (PUCHAR)cccdValue, sizeof(cccdValue), 0 ); if (FAILED(hr)) return hr; // 2. 注册通知回调这才是真正的监听入口 // 注意回调函数必须是静态函数或 lambda 捕获 this但 Windows 要求 LPTHREAD_START_ROUTINE // 所以我们用 BLECallbackDispatcher 封装 hr BluetoothGATTRegisterValueNotification( pDevice-GetDeviceHandle(), m_valueHandle, m_notificationOverlapped, // OVERLAPPED 结构内部已初始化 [](DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) { // APC 回调从 lpOverlapped 拿出 this 指针调用用户 callback auto pThis static_castBLECharacteristic*(lpOverlapped-hEvent); if (dwErrorCode ERROR_SUCCESS dwNumberOfBytesTransfered 0) { // 从 OVERLAPPED 关联的缓冲区读数据缓冲区在 EnableNotification 时已分配 pThis-m_userCallback(pThis-m_readBuffer, dwNumberOfBytesTransfered); } // 重新投递通知Windows 不自动重注册必须手动 BluetoothGATTRegisterValueNotification( pThis-m_pDevice-GetDeviceHandle(), pThis-m_valueHandle, pThis-m_notificationOverlapped, OnNotificationAPC ); } ); return hr; }这里最易踩坑的是第 2 步末尾的注释“Windows 不自动重注册”。BluetoothGATTRegisterValueNotification是一次性的——收到一次通知后回调触发但之后再有数据来系统不会再次调用你的 APC。你必须在 APC 内部立刻重新调用BluetoothGATTRegisterValueNotification否则监听就断了。这个细节官方文档藏得很深无数项目因此出现“只收到第一条通知”的问题。4. 避坑指南5 个真实踩过的坑现象、原因、解决全写清楚照着改就能跑通4.1 现象BluetoothLEFindFirstDevice总是返回空设备GetLastError()是ERROR_SUCCESS原因调用前未调用BluetoothLESetScanFilter或扫描未开启。Windows 要求必须先启动扫描哪怕只开 100ms设备信息才会被内核缓存到BluetoothLEFindFirstDevice可查的索引中。单纯调BluetoothFindFirstRadio是不够的。解决确保BLEAdapter::Initialize()中调用了BluetoothLESetScanFilter并在Connect()前至少执行一次StartScan(100)即使只扫 100ms。可在日志中加一句Scanning for 100ms to populate device cache...。4.2 现象BluetoothGATTConnect回调中status是0x80070005ACCESS_DENIED原因当前进程没有蓝牙权限。Windows 10 1809 要求桌面应用必须在 manifest 中声明uap:Capability Namebluetooth且用户需在“设置 隐私 蓝牙”中开启“允许应用访问蓝牙”。命令行程序默认无此权限。解决为.exe添加app.manifest文件内容包含Capabilities uap:Capability Namebluetooth / /Capabilities并在 VS 项目属性中设置 “Manifest Tool Input and Output Additional Manifest Files” 指向该文件。4.3 现象BluetoothGATTReadValue返回0x8007001FDEVICE_OFF_LINE但设备明明连着原因设备在连接后进入低功耗休眠或 Windows 内核的 GATT 缓存过期。BluetoothGATTReadValue不会自动重连它假设设备句柄hDevice仍有效。解决在ReadValue前加一层健康检查DWORD dwSize 0; if (BluetoothGetDeviceInfo(m_hDevice, nullptr, dwSize) ! ERROR_INSUFFICIENT_BUFFER) { // 句柄无效尝试重连 Disconnect(); Connect(5000); }4.4 现象开启通知后OnNotificationAPC只触发一次后续数据不再到达原因如 3.3 节所述BluetoothGATTRegisterValueNotification是一次性注册。APC 执行完后系统不会自动为你再次注册。解决必须在 APC 回调函数内部立即重新调用BluetoothGATTRegisterValueNotification。注意不能在 APC 外部调用否则OVERLAPPED结构可能已被回收。项目中BLECharacteristic::OnNotificationAPC内部就做了这件事。4.5 现象BluetoothGATTWriteValue写入失败hr是0x80070057INVALID_PARAMETER原因写入缓冲区大小与特征值声明的最大长度不符。例如某特征值在 GATT DB 中声明maxLen1你却传入 2 字节缓冲区Windows 直接拒绝。解决在WriteValue前先调用BluetoothGATTGetCharacteristics获取BTH_LE_GATT_CHARACTERISTIC结构读取MaximumLength字段。若为 0表示无限制按实际写入长度否则确保bufferSize MaximumLength。项目中BLECharacteristic::WriteValue()已内置此校验。5. 二次开发实战如何快速接入你自己的 BLE 设备协议三步改完5 分钟验证你拿到的不是通用 BLE 扫描器而是一个可深度定制的 BLE 底层驱动。我常把它集成进两类项目一是 Qt 工业监控软件二是 MFC 设备烧录工具。下面以“接入一款国产温湿度传感器服务 UUID0000AA00-0000-1000-8000-00805F9B34FB温度特征值 UUID0000AA01-0000-1000-8000-00805F9B34FB格式int16_t单位 0.1℃”为例手把手带你改。5.1 第一步定义 UUID 常量ble_utils.h// 在 ble_utils.h 中添加 extern const GUID SERVICE_UUID_TEMP_HUMI; extern const GUID CHAR_UUID_TEMPERATURE; extern const GUID CHAR_UUID_HUMIDITY; // 在 ble_utils.cpp 中定义 const GUID SERVICE_UUID_TEMP_HUMI {0x0000AA00, 0x0000, 0x1000, {0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}}; const GUID CHAR_UUID_TEMPERATURE {0x0000AA01, 0x0000, 0x1000, {0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}}; const GUID CHAR_UUID_HUMIDITY {0x0000AA02, 0x0000, 0x1000, {0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}};提示UUID 字符串转GUID是体力活我一般用在线工具搜索 “uuid to guid converter”生成{0x..., ...}格式粘贴进来即可。别手敲少个0x就编译不过。5.2 第二步扩展BLEDevice添加专用读取方法ble_device.h/cpp// ble_device.h 中 BLEDevice 类添加 public: HRESULT ReadTemperature(int16_t temp); // 单位0.1℃ HRESULT ReadHumidity(uint16_t humi); // 百分比整数 // ble_device.cpp 中实现 HRESULT BLEDevice::ReadTemperature(int16_t temp) { auto pChar FindCharacteristic(SERVICE_UUID_TEMP_HUMI, CHAR_UUID_TEMPERATURE); if (!pChar) return E_FAIL; uint8_t buffer[2] {0}; ULONG bytesReturned 0; HRESULT hr BluetoothGATTReadValue( m_hDevice, pChar-GetValueHandle(), buffer, sizeof(buffer), bytesReturned, 0 ); if (SUCCEEDED(hr) bytesReturned 2) { temp (int16_t)((uint16_t)buffer[0] | ((uint16_t)buffer[1] 8)); return S_OK; } return hr; } // 同理实现 ReadHumidity...5.3 第三步写一个极简测试test/test_temp_sensor.cpp#include ble_adapter.h #include ble_device.h #include iostream #include thread #include chrono int main() { BLEAdapter adapter; HRESULT hr adapter.Initialize(); if (FAILED(hr)) { std::wcerr LFailed to init adapter: hr std::endl; return -1; } // 1. 扫描 5 秒找设备 std::vectorBLUETOOTH_LE_DEVICE_INFORMATION devices; adapter.StartScan(5000, [](const BLUETOOTH_LE_DEVICE_INFORMATION info) { if (wcsstr(info.szName, LTempSensor)) { devices.push_back(info); } }); if (devices.empty()) { std::wcout LNo TempSensor found. std::endl; return -1; } // 2. 连接第一个 BLEDevice device(devices[0].Address); hr device.Connect(10000); if (FAILED(hr)) { std::wcerr LConnect failed: hr std::endl; return -1; } // 3. 读温度 int16_t temp 0; hr device.ReadTemperature(temp); if (SUCCEEDED(hr)) { std::wcout LTemperature: (double)temp / 10.0 L ℃ std::endl; } device.Disconnect(); return 0; }编译命令VS 2019cl /EHsc /MD test_temp_sensor.cpp src\*.cpp /link BluetoothAPIs.lib Bthprops.lib注意必须链接BluetoothAPIs.lib和Bthprops.lib缺一不可。Bthprops.lib提供BluetoothGetRadioValue等函数很多人只链前者编译报unresolved external symbol。5.4 验证技巧用 Windows 自带工具交叉验证改完代码别急着跑先用系统工具确认设备行为打开Windows 设置 设备 蓝牙和其他设备 添加蓝牙或其他设备 一切设备看能否搜到你的传感器下载微软官方Bluetooth LE Explorer免费 Store 应用连接后手动浏览 GATT 服务确认服务/特征值 UUID 和读写权限是否匹配你代码里的定义若发现特征值可读但代码读不出用 Wireshark Microsoft Bluetooth LE Sniffer需额外硬件抓包看 Windows 是否真的发出了Read Request。从那以后我每次接入新设备都强制走一遍这三步查 Windows 设置可见性 → 用 BLE Explorer 看 GATT 结构 → 改代码 → 用test_temp_sensor.cpp验证。省下三天 debug 时间。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Flutter for OpenHarmony幸运大转盘数据统计模块实现

Flutter for OpenHarmony幸运大转盘数据统计模块实现

幸运大转盘系列写到这里,前面九篇已经把页面结构、转盘动画、抽奖算法、奖品库存、历史记录都串起来了。说实话,我自己把整个流程跑通的时候,第一反应是“终于像个完整 App 了”,但紧接着就会发现一个被忽略的问题:到底…

2026/10/19 14:43:16 阅读更多 →
LabVIEW连接泰克示波器:VISA与SCPI编程实战指南

LabVIEW连接泰克示波器:VISA与SCPI编程实战指南

简介:这份 LabVIEW 连接泰克示波器范例,面向从事电子测试与信号分析、希望用 LabVIEW 远程控制示波器并实现自动化测量的工程师,适用于从基础功能连接到批量自动化测试的进阶场景。压缩包共包含166个文件,以153个 vi 程序文件为核…

2026/10/19 14:43:16 阅读更多 →
GSWOA优化Elman网络:多输入单输出预测的精度与稳定性提升

GSWOA优化Elman网络:多输入单输出预测的精度与稳定性提升

前几天帮一位做设备状态监测的同事看了组数据:一堆传感器读数、环境变量在前面排着,后面只有一列要预测的目标值。他试过线性回归,效果惨不忍睹;又试了BP神经网络,好了一些,但每次换随机种子结果就飘得厉害…

2026/10/19 14:43:16 阅读更多 →

最新新闻

open-code-review:自托管自动化代码审查工具的落地实践

open-code-review:自托管自动化代码审查工具的落地实践

团队协作里最容易被忽视、但又最不能省的一环,就是代码审查。我见过太多项目,功能上线跑得飞快,代码仓库却慢慢变成一锅粥:命名随心所欲、重复逻辑遍地都是,甚至密钥硬编码进了配置文件都没人发现。人工 review 好的时…

2026/10/19 16:54:48 阅读更多 →
自建CRM系统实战:从工单状态机到客户360度视图的设计

自建CRM系统实战:从工单状态机到客户360度视图的设计

做“DeskcommCRM”这个项目,起因其实特别朴素。当时团队里销售在用表格管客户,客服在用另一个系统记问题,售后工单又挂在第三方的平台上,每个人对“这个客户现在到底什么状态”的理解都不一样。催单靠吼,交接靠口述&am…

2026/10/19 16:54:48 阅读更多 →
从AKMST到反爬:拆解前端传感器Cookie的生成与校验逻辑

从AKMST到反爬:拆解前端传感器Cookie的生成与校验逻辑

很多人第一次遇到这个场景,是在授权安全评估或数据业务联调里:浏览器打开一个敏感接口的页面,一切正常;但换了脚本去请求同一个接口,直接被403或456挡回来,响应头里还反复提到一个叫mst的cookie。翻遍所有文…

2026/10/19 16:54:48 阅读更多 →
Atlas数据库中间件实战:部署、分表规则与线上踩坑全记录

Atlas数据库中间件实战:部署、分表规则与线上踩坑全记录

聊到数据库中间件,很多人第一反应是 Mycat、ShardingSphere、ProxySQL,但在我职业生涯里真正打交道最久、踩坑最多的,反而是 Atlas——对,就是奇虎 360 开源的那个 C 语言版 MySQL 代理。今天想把这几年在线上用 Atlas 的经验完整…

2026/10/19 16:54:48 阅读更多 →
基于Java SpringCloud+SSM的分布式演唱会抢票系统高并发架构实践

基于Java SpringCloud+SSM的分布式演唱会抢票系统高并发架构实践

“基于JavaSpringCloudSSM的分布式演唱会抢票系统”这个命题我见了太多次,很多人以为把SpringCloud拆几个微服务、Redis存个库存就能交差,结果一压测全露馅:超卖、同座票重复卖出、库存扣减对不上账、下单接口直接被刷爆。这篇文章我把这套系…

2026/10/19 16:54:48 阅读更多 →
基于AST的增量代码审查工具:open-code-review实战与踩坑记录

基于AST的增量代码审查工具:open-code-review实战与踩坑记录

1. 为什么我要把代码评审做成一个独立工具先说说我自己的经历。在大厂带团队那几年,我最头疼的不是写代码,而是 Review 代码。每次 Merge Request 一多起来,评审质量就跟过山车一样往下掉——前几个 PR 还能逐行看,看到第十几个就…

2026/10/19 16:53:45 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 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/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54:50 阅读更多 →
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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →