共用方式為


Why doesn't the Windows 8.1 Bluetooth Rfcomm Chat Sample work?

I've seen support cases and forum posts asking this question:  "Why doesn't the Windows 8.1 Bluetooth Rfcomm Chat sample work?"  I have an answer for you: there's a bug in the sample.  To explain about the bug, let's look at the sample code - specifically, Scenario1_ChatClient.xaml.cs in the BluetoothRfcommChat.Shared project:

 private async void ServiceList_Tapped(object sender, TappedRoutedEventArgs e)
{
    try
    {
        RunButton.IsEnabled = false;
        ServiceSelector.Visibility = Windows.UI.Xaml.Visibility.Collapsed;

        var chatServiceInfo = chatServiceInfoCollection[ServiceList.SelectedIndex];
        chatService = await RfcommDeviceService.FromIdAsync(chatServiceInfo.Id);

        if (chatService == null)
        {
            MainPage.Current.NotifyUser(
                "Access to the device is denied because the application was not granted access",
                NotifyType.StatusMessage);
            return;
        }

        var attributes = await chatService.GetSdpRawAttributesAsync();
        if (!attributes.ContainsKey(SdpServiceNameAttributeId))
        {
            MainPage.Current.NotifyUser(
                "The Chat service is not advertising the Service Name attribute (attribute id=0x100). " +
                "Please verify that you are running the BluetoothRfcommChat server.",
                NotifyType.ErrorMessage);
            return;
        }

        var attributeReader = DataReader.FromBuffer(attributes[SdpServiceNameAttributeId]);
        var attributeType = attributeReader.ReadByte();
        if (attributeType != SdpServiceNameAttributeType)
        {
            MainPage.Current.NotifyUser(
                "The Chat service is using an unexpected format for the Service Name attribute. " +
                "Please verify that you are running the BluetoothRfcommChat server.",
                NotifyType.ErrorMessage);
            return;
        }

        var serviceNameLength = attributeReader.ReadByte();

        // The Service Name attribute requires UTF-8 encoding.
        attributeReader.UnicodeEncoding = UnicodeEncoding.Utf8;
        ServiceName.Text = "Service Name: \"" + attributeReader.ReadString(serviceNameLength) + "\"";

        lock (this)
        {
            chatSocket = new StreamSocket();
        }

        await chatSocket.ConnectAsync(chatService.ConnectionHostName, chatService.ConnectionServiceName);

        chatWriter = new DataWriter(chatSocket.OutputStream);
        ChatBox.Visibility = Windows.UI.Xaml.Visibility.Visible;

        DataReader chatReader = new DataReader(chatSocket.InputStream);
        ReceiveStringLoop(chatReader);
    }
    catch (Exception ex)
    {
        RunButton.IsEnabled = true;
        MainPage.Current.NotifyUser("Error: " + ex.HResult.ToString() + " - " + ex.Message, 
            NotifyType.ErrorMessage);
    }
}
 
 
 

There’s a very subtle bug in this block of code which needs to be called out.   It’s subtle because you can’t trace your way through this code and find the bug.   Rather, the issue is that this block is missing code which is required to make it work properly. This is the offending line of code:

 chatService = await RfcommDeviceService.FromIdAsync(chatServiceInfo.Id);

By itself, this line of code is very straightforward.  However, we need to put this in the greater context to understand the problem.  The problem is that this is being executed in an async method – not on the UI thread.  The reason that this is a problem comes from the documentation:

The first time this method is invoked by a store app, it should be called from a UI thread in order to display the consent prompt. After the user has granted consent, the method can be invoked from any application thread.

If a store app has not declared the right capabilities or the user does not grant consent, the method returns a null object.

Looking at the overall context of the application, you may expect that this is easy to find due to the exception handling, and indeed, many developers may have found it on their own.   Because the method call should return null, the error message should be :

Access to the device is denied because the application was not granted access

When the testing is performed between two Windows 8.1 Desktop machines, this is true.  However, I think that most of the time, this functionality is tested between a Windows Phone 8.1 acting as the client, and a Windows 8.1 desktop machine acting as the server. In these cases, an exception is thrown, and the catch block posts a HRESULT error rather than the friendly error:

