Qt下使用windows api开发蓝牙搜索及认证程序

文章讲述了如何在Windows系统上使用Qt配合WindowsAPI进行蓝牙设备搜索,包括使用`BluetoothFindFirstDevice`函数搜寻设备并进行配对,以及`BluetoothAuthenticateDevice`进行认证操作的详细过程和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目的

通过自己的软件能够完成搜索蓝牙,并且能够发送认证请求,协商PIN码或者是配对码的功能。

开发方式

Qt + windows api

开发方式说明

因为Qt的蓝牙API在windows系统上不能完成蓝牙发送配对请求的功能,而且对于ble广播PIN码的这种设备需要先添加windows的蓝牙设备,然后调用Qt的蓝牙API进行搜索才能搜索到。 但是Qt的蓝牙socket连接类还是很好用的。所以搜索蓝牙设备和配对的功能就选择调用windows api了。

需要使用到的api

hdf = BluetoothFindFirstDevice( _In_ const   BLUETOOTH_DEVICE_SEARCH_PARAMS * pbtsp,_Inout_ BLUETOOTH_DEVICE_INFO *   pbtdi);

函数功能是寻找第一个搜索到蓝牙设备的蓝牙适配器设备。
返回的hdf 参数为蓝牙适配器设备的句柄。
_In_ const BLUETOOTH_DEVICE_SEARCH_PARAMS * pbtsp 该参数为设置寻找参数的设备参数,告诉API返回相应参数的蓝牙设备。
BLUETOOTH_DEVICE_SEARCH_PARAMS 结构体参数的解释

BLUETOOTH_DEVICE_SEARCH_PARAMS bdsp = { sizeof(bdsp) };
    bdsp.fReturnAuthenticated = 1;   // 返回经过身份验证的蓝牙设备
    bdsp.fReturnRemembered = 1;      // 返回记住的蓝牙设备
    bdsp.fReturnUnknown = 1;         // 返回未知的蓝牙设备
    bdsp.fReturnConnected = 1;       // 返回已连接的蓝牙设备
    bdsp.fIssueInquiry = 1;          // 发出新一轮的查询
    bdsp.cTimeoutMultiplier = 5;    // 1.28s的增量,1.28 * 5 这个参数设置多少BluetoothFindFirstDevice这个函数就会阻塞多长时间。
    bdsp.hRadio = NULL; // 有效的本地蓝牙句柄,如果是NULL,在所有本地蓝牙无线电上进行查询

_Inout_ BLUETOOTH_DEVICE_INFO * pbtdi 返回的是寻找到的蓝牙设备信息。
BLUETOOTH_DEVICE_INFO 参数结构体的解释


typedef struct _BLUETOOTH_DEVICE_INFO {
    _Field_range_(==, sizeof(BLUETOOTH_DEVICE_INFO_STRUCT))
    DWORD   dwSize;                             //  size, in bytes, of this structure - must be the sizeof(BLUETOOTH_DEVICE_INFO)

    BLUETOOTH_ADDRESS Address;                  //  Bluetooth address

    ULONG   ulClassofDevice;                    //  Bluetooth "Class of Device"

    BOOL    fConnected;                         //  Device connected/in use
    BOOL    fRemembered;                        //  Device remembered
    BOOL    fAuthenticated;                     //  Device authenticated/paired/bonded

    SYSTEMTIME  stLastSeen;                     //  Last time the device was seen
    SYSTEMTIME  stLastUsed;                     //  Last time the device was used for other than RNR, inquiry, or SDP

    WCHAR   szName[ BLUETOOTH_MAX_NAME_SIZE ];  //  Name of the device  这里BLUETOOTH_MAX_NAME_SIZE 为248

} BLUETOOTH_DEVICE_INFO_STRUCT;

