Search code examples
c#c++windowsbluetoothpinvoke

How to connect to bluetooth device on Windows?


I would like to allow the user to connect to paired audio devices directly from the app instead of navigating to the bluetooth settings manually.

I am successfully listing all bluetooth devices using WinRT Apis:

var result = await DeviceInformation.FindAllAsync(BluetoothDevice.GetDeviceSelector());
// allow user to select a device
DeviceInfo d = await SelectDevice(result);
BluetoothDevice bluetoothDevice = await BluetoothDevice.FromIdAsync(d.Id);

As the WinRT-Apis do not expose any "Connect" interface (Remember, I want to connect the device as Windows would not communicate with it myself), I'm exploring using P/Invoke, so I use the following after reading this answer on superuser.com which suggests using BluetoothSetServiceState:


// Definitions
private const string bluetoothDll = "bthprops.cpl";

[DllImport(bluetoothDll, ExactSpelling = true, SetLastError = true)]
private static extern uint BluetoothSetServiceState(IntPtr hRadio, ref BLUETOOTH_DEVICE_INFO pbtdi, ref Guid pGuidService, uint dwServiceFlags);

[DllImport(bluetoothDll, SetLastError = true)]
private static extern IntPtr BluetoothFindFirstRadio(ref Bluetooth_Find_Radio_Params pbtfrp, out IntPtr phRadio);

[DllImport(bluetoothDll, SetLastError = true)]
private static extern bool BluetoothFindRadioClose(IntPtr findHandle);

[DllImport(bluetoothDll, SetLastError = true)]
private static extern uint BluetoothGetDeviceInfo(IntPtr hRadio, ref BLUETOOTH_DEVICE_INFO pbtdi);

private const uint BLUETOOTH_SERVICE_DISABLE = 0;
private const uint BLUETOOTH_SERVICE_ENABLE = 0x00000001;

// Code (using the bluetoothDevice obtained from the WinRT Api)
using(var pointer = GetRadioPointer())
{
    BLUETOOTH_DEVICE_INFO deviceInfo = new BLUETOOTH_DEVICE_INFO
    {
        Address = bluetoothDevice.BluetoothAddress,
        dwSize = (uint)Marshal.SizeOf<BLUETOOTH_DEVICE_INFO>()
    };

    uint result = BluetoothGetDeviceInfo(pointer.Handle, ref deviceInfo);

    Guid serviceRef = InTheHand.Net.Bluetooth.BluetoothService.Handsfree;
    result = BluetoothSetServiceState(pointer.Handle, ref deviceInfo, ref serviceRef, 1);
}

// I get the radio like this:

private RadioHandle GetRadioPointer()
{
    Bluetooth_Find_Radio_Params pbtfrp = new Bluetooth_Find_Radio_Params();
    pbtfrp.Initialize();

    IntPtr findHandle = IntPtr.Zero;
    try
    {
        findHandle = BluetoothFindFirstRadio(ref pbtfrp, out IntPtr phRadio);
        return new RadioHandle(phRadio);
    }
    finally
    {
        if (findHandle != IntPtr.Zero)
        {
            BluetoothFindRadioClose(findHandle);
        }
    }
}

However, I cannot get it to work. BluetoothSetServiceState always returns 87, which is ERROR_INVALID_PARAMETER and nothing happens. Any idea on how to solve this? Using the command line tools referenced in the superuser-post, it works...

Thanks for your help.


Solution

  • I found it out by chance myself and it works now. Depending whether the service is already in the state (even if the device is disconnected), you will need to turn it off before. So turning it off and on again works:

    BluetoothSetServiceState(pointer.Handle, ref deviceInfo, ref serviceRef, 0);
    BluetoothSetServiceState(pointer.Handle, ref deviceInfo, ref serviceRef, 1);
    

    So as it turns out, you can connect a device if you enumerate the services, turn all off and on again. Disconnecting works by turning all off. When the last one is off, Windows disconnects the device.