ERROR: 0x80070005 – Access is denied. (Exception from HRESULT: 0x80070005)

 

This prevents the true problem from being readily determined. The fix for this is quite easy – wrap the call so that it is called from the UI thread.  This is the proper code to fix this problem:

 

 await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
{
    chatService = await RfcommDeviceService.FromIdAsync(chatServiceInfo.Id);
});

The additional code allows the method call or occur on the UI thread, thus allowing the app to request permission of the user to access the Bluetooth services. Once this occurs, the app will function normally.

We are working on getting this sample fixed, but until it is, I hope that this blog post resolves your Bluetooth problems. Happy coding to you.

Comments are encouraged, and feel free to tweet to me at WinDevMatt, or use my team’s handle WSDevSol.

Comments

  • Anonymous
    March 30, 2015
    So 6 months later and the official example still doesn't work? No notes on the page with the bad code. Nothing.    Just this work around?

  • Anonymous
    April 13, 2015
    Hi Matt, I've applied your code. I'm getting RfcommDeviceService object null. Please suggest a way forward. Here is my code : private static readonly Guid RfcommChatServiceUuid = Guid.Parse("00001101-0000-1000-8000-00805f9b34fb"); private async void Button_Click(object sender, RoutedEventArgs e)        {            try            {                DeviceInformationCollection obj = await DeviceInformation.FindAllAsync(RfcommDeviceService.GetDeviceSelector(RfcommServiceId.FromUuid(RfcommChatServiceUuid)));               RfcommDeviceService service = null;                await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>                {                    service = await RfcommDeviceService.FromIdAsync(obj[0].Id);                                    });                if (service == null)              <--- Always service object is null                {                    MessageDialog d = new MessageDialog("*** Empty ");                    await d.ShowAsync();                }                else                {                   var _socket = new StreamSocket();                   await _socket.ConnectAsync(service.ConnectionHostName, service.ConnectionServiceName);                   //Here code to print                    _socket.Dispose();                    MessageDialog d1 = new MessageDialog(" Done ***");                    await d1.ShowAsync();                }                          }            catch (Exception ex)            {                MessageDialog d = new MessageDialog(ex.Message);                d.ShowAsync();                          }        } <Capabilities>    <Capability Name="internetClientServer" />      <Capability Name="privateNetworkClientServer" />    <DeviceCapability Name="proximity" />    <m2:DeviceCapability Name="bluetooth.rfcomm">      <m2:Device Id="any">        <m2:Function Type="serviceId:00001101-0000-1000-8000-00805f9b34fb" />        <m2:Function Type="name:serialPort" />      </m2:Device>    </m2:DeviceCapability>  </Capabilities>

  • Anonymous
    August 30, 2015
    Hi Dilip, I have the same problem. RfcommDeviceService service = null; await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => { service = await RfcommDeviceService.FromIdAsync(DeviceInfo.Id); }); if (service == null) { System.Diagnostics.Debug.WriteLine("service null"); // <- always return; } OR var service = await RfcommDeviceService.FromIdAsync(DeviceInfo.Id); if (service == null) { System.Diagnostics.Debug.WriteLine("service null");  // <- always return; } My Package.appxmanifest contains:  <Capabilities>    <DeviceCapability Name="proximity" />    <m2:DeviceCapability Name="bluetooth.rfcomm">      <m2:Device Id="any">        <m2:Function Type="name:serialPort" />      </m2:Device>    </m2:DeviceCapability>      </Capabilities> My WMAppManifest.xml contains:    <Capabilities>      <Capability Name="ID_CAP_NETWORKING" />      <Capability Name="ID_CAP_MEDIALIB_AUDIO" />      <Capability Name="ID_CAP_MEDIALIB_PLAYBACK" />      <Capability Name="ID_CAP_SENSORS" />      <Capability Name="ID_CAP_WEBBROWSERCOMPONENT" />      <Capability Name="ID_CAP_PROXIMITY" />    </Capabilities> What is wrong? I'm using WP8.1 Silverlight App.