http://www.developer.com/

Back to article

Get Familiar: Microsoft Bluetooth Stack on Windows Mobile


October 27, 2006

Few General Words

The previous article, "Experiencing This Mysterious Bluetooth Stack Programming," gave you a very brief overview of the Widcomm BT stack and how to manage it from your own applications. Now, you switch to yet another implementation of Bluetooth stack, this time from Microsoft as a part of the Windows Mobile OS SDK. It follows the common Microsoft C-style paradigm and is quite different from, for example, the Widcomm stack. In this article, you will repeat those typical operations you might be needing to use in many cases (as in BT devices enumeration and so forth) as you did for the Widcomm stack. Code snippets here and there throughout the article should provide you a good basis for your own experiments.

Enumerating Bluetooth Devices

If you look at the SDK headers, you will see a few files related to Bluetooth. What you are interested in right now is located in winsock2.h. Here, you may find client-level APIs to the BT stack. For enumeration purposes, you will use the following functions:

WSALookupServiceBegin
WSALookupServiceNext
WSALookupServiceEnd

Seeing a "WSA" prefix in the API declarations above, you might bet it has to deal with sockets somehow. Yes, that's correct. Here, you'll get a good mix of sockets with virtual RFComm channels. Saying all this, the actual programming may be even simpler than for the Widcomm stack, but it really depends on your requirements.

All right, let me start with device enumeration. A scheme here is quite typical for many enumerating operations you can perform with other media, such as, for example, files:

  • Open enumeration
  • Loop through with 'Next' function
  • Close enumeration

In practice you have to do the following steps, as in this code snippet:

struct BtInfo
{
   CString m_sName;
   BT_ADDR m_btAddr;
};

typedef CArray<BtInfo,BtInfo&> CBtInfoArray;

int DiscoverBtDevList(CBtInfoArray& arrBtInfo)
{
   BOOL           bRes = FALSE;
   int            iResult = 0;
   LPWSAQUERYSET  pwsaResults;
   DWORD          dwSize = 0;
   WSAQUERYSET    wsaq;
   HANDLE         hLookup = 0;

   memset (&wsaq, 0, sizeof(wsaq));
   wsaq.dwSize      = sizeof(wsaq);
   wsaq.dwNameSpace = NS_BTH;
   wsaq.lpcsaBuffer = NULL;

   // initialize searching procedure
   iResult = WSALookupServiceBegin(&wsaq,
      LUP_CONTAINERS,
      &hLookup);

   if (iResult != 0)
   {
      iResult = WSAGetLastError();
      return iResult;
   }

   union {
      // returned struct can be quite large
      CHAR buf[5000];
      // properly align buffer to BT_ADDR requirements
      SOCKADDR_BTH  __unused;
   };

   for (;;)
   {
      pwsaResults = (LPWSAQUERYSET) buf;

      dwSize  = sizeof(buf);

      memset(pwsaResults,0,sizeof(WSAQUERYSET));
      pwsaResults->dwSize = sizeof(WSAQUERYSET);
      // namespace MUST be NS_BTH for bluetooth queries
      pwsaResults->dwNameSpace = NS_BTH;
      pwsaResults->lpBlob = NULL;

      // iterate through all found devices, returning name and address
      iResult = WSALookupServiceNext (hLookup,
         LUP_RETURN_NAME | LUP_RETURN_ADDR,
         &dwSize,
         pwsaResults);

      if (iResult != 0)
      {
         iResult = WSAGetLastError();
         if (iResult != WSA_E_NO_MORE)
         {
            TCHAR tszErr[32];
            iResult = WSAGetLastError();
            _sntprintf(tszErr, 32, _T("Socket Error: %d"), iResult);
            AfxMessageBox(tszErr, MB_OK);
         }
         break;
      }

      // collect name and address
      if (pwsaResults->lpszServiceInstanceName)
      {
         BtInfo btInfo;
         btInfo.m_sName = pwsaResults->lpszServiceInstanceName;
         memcpy(&(btInfo.m_btAddr),pwsaResults->
                lpcsaBuffer,sizeof(BT_ADDR));
         arrBtInfo.Add(btInfo);
      }
      Sleep(100);
   }

   WSALookupServiceEnd(hLookup);

   return iResult;
}