res = BluetoothAuthenticateDevice(_In_opt_ HWND hwndParent, _In_opt_ HANDLE hRadio, _Inout_ BLUETOOTH_DEVICE_INFO * pbtbi, _In_reads_opt_(ulPasskeyLength) PWSTR pszPasskey, _In_ ULONG ulPasskeyLength)这个函数是蓝牙发送认证请求的函数 res`为返回的错误码

搜索蓝牙的具体示例

WSADATA wsaData;
    if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        qDebug() << "WSAStartup failed";

    }
    BLUETOOTH_DEVICE_SEARCH_PARAMS bdsp = { sizeof(bdsp) };
    bdsp.fReturnAuthenticated = 1;   // 返回经过身份验证的蓝牙设备
    bdsp.fReturnRemembered = 0;      // 返回记住的蓝牙设备
    bdsp.fReturnUnknown = 1;         // 返回未知的蓝牙设备
    bdsp.fReturnConnected = 1;       // 返回已连接的蓝牙设备
    bdsp.fIssueInquiry = 1;          // 发出新一轮的查询
    bdsp.cTimeoutMultiplier = 10;    // 1.28s的增量,1.28 * 20
    bdsp.hRadio = NULL; // 有效的本地蓝牙句柄,如果是NULL,在所有本地蓝牙无线电上进行查询

    // 蓝牙设备结构体
    BLUETOOTH_DEVICE_INFO bdi[20];
    for (int i = 0; i < 20; i++) {
        bdi[i].dwSize = sizeof(bdi[i]);
    }
    BLUETOOTH_FIND_RADIO_PARAMS radioParams = { sizeof(BLUETOOTH_FIND_RADIO_PARAMS) };
    HANDLE hRadio = NULL;

    // 查找第一个可用的蓝牙适配器
    HBLUETOOTH_RADIO_FIND hFind = BluetoothFindFirstRadio(&radioParams, &hRadio);
    if(hFind == NULL || hRadio == NULL)
    {
        qDebug() << "BluetoothFindFirstRadio failed";
    }

    HBLUETOOTH_DEVICE_FIND hdf = BluetoothFindFirstDevice(&bdsp, &bdi[0]);    // 返回的hdf 句柄留给 BluetoothFindNextDevice使用
    qDebug() << "bdi =" << QString::fromWCharArray(bdi[0].szName);
    if (hdf == NULL) {
        qDebug() << "BluetoothFindFirstDevice hdf failed";
    }
    int count = 0;
    do {
        // 如果扫到了第一个蓝牙设备
        QString bluetooth_name = QString::fromWCharArray(bdi[count].szName);
        qDebug() << "bluetooth_name =" << bluetooth_name;
        count++;
        qDebug() << "count =" << count;
        if(count >= 20)
        {
            break;
        }
    } while (BluetoothFindNextDevice(hdf, &bdi[count]));

对指定蓝牙发送认证请求示例

QString path = QCoreApplication::applicationDirPath() + "/devtype.ini";
QSettings inifile(path,QSettings::IniFormat);
QString pin_info = inifile.value("pin/pincode").toString();
if(pin_info == "")
{
    QMessageBox::critical(this,"错误","pin码为空");
    return;
}
BLUETOOTH_DEVICE_INFO devInfo = dev_list.at(i);
if(devInfo.fAuthenticated == false)
{
    /*将字符串转换为宽字符串*/
    PWSTR pwstr;
    size_t size = pin_info.toStdString().size();
    wchar_t buffer[BLUETOOTH_MAX_PASSKEY_SIZE + 1] = {0};
    MultiByteToWideChar(CP_ACP, 0, pin_info.toStdString().c_str(), size, buffer, size * sizeof(wchar_t)); 
    pwstr = buffer;
    int pwstrlength = sizeof(pwstr);
    DWORD res = BluetoothAuthenticateDevice(NULL,NULL,&devInfo,pwstr,pwstrlength);  //自动输入pin码和蓝牙配对。
    if(res != ERROR_SUCCESS)
    {
        qDebug() << "bt failed";
        QMessageBox::information(this,tr("warn"),tr("请检查是否已经与本背夹配对过,可以点击搜索,更新显示状态"));
        return;
    }
}

上述示例是使用了从ini文件中自动读取pin码,进行自动填充连接蓝牙。
如果需要手动输入pin码可以将BluetoothAuthenticateDevice(NULL,NULL,&devInfo,pwstr,pwstrlength)中的pwstr更改为NULL。pwstrlength更改为NULL。pwstrlength这个参数在pwstr为NULL时是不起作用的。但是当你需要指定pin码时,长度一定要是pin码的长度。
在这里还有一个需要注意的点是pin码的转换问题,pin码的数据类型是Qt的QString,需要转换为windows api需要的PSTR。这个地方很坑。

注意事项

  1. 在windows+QT蓝牙开发中,编译器一定要选用MSVC编译器,不要使用Mingw编译器,Mingw编译器编译不了Qt的蓝牙库。
  2. 根据官方说明,自己的系统版本一定要是WIN10的一个版本往上,因为这样QT的相关蓝牙库能够调用WinRT的蓝牙相关API。
### 实现 Windows蓝牙 BLE 服务端功能 为了实现在 Windows 平台上作为蓝牙低功耗(BLE)的服务端角色,可以利用微软提供的 WinRT API 或者第三方库来完成这一目标。WinRT 提供了一套完整的接口用于创建、管理和操作 BLE 设备和服务。 #### 使用 C++/WinRT 创建 BLE 服务端应用 通过调用 `Windows.Devices.Bluetooth.GenericAttributeProfile` 命名空间中的类[^1],开发者能够定义 GATT 特征值并对外提供这些特征值给其他客户端访问: ```cpp #include <winrt/Windows.Foundation.h> #include <winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h> using namespace winrt; using namespace Windows::Devices::Bluetooth::GenericAttributeProfile; // 定义 UUID 和初始属性值 Guid serviceUuid{ L"0000fff0-0000-1000-8000-00805f9b34fb" }; Guid characteristicUuid{ L"0000fff1-0000-1000-8000-00805f9b34fb" }; GattLocalCharacteristic CreateGattService() { auto gattServer = BluetoothLEAdvertisementWatcher(); // 初始化广告观察器 // 构建本地 GATT 服务对象 var local_service = GattLocalService(serviceUuid); // 设置 Characteristic 属性 IBuffer initialValue = CryptographicBuffer.ConvertStringToBinary(L"Hello", BinaryStringEncoding::Utf8); // 创建可读写的 Characteristic 对象 var charProps = GattCharacteristicsProperties::Read | GattCharacteristicsProperties::WriteWithoutResponse | GattCharacteristicsProperties::Notify; var descriptor = GattDescriptor(characteristicUuid, initialValue); std::vector<GattLocalCharacteristicParameter> params = single_threaded_vector({ GattLocalCharacteristicParameters {characteristicUuid, charProps } }); co_await local_service.CreateAsync(params); return *local_service.GetCharacteristics().First(); } ``` 这段代码展示了如何基于特定的UUID构建一个简单的 GATT 服务实例,并为其添加了一个具有基本权限设置的特性描述符。此过程涉及到了多个异步API调用来确保线程安全性和响应性能[^2]。 请注意,在实际开发过程中还需要处理诸如错误捕捉、状态变化通知等一系列细节问题;此外,对于更复杂的应用场景可能需要用到额外的安全机制如加密传输等高级配置项。 #### 配置环境与依赖安装 要使上述代码正常工作,需先确认已安装 Visual Studio 及其 .NET Desktop Development 工作负载组件。同时建议启用开发者模式以便更好地测试应用程序的行为表现。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值