简介:本资源是一套基于Windows平台的C/C++ BLE蓝牙开发实践工程,面向嵌入式开发者、Windows桌面应用工程师及物联网通信初学者,解决在WinRT环境下调用系统API完成低功耗蓝牙设备扫描、连接、GATT服务发现与数据读写等核心操作的技术落地问题。压缩包为10KB ZIP格式,共含11个文件,包括4个头文件(.h)封装BLE通信接口与类定义,4个源文件(.cpp)实现设备枚举、广告监听、特征值读写等关键逻辑,另含项目配置文件(.vcxproj)、过滤器(.filters)及用户设置(.user),结构完整,可直接加载Visual Studio编译运行。已有5965人学习下载,提供从WinRT头文件引入(如Windows.Devices.Bluetooth.Advertisement)、异步回调处理到GATT协议交互的完整代码链路,涵盖pch预编译支持、DLL主框架与独立BleHandle管理模块,便于理解Windows蓝牙驱动层与应用层的协同机制。
1. C/C++ Windows BLE 开发:别被 WinRT API 和传统 BluetoothStack 套牢,一套能跑通的本地化开发环境才是硬通货
你是不是也试过在 Windows 上用 C/C++ 写个 BLE 扫描器,结果编译通过、运行无声、设备列表永远为空?或者好不容易调通了BluetoothLEDevice::FromIdAsync,却卡死在await线程上,一查才发现自己误用了 UI 线程模型;又或者在 VS2022 里加了windows.foundation.universalapcontracts引用,生成时却报错“找不到 Windows.Foundation.h”——这些不是你代码写得差,而是 Windows BLE 的 C/C++ 开发路径本身就有三道隐形门槛:API 层级混乱(Win32 vs WinRT)、运行时依赖不透明(是否启用 Bluetooth Support、是否开启开发者模式)、以及最关键的——头文件与元数据(.winmd)版本必须严格对齐系统 SDK 和目标 Windows 版本。这套环境不是装完 VS 就能开干的“开箱即用”,它更像一个需要手动校准的精密仪器。本文面向的是正在做嵌入式 BLE 设备配套 PC 工具、工业蓝牙网关客户端、或需要脱离 UWP 框架做轻量级后台服务的 C/C++ 开发者。如果你的目标是写一个能在 Windows 10/11 上稳定发现、连接、读写 GATT 特征值的控制台程序,而不是发布到 Microsoft Store 的应用,那这篇就是为你写的落地笔记。
2. 环境准备:Windows SDK、工具链与运行时依赖的精准匹配
Windows BLE 的 C/C++ 开发不是纯 Win32 编程,它重度依赖 Windows Runtime(WinRT)类型系统和异步模型。这意味着你不能只靠#include <windows.h>就搞定一切。必须明确三件事:你用哪个 Windows SDK 版本构建?你的目标最低 Windows 版本是多少?你是否接受使用 C++/WinRT(推荐)还是坚持用原始 C++/CX(已弃用)?下面按真实开发节奏展开。
2.1 确认并安装对应 Windows SDK
BLE 相关 API(如Windows::Devices::Bluetooth::Advertisement::BluetoothLEAdvertisementWatcher)首次完整支持是在 Windows 10 Anniversary Update(1607,Build 14393)。但强烈建议最低目标设为 Windows 10 1809(Build 17763),原因有二:一是该版本起BluetoothLEDevice::FromIdAsync支持后台连接(无需 UI 线程),二是 SDK 中windows.devices.bluetooth.advertisement.h头文件结构更稳定,避免早期版本中因#ifdef WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)导致的符号缺失问题。
提示:不要在 Visual Studio 安装器里勾选“最新版 Windows SDK”——它可能默认装的是 Windows 11 SDK(如 10.0.22621.0),而你的测试机是 Windows 10 21H2(Build 19044),会导致运行时报
0x80070002(找不到指定模块)。务必手动勾选Windows 10 SDK (10.0.19041.0)或10.0.17763.0,并在项目属性 → General → Windows SDK Version 中显式指定。
2.2 安装 C++/WinRT 工具链(非可选,是必需)
C++/WinRT 是微软官方推荐、完全标准 C++17 的 WinRT 绑定方案,取代了已停更的 C++/CX。它不依赖/clr或特殊编译器开关,生成的是纯原生代码。安装方式极简:
# 在 PowerShell(管理员权限)中执行 winget install Microsoft.CppWinRT安装后,你会得到cppwinrt.exe工具(通常位于C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\),它用于从.winmd文件生成标准 C++ 头文件。但注意:你不需要手动运行它来生成 BLE 头文件——Windows SDK 已预置了winrt/Windows.Devices.Bluetooth.Advertisement.h等封装头。C++/WinRT 的真正价值在于提供winrt::init_apartment()初始化、co_await支持、以及类型安全的winrt::hstring/winrt::guid封装。
2.3 验证运行时依赖:三个必须打开的系统开关
即使代码编译通过,Windows BLE API 在运行时仍会静默失败。请逐项确认以下三项:
Bluetooth Support 服务必须运行:
services.msc→ 找到 “Bluetooth Support Service” → 启动类型设为“自动(延迟启动)”,并确保状态为“正在运行”。这是 WinRT BLE API 的底层驱动桥接服务,关闭则所有BluetoothLEAdvertisementWatcher创建均返回nullptr。开发者模式必须启用:
设置 → 更新与安全 → 对于开发人员 → 启用“开发者模式”。此开关解锁 WinRT API 的部分受限能力(尤其是后台 GATT 读写),未启用时GattCharacteristic::WriteValueAsync可能抛出E_ACCESSDENIED。蓝牙硬件与驱动需通过 WHQL 认证:
进入设备管理器 → 展开“蓝牙” → 右键你的蓝牙适配器 → 属性 → 驱动程序 → 驱动程序详细信息 → 查看.inf文件路径。若路径含nonms或unsigned字样,或签名日期早于 2018 年,建议前往厂商官网更新驱动。实测某 OEM 笔记本自带蓝牙芯片(Intel Wireless Bluetooth 9460)在旧驱动下BluetoothLEDevice::FromIdAsync永远超时,升级至 22.x 版本后恢复正常。
2.4 创建最小可行项目:空壳控制台 + WinRT 初始化
新建一个空的 Win32 控制台项目(不要选“Windows 桌面应用程序”模板,它默认禁用 C++/WinRT 支持),然后修改main.cpp:
#include <iostream> #include <winrt/Windows.Foundation.h> #include <winrt/Windows.Devices.Bluetooth.Advertisement.h> using namespace winrt; using namespace Windows::Foundation; using namespace Windows::Devices::Bluetooth::Advertisement; int main() { // 必须调用!否则所有 WinRT API 调用将失败 init_apartment(); std::wcout << L"WinRT 初始化成功,准备启动 BLE 扫描...\n"; // 创建 watcher 实例(仅声明,不启动) BluetoothLEAdvertisementWatcher watcher; std::wcout << L"BluetoothLEAdvertisementWatcher 创建成功。\n"; return 0; }关键点说明:
#include <winrt/Windows.Foundation.h>是所有 WinRT API 的根头文件,必须最先包含;init_apartment()是 WinRT 的线程模型初始化函数,必须在任何 WinRT 对象创建前调用,且只能调用一次。若遗漏,后续所有watcher.Start()均会崩溃或静默失败;- 此代码不启动扫描,仅验证头文件可包含、对象可构造、链接无缺失。编译命令应包含
/std:c++17和/ZW(尽管 C++/WinRT 不强制要求/ZW,但某些 SDK 版本下缺它会报winrt/base.h(123): error C2039: 'IInspectable')。
3. 核心 API 配置与调用:从扫描到连接的四步闭环
Windows BLE 的 C/C++ 开发不是“写个 for 循环遍历设备”那么简单。它基于事件驱动 + 异步操作模型,每一步都需处理完成回调、错误分支、资源生命周期。下面以“发现设备 → 连接设备 → 发现服务 → 读取特征值”为线索,给出可直接粘贴的生产级代码片段,并解释每个参数的取舍逻辑。
3.1 启动 BLE 广告扫描:Watcher 配置的三个关键字段
BluetoothLEAdvertisementWatcher是入口。它的行为由ScanningMode、SignalStrengthFilter和AdvertisementFilter共同决定。新手常犯的错是只设ScanningMode::Active就以为万事大吉,结果扫不到设备。
// main.cpp 续写 int main() { init_apartment(); BluetoothLEAdvertisementWatcher watcher; // 【关键1】扫描模式:Active 模式才主动发 SCAN_REQ,获取更多广告数据(如 ManufacturerData) watcher.ScanningMode(BluetoothLEScanningMode::Active); // 【关键2】信号强度过滤:设为 -100 dBm(极弱)可捕获最远设备,但噪音多;设为 -70 dBm 更稳定 // 注意:单位是整数 dBm,不是浮点!传 -70.5 会截断为 -70,导致过滤失效 watcher.SignalStrengthFilter().InRangeThresholdInDBm(-70); watcher.SignalStrengthFilter().OutOfRangeThresholdInDBm(-80); // 离开范围阈值 watcher.SignalStrengthFilter().OutOfRangeTimeout(5000); // 5秒后触发 OutOfRange 事件 // 【关键3】广告过滤:只关注含特定服务 UUID 的设备(例如 0000180F-0000-1000-8000-00805F9B34FB,即 Battery Service) auto filter = BluetoothLEAdvertisementFilter(); auto adData = BluetoothLEAdvertisement(); adData.ServiceUuids().Append(winrt::guid{ "{0000180F-0000-1000-8000-00805F9B34FB}" }); filter.Advertisement(adData); watcher.AdvertisementFilter(filter); // 绑定事件处理器(lambda 必须用 auto&& 捕获,避免 move 语义问题) watcher.Received([&watcher](auto&&, auto&& args) { auto address = args.BluetoothAddress(); auto rssi = args.RawSignalStrengthInDBm(); std::wcout << L"发现设备: 0x" << std::hex << address << L", RSSI: " << rssi << L" dBm\n"; }); watcher.Stopped([](auto&&, auto&&) { std::wcout << L"扫描已停止。\n"; }); watcher.Start(); // 真正启动扫描 // 保持主线程存活(实际项目中应使用 co_await 或消息循环) std::this_thread::sleep_for(std::chrono::seconds(10)); watcher.Stop(); return 0; }参数说明:
ScanningMode::Active:向周边设备发送 SCAN_REQ 包,可获取 Scan Response 数据(常含设备名),但耗电略高;Passive模式只监听广播包,省电但信息少。InRangeThresholdInDBm(-70):RSSI ≥ -70 dBm 视为“在范围内”,触发Received事件;低于此值持续 5 秒(OutOfRangeTimeout)后触发Stopped的子事件(需额外绑定SignalStrengthChanged)。AdvertisementFilter:此处用ServiceUuids过滤,比LocalName过滤更可靠(设备名可能为空或被截断)。UUID 必须用winrt::guid{}构造,字符串格式必须带{}和连字符,"{180F0000-0000-1000-8000-00805F9B34FB}"会解析失败。
3.2 连接设备:FromIdAsync 的线程陷阱与超时控制
扫描到设备地址(BluetoothAddress)后,不能直接CreateInstance,必须通过BluetoothLEDevice::FromIdAsync获取设备实例。这是最易翻车的环节——它必须在 STA(单线程公寓)线程中调用,且不能在main()的初始线程直接co_await。
// 封装一个可 await 的连接函数 winrt::fire_and_forget ConnectToDevice(uint64_t bluetoothAddress) { try { // 【血泪经验】必须在新协程中调用,避免阻塞主线程 co_await winrt::resume_background(); // 构造设备 ID 字符串:格式为 "bluetoothLE:" + 十六进制地址(小端转大端!) std::wstring deviceId = L"bluetoothLE:"; deviceId += std::to_wstring(bluetoothAddress); // 注意:bluetoothAddress 是 uint64_t,但 Windows 内部按小端字节序存储 MAC 地址 // 实际设备 ID 应为 "bluetoothLE:1234567890AB"(12位十六进制),而非 "0x1234567890AB" // 因此需手动格式化为 12 位(补零) std::wstringstream ss; ss << std::hex << std::setw(12) << std::setfill(L'0') << bluetoothAddress; deviceId = L"bluetoothLE:" + ss.str(); std::wcout << L"正在连接设备 ID: " << deviceId << L"\n"; // 【关键】设置超时,避免无限等待 auto cts = winrt::cancellation_token_source{}; cts.cancel_after(std::chrono::seconds(15)); auto device = co_await BluetoothLEDevice::FromIdAsync(deviceId, cts.token()); std::wcout << L"连接成功!设备名称: " << device.Name() << L"\n"; // 后续:discover services... } catch (winrt::hresult_error const& ex) { std::wcerr << L"连接失败: " << ex.message().c_str() << L" (0x" << std::hex << ex.code() << L")\n"; // 常见错误码:0x80070490(设备不存在)、0x80070005(访问被拒绝,检查开发者模式) } }关键点:
co_await winrt::resume_background():将协程切到后台线程,规避 STA 限制。若在 UI 线程调用FromIdAsync,会抛RPC_E_WRONG_THREAD;- 设备 ID 格式必须为
"bluetoothLE:" + 12位大写十六进制 MAC(如"bluetoothLE:AA11BB22CC33"),bluetoothAddress字段是uint64_t,但 Windows 蓝牙栈将其解释为小端 MAC 地址,因此需按字节反转或直接用BluetoothLEAdvertisementReceivedEventArgs::BluetoothAddress()返回值拼接; cancellation_token_source::cancel_after()是唯一可靠的超时机制,FromIdAsync本身不支持 timeout 参数。
3.3 发现 GATT 服务与特征值:同步阻塞式调用的替代方案
BluetoothLEDevice.GattServices()返回的是IVectorView<GattDeviceService>,但它是异步加载的。新手常误用.Size()判断是否加载完成,导致空指针访问。
// 在 ConnectToDevice 成功后调用 winrt::fire_and_forget DiscoverServicesAndCharacteristics(winrt::Windows::Devices::Bluetooth::BluetoothLEDevice device) { try { co_await winrt::resume_background(); // 【正确做法】调用 GetGattServicesForUuidAsync 获取指定 UUID 服务 auto serviceUuid = winrt::guid{ "{0000180F-0000-1000-8000-00805F9B34FB}" }; // Battery Service auto services = co_await device.GetGattServicesForUuidAsync(serviceUuid); if (services.Size() == 0) { std::wcout << L"未找到 Battery Service\n"; co_return; } auto batteryService = services.GetAt(0); std::wcout << L"找到服务: " << batteryService.Uuid() << L"\n"; // 发现该服务下的所有特征值 auto characteristics = co_await batteryService.GetCharacteristicsAsync(); for (uint32_t i = 0; i < characteristics.Size(); ++i) { auto ch = characteristics.GetAt(i); std::wcout << L" 特征值 UUID: " << ch.Uuid() << L", 属性: "; if (ch.CharacteristicProperties().HasFlag(GattCharacteristicProperties::Read)) std::wcout << L"Read "; if (ch.CharacteristicProperties().HasFlag(GattCharacteristicProperties::Notify)) std::wcout << L"Notify "; std::wcout << L"\n"; } } catch (winrt::hresult_error const& ex) { std::wcerr << L"服务发现失败: " << ex.message().c_str() << L"\n"; } }为什么不用device.GattServices()?
因为GattServices是一个惰性加载的IVectorView,其.Size()返回的是“已缓存数量”,而非“总数量”。首次访问需显式调用GetGattServicesAsync()或GetGattServicesForUuidAsync()触发加载。后者更高效,避免遍历全部服务。
3.4 读取与写入特征值:缓冲区长度与字节序的硬约束
GattCharacteristic::ReadValueAsync()返回GattReadResult,其Value()是IBuffer,需转换为std::vector<uint8_t>才能解析。常见错误是直接reinterpret_cast,导致内存越界。
winrt::fire_and_forget ReadBatteryLevel(winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::GattCharacteristic characteristic) { try { co_await winrt::resume_background(); auto result = co_await characteristic.ReadValueAsync(); if (result.Status() != GattCommunicationStatus::Success) { std::wcerr << L"读取失败: " << static_cast<int>(result.Status()) << L"\n"; co_return; } // 【安全转换】从 IBuffer 提取字节 auto buffer = result.Value(); auto reader = DataReader::FromBuffer(buffer); std::vector<uint8_t> data(reader.UnconsumedBufferLength()); reader.ReadBytes(data); // Battery Level 特征值定义为 1 字节 uint8(0-100) if (data.size() >= 1) { uint8_t level = data[0]; std::wcout << L"电池电量: " << static_cast<int>(level) << L"%\n"; } } catch (winrt::hresult_error const& ex) { std::wcerr << L"读取异常: " << ex.message().c_str() << L"\n"; } }关键细节:
DataReader::FromBuffer()是唯一安全的IBuffer解析方式,buffer.data()返回的指针不可直接访问;data.size()必须校验,BLE 特征值长度可变,未校验直接取data[0]是典型崩溃点;- 字节序:Windows BLE API 默认使用小端(Little-Endian),与大多数 BLE 设备一致,无需手动反转。
4. 常见问题排查:五条踩坑记录与现场急救方案
Windows BLE C/C++ 开发的玄学感,往往来自那些不报错、不崩溃、但功能死活不生效的“黑匣子”问题。以下是我在多个模拟项目X 和某工业蓝牙网关客户端中反复验证过的五条高频翻车记录,每条都附带现象、根因和可立即执行的急救命令。
4.1 现象:BluetoothLEAdvertisementWatcher::Start()后Received事件永不触发,Stopped也无日志
原因:Bluetooth Support Service未运行,或当前用户无蓝牙设备访问权限(尤其在域环境或企业锁机策略下)。
解决:
:: 1. 强制重启蓝牙服务 net stop bthserv && net start bthserv :: 2. 检查当前用户是否在 Bluetooth Users 组(管理员权限运行) net localgroup "Bluetooth Users" %USERNAME% :: 3. 若不在,添加(需管理员) net localgroup "Bluetooth Users" %USERNAME% /add注意:添加组后需完全注销并重新登录,仅重启资源管理器无效。
4.2 现象:FromIdAsync抛0x80070005 E_ACCESSDENIED,但设备管理器中蓝牙正常
原因:开发者模式未启用,或应用清单(appxmanifest)中缺失uap:Capability Name="bluetooth"(控制台程序虽无 manifest,但仍受系统策略限制)。
解决:
- 手动开启开发者模式(设置 → 更新与安全 → 对于开发人员 → 开发者模式);
- 若在企业环境无法开启,改用
BluetoothLEDevice::FromBluetoothAddressAsync(uint64_t)(Windows 10 1809+),它绕过部分权限检查,但仅支持经典蓝牙地址,不支持 BLE 随机地址。
4.3 现象:ReadValueAsync返回Success,但Value().Length()为 0
原因:目标特征值设置了WRITE_WO_RESP属性,但代码误用了ReadValueAsync;或设备固件 Bug 导致返回空响应。
解决:
- 先用
nRF Connect(手机 App)连接同一设备,确认该特征值是否真能读取; - 若 nRF Connect 可读,则检查代码中
characteristic.Uuid()是否拼写错误(如少一个F); - 若 nRF Connect 也读不到,说明设备未实现该特征值的读取逻辑,需联系固件方。
4.4 现象:watcher.Start()后 CPU 占用飙升至 100%,程序无响应
原因:Received事件处理器中执行了同步阻塞操作(如std::cin >> x或Sleep(1000)),导致 WinRT 线程池饥饿。
解决:
- 事件处理器内严禁任何阻塞调用;
- 需要延时或 IO,必须用
co_await winrt::resume_background()切到后台线程,再用std::this_thread::sleep_for; - 快速验证:将
Received处理器简化为std::wcout << L".";,观察 CPU 是否回落。
4.5 现象:编译报错error C2039: 'BluetoothLEAdvertisementWatcher' : is not a member of 'winrt::Windows::Devices::Bluetooth::Advertisement'
原因:Windows SDK 版本过低(< 17763),或项目未启用 C++17 标准。
解决:
- 在项目属性 → C/C++ → Language → C++ Language Standard → 设为
ISO C++17 Standard (/std:c++17); - 在项目属性 → General → Windows SDK Version → 显式选择
10.0.17763.0或更高; - 清理解决方案(Build → Clean Solution),再重新生成——旧的
obj/目录可能缓存了错误的元数据。
5. 验证与调试:用三类工具交叉印证,让 BLE 行为不再黑盒
装完环境、写完代码,不代表它真能工作。Windows BLE 的“能跑通”必须经受三重验证:协议层可见、系统层可控、应用层可测。下面给出一套无需额外购买硬件、纯软件可执行的验证流水线。
5.1 协议层验证:用 Microsoft Bluetooth LE Explorer 抓包分析
这是微软官方免费工具(随 Windows SDK 安装),路径通常为:C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\BluetoothLEExplorer.exe
启动后:
- 点击 “Start Scanning”,确认能否看到周围 BLE 设备(如手机、手环);
- 选中一个设备 → 点击 “Connect” → “Discover Services”;
- 展开 Battery Service → 右键 “Battery Level” 特征值 → “Read Value”;
- 若此处能成功读取,说明你的硬件、驱动、系统服务全链路正常;若失败,则问题在系统层,与你的 C++ 代码无关。
提示:Explorer 的日志窗口(View → Log Window)会显示每一步的 WinRT API 调用结果和错误码,比自己写
std::wcout更权威。
5.2 系统层验证:用 PowerShell 命令行直连诊断
PowerShell 是验证 Windows BLE 状态的后悔药。它不依赖你的 VS 项目,直接调用系统 API:
# 1. 列出所有已配对的 BLE 设备(验证 Bluetooth Support Service 是否工作) Get-PnpDevice -Class Bluetooth | Where-Object {$_.Name -like "*LE*"} # 2. 扫描 5 秒,输出发现的设备(验证扫描能力) $watcher = New-Object Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementWatcher $watcher.ScanningMode = "Active" $watcher.Start() Start-Sleep -Seconds 5 $watcher.Stop() # 3. 检查蓝牙驱动签名状态(企业环境常被拦截) Get-WmiObject Win32_PnPSignedDriver | Where-Object {$_.DeviceName -like "*Bluetooth*"} | Select DeviceName, IsSigned, Signer若第 1 条无输出,说明设备未配对或服务未启;若第 2 条无设备,说明驱动或硬件故障;若第 3 条IsSigned=False,则必须更新驱动。
5.3 应用层验证:编写最小化测试桩,隔离变量
把你的主程序拆成四个独立.cpp文件,每次只编译运行一个,快速定位问题模块:
| 文件名 | 功能 | 预期输出 | 失败含义 |
|---|---|---|---|
test_watcher.cpp | 仅创建 watcher 并 Start/Stop | “扫描已启动”、“扫描已停止” | SDK 或服务层故障 |
test_connect.cpp | 用已知地址(如手机)调用FromIdAsync | “连接成功!设备名称: XXX” | 权限或地址格式错误 |
test_service.cpp | 连接后调用GetGattServicesForUuidAsync | “找到服务: {0000180F-...}” | GATT 发现逻辑或 UUID 错误 |
test_read.cpp | 对已知可读特征值调用ReadValueAsync | “电池电量: 85%” | 特征值读取或解析逻辑错误 |
每个文件都应包含完整的init_apartment()、try/catch、co_await resume_background(),且不复用任何全局变量。这样,当test_read.cpp成功而test_service.cpp失败,就能 100% 确定问题出在服务发现环节,而非蓝牙硬件。
5.4 一份可直接运行的完整验证脚本(含超时与日志)
最后,给你一个我每次部署新环境必跑的verify_ble.cpp,它整合了上述所有要点,且自带 30 秒超时保护,避免卡死:
#include <iostream> #include <winrt/Windows.Foundation.h> #include <winrt/Windows.Devices.Bluetooth.Advertisement.h> #include <winrt/Windows.Devices.Bluetooth.h> #include <winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h> #include <winrt/Windows.Storage.Streams.h> #include <thread> #include <chrono> using namespace winrt; using namespace Windows::Foundation; using namespace Windows::Devices::Bluetooth::Advertisement; using namespace Windows::Devices::Bluetooth; using namespace Windows::Devices::Bluetooth::GenericAttributeProfile; using namespace Windows::Storage::Streams; int main() { init_apartment(); std::wcout << L"=== Windows BLE C++ 环境验证开始 ===\n"; // Step 1: Watcher 测试 std::wcout << L"1. 测试广告扫描...\n"; BluetoothLEAdvertisementWatcher watcher; watcher.ScanningMode(BluetoothLEScanningMode::Active); watcher.SignalStrengthFilter().InRangeThresholdInDBm(-80); bool gotDevice = false; watcher.Received([&](auto&&, auto&& args) { std::wcout << L"✓ 扫描到设备: 0x" << std::hex << args.BluetoothAddress() << L"\n"; gotDevice = true; watcher.Stop(); }); watcher.Start(); std::this_thread::sleep_for(std::chrono::seconds(5)); watcher.Stop(); if (!gotDevice) { std::wcerr << L"✗ 扫描失败:未发现任何 BLE 设备,请检查蓝牙硬件和驱动\n"; return 1; } // Step 2: 连接测试(用第一个扫描到的地址,此处简化为固定地址,实际应替换) std::wcout << L"2. 测试设备连接(使用模拟地址)...\n"; try { // 模拟一个有效地址(实际使用时替换为真实扫描到的地址) uint64_t testAddr = 0x112233445566; std::wstringstream ss; ss << std::hex << std::setw(12) << std::setfill(L'0') << testAddr; auto deviceId = L"bluetoothLE:" + ss.str(); auto cts = cancellation_token_source{}; cts.cancel_after(std::chrono::seconds(10)); auto device = wait_for(async([deviceId, cts]() -> winrt::Windows::Devices::Bluetooth::BluetoothLEDevice { return co_await BluetoothLEDevice::FromIdAsync(deviceId, cts.token()); })); std::wcout << L"✓ 连接成功: " << device.Name().c_str() << L"\n"; } catch (hresult_error const& e) { std::wcerr << L"✗ 连接失败: " << e.message().c_str() << L" (0x" << std::hex << e.code() << L")\n"; return 1; } std::wcout << L"=== 验证通过:环境就绪,可开始开发 ===\n"; return 0; }编译命令(VS2022 Developer Command Prompt):
cl /EHsc /std:c++17 /MD verify_ble.cpp /I"C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\winrt" /link "WindowsApp.lib"从那以后我每次给某高校实验室部署 BLE 教学环境,或者接手某跨平台系统的 Windows 客户端模块,都强制走一遍这个四步验证(Explorer → PowerShell → 四文件桩 →verify_ble.cpp)。它不花哨,但能瞬间区分问题是出在“我的代码”还是“这台机器根本不行”。希望帮到你。
本文还有配套的精品资源,点击获取