背景
项目为 Flutter 项目
业务需求要实现 PC 端的蓝牙检测,检索了多个开源插件发现对于声明支持 Windows 检测的插件均不能支持蓝牙驱动支持;
检索自定义实现对应功能基本有两种通用方案:
- 调用命令行进行驱动检测
- 自定义 C++ 完成打包 dll 通过 ffi 通信实现;
命令行检测
powershell
Get-PnpDevice -Class Bluetooth
OK Bluetooth Microsoft 蓝牙 LE 枚举器 BTH...
OK Bluetooth 英特尔(R) 无线 Bluetooth(R) USB...
命令行方案适合快速检测蓝牙服务可用性,但无法精准判断驱动状态 业务需要先进行驱动检测,然后才是服务检测
期望的检测逻辑
graph TD
A[检测开始]-->B{驱动支持}
B--否-->C[不支持]
B--是-->D{蓝牙开启}
D--否-->E[已关闭]
D--是-->F[已启用]
DLL 检测
自定义 dll 利用 C++ 库提供原生 Windows API 检测,可满足驱动检测,但技术实现较为困难 ,且调用链路较长
FFI 调用时序
sequenceDiagram
participant D as Dart
participant F as FFI
participant C as C++ DLL
D->>D: 1. 准备参数
D->>D: 2. 转换数据类型
D->>D: 3. 分配内存
D->>F: 4. 调用绑定函数
F->>C: 5. 跨语言调用
C->>C: 6. 执行业务逻辑
C->>F: 7. 返回结果
F->>D: 8. 接收结果
D->>D: 9. 解析数据
D->>D: 10. 释放内存
D->>D: 11. 更新状态
DLL 生成方案
演练:创建和使用自己的动态链接库 (C++) | Microsoft Learn
Visual Studio 是创建 DLL 的最佳工具,它提供了友好的图形界面和强大的项目管理功能。
- 安装 Visual Studio : 如果您尚未安装,请从 Microsoft 官方网站 下载并安装。在安装过程中,务必选择安装 "使用 C++ 的桌面开发" 工作负载。
- 创建新项目 : 启动 Visual Studio,选择 "创建新项目"。
- 选择项目模板 : 在项目模板列表中,搜索并选择 "动态链接库 (DLL)" 模板。确保选择的是 C++ 语言和 Windows 平台。点击 "下一步"。
- 配置项目 :
- 项目名称 : 输入一个有意义的名称,例如
CustomDll
。 - 位置: 选择项目保存路径。
- 点击 "创建"。
- 项目名称 : 输入一个有意义的名称,例如
- 添加 C++ 源文件 : Visual Studio 会自动为您创建一个
dllmain.cpp
和pch.h
/pch.cpp
文件。您可以将custom_calculator.cpp
的内容复制到dllmain.cpp
中,或者右键点击 "源文件" 文件夹,选择 "添加" > "新建项" ,创建一个新的.cpp
文件(例如custom_functions.cpp
),然后将代码粘贴进去。- 预编译头注意事项 : 如果您的项目使用了预编译头(默认通常是
pch.h
),请确保在您的 C++ 源文件(例如custom_functions.cpp
)的最顶部(除了注释和空白行之外)包含 `#include
- 预编译头注意事项 : 如果您的项目使用了预编译头(默认通常是
"pch.h"`。如果遇到预编译头相关的编译错误,可以尝试在源文件的属性中,将"预编译头"设置为"不使用预编译头"。
- 配置项目属性 :
- 右键点击项目名称,选择 "属性"。
- 在 "配置属性" > "链接器" > "输入" 中,检查 "附加依赖项" 。对于本例的
AddNumbers
函数,不需要额外的库。但对于更复杂的 Windows API 调用(例如蓝牙检测),您可能需要添加setupapi.lib
和cfgmgr32.lib
等。
- 编译生成 DLL : 在 Visual Studio 顶部的工具栏中,选择 "Release" 配置和 "x64" 平台(推荐用于生产环境)。然后,在菜单栏中选择 "生成" > "生成解决方案"。成功后,您将在项
graph TD
A[Flutter Application] -->|Import| B[pc_connect Plugin]
B -->|Export| C[BluetoothDriverDetector]
C -->|dart:ffi| D[bluetooth_detector.dll]
D -->|Windows API| E[Bluetooth Driver Stack]
E -->|Hardware Check| F[Bluetooth Hardware]
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#e8f5e8
style D fill:#fff3e0
style E fill:#fce4ec
style F fill:#f1f8e9
实操
题主并不掌握 C++,主要由 trae 完成
关键伪代码
C++
function detectBluetoothState() -> BluetoothStateResult:
if not isBluetoothDriverAvailable():
return BluetoothStateResult(
isSupported = false,
isEnabled = false,
message = "蓝牙驱动未检测到"
)
if not isBluetoothEnabled():
return BluetoothStateResult(
isSupported = true,
isEnabled = false,
message = "蓝牙已关闭"
)
return BluetoothStateResult(
isSupported = true,
isEnabled = true,
message = "蓝牙可用"
)
function isBluetoothDriverAvailable() -> bool:
// 使用 win_ble 的底层 API 检查 Bluetooth adapter 存在
adapters = win_ble.get_adapters()
return adapters.length > 0
function isBluetoothEnabled() -> bool:
// 遍历 adapter,检测其 radio 状态是否为 ON
for adapter in win_ble.get_adapters():
if adapter.radio_state == RADIO_ON:
return true
return false
功能分类 | 关键调用 | 说明 |
---|---|---|
设备枚举 | SetupDiGetClassDevs(&GUID_DEVCLASS_BLUETOOTH, ..., DIGCF_PRESENT) |
获取当前系统中所有蓝牙设备的集合句柄 |
设备遍历 | SetupDiEnumDeviceInfo(...) |
遍历设备集合 |
设备状态判断 | CM_Get_DevNode_Status(...) |
读取单个设备的工作状态 + 问题码 |
判断结果逻辑 | ulStatus & DN_HAS_PROBLEM , ulProblemNumber == CM_PROB_DISABLED |
是否禁用、是否有异常 |
资源释放 | SetupDiDestroyDeviceInfoList(...) |
避免内存泄露 |
衍生问题
在实际检测时,首版方案是通过 GUID GUID_DEVCLASS_BLUETOOTH
进行检测,发现无法区分驱动不可用与驱动可用蓝牙关闭,本质与调用命令行检测结果一致;
继续检索发现需要通过查询注册表完成
c++
LONG regResult = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SYSTEM\\CurrentControlSet\\Control\\Class\\{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}",
0, KEY_READ, &hKey);
进一步验证后发现对于安装过蓝牙重新卸载设备依旧存在问题(注册表未清理)
于是检索具体方案要求
微软官方蓝牙排查流程 修复 Windows 中的蓝牙问题 - Microsoft 支持
- 检查物理设备
- 验证驱动状态
- 确认服务运行
- 测试无线电开关
最终链路
graph TD
A[驱动层检测] --> B[检查注册表设备类]
B -->|存在| C[枚举设备驱动状态]
B -->|不存在| D[无驱动支持]
C -->|驱动正常| E[进入服务检测]
C -->|驱动禁用| F[驱动被禁用]
C -->|未安装| G[驱动未安装]
C -->|需重启| H[驱动需重启]
C -->|无设备| I[系统支持但无物理硬件]
关键伪代码
c++
BluetoothErrorCode GetBluetoothAdapterStatus() {
// 执行三层独立检测
硬件结果 = DetectBluetoothHardware();
服务结果 = DetectBluetoothService();
驱动结果 = DetectBluetoothDriver();
// 设置基础状态标志
isPresent = (硬件结果 == BT_SUCCESS);
isEnabled = (服务结果 == BT_SUCCESS);
isDriverWorking = (驱动结果 == BT_SUCCESS);
// 特殊处理:如果驱动检测成功但硬件检测失败
if (!isPresent && isDriverWorking) {
isPresent = 1; // 系统支持蓝牙
}
// 状态场景判断
if (isEnabled && isDriverWorking) {
return BT_SUCCESS; // 场景1:完全正常
} else if (!isEnabled && isDriverWorking) {
return BT_ERROR_SERVICE_NOT_RUNNING; // 场景2:驱动可用但蓝牙关闭
} else if (驱动结果 == BT_ERROR_DRIVER_DISABLED) {
return BT_ERROR_DRIVER_DISABLED; // 场景3:驱动被禁用
} else {
return BT_ERROR_DRIVER_NOT_INSTALLED; // 场景4:驱动问题
}
}
健壮性与兼容性考量
在实际项目中,尤其是在与原生代码交互时,健壮性和兼容性至关重要:
- 异常处理 : 在 C++ DLL 和 Dart FFI 层都应包含
try-catch
块。C++ 端应避免直接抛出异常,而是通过返回错误码或状态枚举来指示问题。Dart 端则应捕获 DLL 加载失败、函数调用失败等异常,并进行优雅处理,例如显示错误信息、禁用相关功能,而不是让应用崩溃。 - DLL 缺失/损坏 : 如果 DLL 文件缺失、路径不正确或损坏,
DynamicLibrary.open()
会抛出异常。在initialize()
方法中捕获此异常,并提供用户友好的反馈。 - 跨平台兼容性 : FFI 只能在 Dart 支持的平台上调用原生代码。对于 Windows 特定的 DLL,务必在 Dart 代码中通过
Platform.isWindows
进行平台检查,避免在非 Windows 平台上尝试加载和调用 DLL。 - 内存管理: 当在 Dart 和 C/C++ 之间传递复杂数据类型(如字符串、结构体)时,需要特别注意内存的分配和释放。遵循 FFI 的内存管理规则,避免内存泄漏或访问冲突。
- C++ 函数签名: 确保 C++ 函数的签名(包括参数类型、返回类型和调用约定)与 Dart FFI 中定义的签名完全匹配,否则会导致运行时错误。
