抽丝剥茧 —— 解析 PC 蓝牙检测

背景

项目为 Flutter 项目

业务需求要实现 PC 端的蓝牙检测,检索了多个开源插件发现对于声明支持 Windows 检测的插件均不能支持蓝牙驱动支持;

检索自定义实现对应功能基本有两种通用方案:

  1. 调用命令行进行驱动检测
  2. 自定义 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 的最佳工具,它提供了友好的图形界面和强大的项目管理功能。

  1. 安装 Visual Studio : 如果您尚未安装,请从 Microsoft 官方网站 下载并安装。在安装过程中,务必选择安装 "使用 C++ 的桌面开发" 工作负载。
  2. 创建新项目 : 启动 Visual Studio,选择 "创建新项目"
  3. 选择项目模板 : 在项目模板列表中,搜索并选择 "动态链接库 (DLL)" 模板。确保选择的是 C++ 语言和 Windows 平台。点击 "下一步"
  4. 配置项目 :
    • 项目名称 : 输入一个有意义的名称,例如 CustomDll
    • 位置: 选择项目保存路径。
    • 点击 "创建"
  5. 添加 C++ 源文件 : Visual Studio 会自动为您创建一个 dllmain.cpppch.h/pch.cpp 文件。您可以将 custom_calculator.cpp 的内容复制到 dllmain.cpp 中,或者右键点击 "源文件" 文件夹,选择 "添加" > "新建项" ,创建一个新的 .cpp 文件(例如 custom_functions.cpp),然后将代码粘贴进去。
    • 预编译头注意事项 : 如果您的项目使用了预编译头(默认通常是 pch.h),请确保在您的 C++ 源文件(例如 custom_functions.cpp)的最顶部(除了注释和空白行之外)包含 `#include

"pch.h"`。如果遇到预编译头相关的编译错误,可以尝试在源文件的属性中,将"预编译头"设置为"不使用预编译头"。

  1. 配置项目属性 :
    • 右键点击项目名称,选择 "属性"
    • "配置属性" > "链接器" > "输入" 中,检查 "附加依赖项" 。对于本例的 AddNumbers 函数,不需要额外的库。但对于更复杂的 Windows API 调用(例如蓝牙检测),您可能需要添加 setupapi.libcfgmgr32.lib 等。
  2. 编译生成 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 支持

  1. 检查物理设备
  2. 验证驱动状态
  3. 确认服务运行
  4. 测试无线电开关

最终链路

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 中定义的签名完全匹配,否则会导致运行时错误。
相关推荐
kebeiovo21 分钟前
C++实现线程池(3)缓存线程池
开发语言·c++
半桔1 小时前
【STL源码剖析】从源码看 vector:底层扩容逻辑与内存复用机制
java·开发语言·c++·容器·stl
千里镜宵烛2 小时前
互斥锁与条件变量
linux·开发语言·c++·算法·系统架构
爱科研的瞌睡虫2 小时前
C++线程中 detach() 和 join() 的区别
java·c++·算法
凤年徐2 小时前
【数据结构与算法】刷题篇——环形链表的约瑟夫问题
c语言·数据结构·c++·算法·链表
牟同學3 小时前
深入理解 C++ 中的stdpriority_queue:从原理到实战的高效优先级管理
数据结构·c++·priority_queue
艾莉丝努力练剑3 小时前
【C/C++】形参、实参相关内容整理
c语言·开发语言·c++·学习
TralyFang3 小时前
Flutter CachedNetworkImage 的解码、缩放和缓存策略
flutter
ilmari3 小时前
HarmonyOS 基于Network Kit封装的网络请求工具
android·flutter·harmonyos