As you can see, there is nothing complex except few magical words and passes. The latter, I will discuss right here. First, you want to look up all containers in NS_BTH namespace. Second, you don't want to get back any additional information about neither local nor remote addresses. Thus wsaq variable setup and call to WSALookupServiceBegin start the race.

The next stage is a simple iteration through obtained results until you get an error WSA_E_NO_MORE after calling WSALookupServiceNext. You can control which data you want to get via parameters of that function. Our snippet requests a name and BT address. For simplicity, you use MFC's CArray here, but you may return the result as you want to—for example, looking up only for specific device and so forth.

Detecting RFComm Channel

Once you have detected a device and grabbed its address, you can connect to it via WinSock. The first steps are quite common for any other networking code except some BT-specific settings:

...
WSADATA wsd;
if(WSAStartup(MAKEWORD(1,1), &wsd))
   return FALSE;

// Create a Bluetooth socket
SOCKET btSocket = socket (AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
if(btSocket == INVALID_SOCKET)
{
   //report an error
   return FALSE;
}

// Setup the socket address
SOCKADDR_BTH sockAddr;
memset (&sockAddr, 0, sizeof(sockAddr));
sockAddr.addressFamily = AF_BTH;
// m_btAddr was obtained earlier via device enumeration
sockAddr.btAddr = m_btAddr;
...
int nResult = connect(btSocket, (SOCKADDR*)&sockAddr,
                      sizeof(sockAddr));

As you see, there is nothing unusual in the above snippet except for the address family, the socket address itself, and the given protocol. The most interesting question here is what should be a port value to connect to? To answer this question, you need to determine the RFComm channel associated with SPP service because in this sample you want to use BTHPROTO_RFCOMM. In case of BTHPROTO_L2CAP, you will be required to set up the appropriate stuff too. But here, you will focus on RFComm for it is supported in Widcomm stack as well.

Now, you get to the problem of RFComm channel detection. You will find everything required to perform this task in bthapi.h. There are a number of COM interfaces responsible for various Bluetooth operations. You will be interested in SDP. Having said this, you should call

...
CoInitializeEx (0, COINIT_MULTITHREADED);
...
CoUninitialize ();
...

somewhere in the code.

To obtain a RFComm channel associated with SPP service from given device, you need to run a query similar to the one that you used for device enumeration, but this time with more restrictions:

int DetectRFCommChannel(BT_ADDR *pbBtAddr)
{
   int iResult = 0;

   BTHNS_RESTRICTIONBLOB RBlob;
   memset (&RBlob, 0, sizeof(RBlob));

   RBlob.type                   = SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST;
   RBlob.numRange               = 1;
   RBlob.pRange[0].minAttribute = SDP_ATTRIB_PROTOCOL_DESCRIPTOR_LIST;
   RBlob.pRange[0].maxAttribute = SDP_ATTRIB_PROTOCOL_DESCRIPTOR_LIST;
   RBlob.uuids[0].uuidType      = SDP_ST_UUID16;
   RBlob.uuids[0].u.uuid16      = SerialPortServiceClassID_UUID16;

   BLOB blob;
   blob.cbSize    = sizeof(RBlob);
   blob.pBlobData = (BYTE *)&RBlob;

   SOCKADDR_BTH   sa;
   memset (&sa, 0, sizeof(sa));

   *(BT_ADDR *)(&sa.btAddr) = *pbBtAddr;
   sa.addressFamily = AF_BT;

   CSADDR_INFO      csai;
   memset (&csai, 0, sizeof(csai));
   csai.RemoteAddr.lpSockaddr = (sockaddr *)&sa;
   csai.RemoteAddr.iSockaddrLength = sizeof(sa);

   WSAQUERYSET      wsaq;
   memset (&wsaq, 0, sizeof(wsaq));
   wsaq.dwSize      = sizeof(wsaq);
   wsaq.dwNameSpace = NS_BTH;
   wsaq.lpBlob      = &blob;
   wsaq.lpcsaBuffer = &csai;

   HANDLE hLookup;
   int iRet = WSALookupServiceBegin (&wsaq, 0, &hLookup);
   if (ERROR_SUCCESS == iRet)
   {
      CHAR buf[5000];
      LPWSAQUERYSET pwsaResults = (LPWSAQUERYSET) buf;
      DWORD dwSize  = sizeof(buf);

      memset(pwsaResults,0,sizeof(WSAQUERYSET));
      pwsaResults->dwSize      = sizeof(WSAQUERYSET);
      pwsaResults->dwNameSpace = NS_BTH;
      pwsaResults->lpBlob      = NULL;

      iRet = WSALookupServiceNext (hLookup, 0, &dwSize, pwsaResults);
      if (iRet == ERROR_SUCCESS)
      {
         unsigned char cChannel = 0;
         if (ERROR_SUCCESS == FindRFCOMMChannel(
                              pwsaResults->lpBlob->pBlobData,
                              pwsaResults->lpBlob->cbSize,
                              &cChannel))
                              iResult = cChannel;
      }

      WSALookupServiceEnd(hLookup);
   }

   return iResult;
}

An important difference from previous query is that you have specified the previously found BT address and service. All above structures are documented in MSDN, so you can consult it for more detailed information.

If the lookup was successful, FindRFCOMMChannel tries to get the associated channel number. You will find the details below. Note that you need to link your application with two libs (see the #pragma statements). This function looks ugly at first glance, but actually there is nothing special. All the black job is done for you by the ISdpStream interface, which verifies the data obtained from a service lookup (see the snippet above) and loads ISdpRecords to the allocated memory pool. The snippet then loops through all records in the table and tries to detect the RFCOMM protocol and associated channel number:

#include <winsock2.h>
#include <ws2bth.h>
#include <bt_sdp.h>
#include <bthapi.h>
#include <bt_api.h>

#pragma comment(lib,"ws2.lib")
#pragma comment(lib,"bthguid.lib")
...
int IsRfcommUuid(NodeData *pNode)
{
   if (pNode->type != SDP_TYPE_UUID)
      return FALSE;

   if (pNode->specificType      == SDP_ST_UUID16)
      return (pNode->u.uuid16   == RFCOMM_PROTOCOL_UUID16);
   else if (pNode->specificType == SDP_ST_UUID32)
      return (pNode->u.uuid32   == RFCOMM_PROTOCOL_UUID16);
   else if (pNode->specificType == SDP_ST_UUID128)
      return (0 == memcmp(&RFCOMM_PROTOCOL_UUID,&pNode->
              u.uuid128,sizeof(GUID)));

   return FALSE;
}

HRESULT FindRFCOMMChannel (unsigned char *pStream, int cStream,
                           unsigned char& nChannel)
{
   ISdpRecord **pRecordArg = NULL;
   int cRecordArg          = 0;
   ISdpStream *pIStream    = NULL;
   HRESULT hr              = 0;
   ULONG ulError           = 0;

   nChannel = 0;

   hr = CoCreateInstance(__uuidof(SdpStream),NULL,
                         CLSCTX_INPROC_SERVER,
                         __uuidof(ISdpStream),(LPVOID *)
                         &pIStream);

   if ( FAILED(hr) || pIStream == NULL )
      return hr;

   hr = pIStream->Validate (pStream, cStream,&ulError);

   if (SUCCEEDED(hr))
   {
      hr = pIStream->VerifySequenceOf(pStream, cStream,
                                      SDP_TYPE_SEQUENCE,NULL,
                                      (ULONG *)&cRecordArg);

      if (SUCCEEDED(hr) && cRecordArg > 0)
      {
         pRecordArg =
            (ISdpRecord **) CoTaskMemAlloc(sizeof(ISdpRecord*)
                                           * cRecordArg);

         if (pRecordArg != NULL)
         {
            hr =
               pIStream->RetrieveRecords(pStream, cStream,
                                         pRecordArg,(ULONG *)
                                         &cRecordArg);

            if ( FAILED(hr) )
            {
               CoTaskMemFree(pRecordArg);
               pRecordArg = NULL;
               cRecordArg = 0;
            }
         }
         else
         {
            hr = E_OUTOFMEMORY;
         }
     }
   }

   if (pIStream != NULL)
   {
      pIStream->Release();
      pIStream = NULL;
   }

   if ( FAILED(hr) )
      return hr;

   for (int i = 0; (nChannel == 0) && (i < cRecordArg); i++)
   {
      ISdpRecord *pRecord = pRecordArg[i];
      // contains SDP_ATTRIB_PROTOCOL_DESCRIPTOR_LIST data,
      // if available
      NodeData protocolList;

      if (ERROR_SUCCESS !=
          pRecord->GetAttribute(SDP_ATTRIB_PROTOCOL_DESCRIPTOR_LIST,
                                &protocolList) ||
                                (protocolList.type !=
                                 SDP_TYPE_CONTAINER))
      {
         if (protocolList.type == SDP_TYPE_STRING)
            CoTaskMemFree(protocolList.u.str.val);
         else if (protocolList.type == SDP_TYPE_URL)
            CoTaskMemFree(protocolList.u.url.val);
         continue;
      }

      ISdpNodeContainer *pRecordContainer = protocolList.u.container;
      int cProtocols = 0;
      NodeData protocolDescriptor;

      pRecordContainer->GetNodeCount((DWORD *)&cProtocols);
      for (int j = 0; (nChannel == 0) && (j < cProtocols); j++)
      {
         pRecordContainer->GetNode(j,&protocolDescriptor);

         if (protocolDescriptor.type != SDP_TYPE_CONTAINER)
            continue;

         ISdpNodeContainer *pProtocolContainer =
            protocolDescriptor.u.container;
         int cProtocolAtoms = 0;
         pProtocolContainer->GetNodeCount((DWORD *)&cProtocolAtoms);

         for (int k = 0; (nChannel == 0) && (k < cProtocolAtoms); k++)
         {
            NodeData nodeAtom;
            pProtocolContainer->GetNode(k,&nodeAtom);

            if (IsRfcommUuid(&nodeAtom))
            {
               if (k+1 == cProtocolAtoms)
               {
                  // Error: Channel ID should follow RFCOMM uuid
                  break;
               }

               NodeData channelID;
               pProtocolContainer->GetNode(k+1,&channelID);

               switch(channelID.specificType)
               {
               case SDP_ST_UINT8:
                  nChannel = channelID.u.uint8;
                  break;
               case SDP_ST_INT8:
                  nChannel = channelID.u.int8;
                  break;
               case SDP_ST_UINT16:
                  nChannel = channelID.u.uint16;
                  break;
               case SDP_ST_INT16:
                  nChannel = channelID.u.int16;
                  break;
               case SDP_ST_UINT32:
                  nChannel = channelID.u.uint32;
                  break;
               case SDP_ST_INT32:
                  nChannel = channelID.u.int32;
                  break;
               default:
                  nChannel = 0;
               }
               break;
            }
         }
      }
      if (protocolList.type == SDP_TYPE_STRING)
         CoTaskMemFree(protocolList.u.str.val);
      else if (protocolList.type == SDP_TYPE_URL)
         CoTaskMemFree(protocolList.u.url.val);
   }

   // cleanup
   for (i = 0; i < cRecordArg; i++)
      pRecordArg[i]->Release();
   CoTaskMemFree(pRecordArg);

   return (nChannel != 0) ? S_OK : S_FALSE;
}

The preceding code doesn't pretend to be too elegant, so you're invited to polish it. The main thing is that you fulfilled your goal: a channel number associated with a service is successfully detected, so you can use BT sockets just as normal ones:

...
sockAddr.port = nChannel & 0xff;
int nResult   = connect(btSocket, (SOCKADDR*)&sockAddr,
                        sizeof(sockAddr));

Conclusion

In the last two articles, you have see two of the most popular BT stacks: from Widcomm and Microsoft. They are different, but nevertheless may be wrapped together. I hope that the sample snippets provide enough basic information to use them in your own applications with both stacks. But, as usual, the task of studying tons of technical documentation is inevitable if you need more details than were given here...

About the Author

Alex Gusev started to play with mainframes at the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. After working almost a decade for an international retail software company as a team leader of the Windows Mobile R department, he has decided to dive into Symbian OS ™ Core development.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date