0% found this document useful (0 votes)
51 views

Bluetooth - Programming Microsoft Windows CE .NET - 3rd Edition - Douglas Boling

Bluetooth - Programming Microsoft Windows CE .NET - 3rd Edition - Douglas Boling

Uploaded by

jei li
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
51 views

Bluetooth - Programming Microsoft Windows CE .NET - 3rd Edition - Douglas Boling

Bluetooth - Programming Microsoft Windows CE .NET - 3rd Edition - Douglas Boling

Uploaded by

jei li
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 35

Bluetooth

Bluetooth is the name of a wireless interface standard that uses radio frequency (RF) as its
medium instead of infrared frequency, as is used with IrDA. Bluetooth is designed to be a
successor to IrDA, providing the file transfer capabilities of IrDA along with a number of other
capabilities centering on cableless connections.

Bluetooth is named for Harald Blåtand (Bluetooth), who was king of Denmark from 940 to 985.
Harald was the grandson of King Ethelred of England and the grandfather of King Canute,
famous for demonstrating the limits of kingly power by commanding the tide not to come in For
those wondering, the tide came in anyway. Harald’s claim to fame is the unification of Denmark
and Norway during his rule. One thousand ten years later, following an Ericsson-initiated
feasibility study of using a low-power radio frequency network to link peripherals, a special
interest group (SIG) was formed with Ericsson, IBM, Toshiba, Nokia, and Intel to organize and
form a standard under the codename Bluetooth. That catchy code name was soon chosen as the
actual name of the standard.

Although it has taken longer than expected for Bluetooth-enabled devices to reach the
mainstream, the number of devices supporting Bluetooth has grown. Following this trend, a
number of Pocket PC and other Windows CE devices now include support for Bluetooth. Windows
CE 4.0 .NET provides integrated support for the Bluetooth protocol, which is also supported by
the Pocket PC 2003. Some Pocket PC OEMs use third-party Bluetooth software on their devices
instead of the Windows CE stack. This Bluetooth discussion covers only the Windows CE
Bluetooth API. To program third-party Bluetooth stacks, developers should contact the device
manufacturers for information.

Bluetooth functionality is centered on profiles that define services provided to the user. Profiles
include Cordless Telephony, Intercom, Headset, Fax, Dial-Up Networking, LAN Access, Object
Push, Synchronization, and File Transfer. Not all profiles are supported by all devices. In fact,
most devices support only a very few profiles relevant to the device.

Windows CE provides the Dial-up Networking, LAN Access, Object Push and File Transfer profiles
out of the box, although OEMs are free to add support for other profiles in their products. The
Pocket PC 2003 provides support for Object Push and File Transfer profiles. OEMs add support
for additional profiles, such as a headset profile for wireless headsets.

The applications, such as Pocket Inbox and Pocket Outlook, that are bundled with the devices
support Bluetooth for file transfer, business card exchange, and synchronization. Working with
these applications is preferable to writing code to work directly with the Bluetooth API because of
the complexity of that API.

For those who are interested in working directly with the Bluetooth API, the task isn’t easy, clean,
or quick. Part of the problem is the flexibility of the Bluetooth standard and the complexity of the
discovery protocol that communicates which services are available from a device. Before we can
dive into this code, a bit of background is necessary.
Stack

A diagram of the Bluetooth stack is shown in Figure 14-3. The lower three layers—Baseband, Link Manager
Protocol, and the first Host Controller Interface (HCI) layer—are implemented in the Bluetooth hardware.
The layers above the hardware and below the application are provided by Windows CE, although it’s
possible for third parties to extend the Bluetooth stack by providing additional profiles above the HCI layer.

Figure 14-3. A diagram of the Bluetooth stack on Windows CE

Applications interact with the Bluetooth stack through one of two interfaces. The preferred
method is for applications to use the Winsock API to access the Bluetooth stack. Just as with
IrDA, applications use standard Winsock functions to open sockets associated with the Bluetooth
stack. Control is accomplished through various WSAxxx functions. Data transfer is accomplished
through the standard socket send and recv functions.

Winsock support for Bluetooth depends on the Winsock stack installed on the device. If the
system has Winsock 2.0 installed, such as the Pocket PC 2003, Bluetooth functionality is accessed
directly through Winsock calls such as setsockopt. For systems with Winsock 1.1 installed, the
Bluetooth stack needs to be configured through a dedicated Bluetooth API. For example, to query
the current mode of an asynchronous connection, an application can use the dedicated function
BthGetCurrentMode or, if Winsock 2.0 is on the system, a call to getsockopt with the option name
SO_BTH_GET_MODE.

The other way applications can work with Bluetooth is through virtual serial ports. With this
method, applications load a Bluetooth-dedicated serial driver. Control of the stack is
accomplished through DeviceIoControl calls to the COM driver. Calling WriteFile and ReadFile to
write and read the COM port sends and receives data across the Bluetooth connection.
Discovery

Before devices can communicate across a Bluetooth connection, devices and the services those
devices provide must be discovered. The discovery process is quite complex because of the
flexible nature of the Bluetooth feature set. Devices and services on particular devices can be
queried in a general way—all printers, for example—or they can be specifically queried—for
example, whether a particular device supports a particular service, such as the Headset-Audio-
Gateway service.

Both device discovery and service discovery are accomplished through the same series of
functions, albeit with significantly different parameters. The discovery process is accomplished
through a series of three functions: WSALookupServiceBegin, WSALookupServiceNext, and
WSALookupServiceEnd. These functions aren’t specific to Winsock 2.0, but in the discussion that
follows, I’m providing information only about using them in Bluetooth applications. A parallel
series of functions—BthNsLookupServiceBegin, BthNsLookupServiceNext, and
BthNsLookupServiceEnd—are functionally identical and can be used for systems with Winsock 1.1.
Although the function names imply a simple iterative search, the parameters required for the
search are daunting.

Device Discovery

To find local devices, an application first calls WSALookupServiceBegin, which is prototyped as

INT WSALookupServiceBegin (LPWSAQUERYSET pQuerySet, DWORD dwFlags,


LPHANDLE lphLookup);

The first parameter is a pointer to a WSAQUERYSET structure, which I’ll discuss shortly. For
T

device searches, the dwFlags parameter should contain the flag LUP_CONTAINERS. The other
allowable flags for this parameter will be covered in the upcoming discussion about service
queries. The final parameter should point to a handle value that will be filled in with a search
handle; this search handle will be used for the other calls in the search. The return value is an
HRESULT with 0, indicating success.
T

The WSAQUERYSET structure is defined as


T

typedef struct _WSAQuerySet {


DWORD dwSize;
LPTSTR lpszServiceInstanceName;
LPGUID lpServiceClassId;
LPWSAVERSION lpVersion;
LPTSTR lpszComment;
DWORD dwNameSpace;
LPGUID lpNSProviderId;
LPTSTR lpszContext;
DWORD dwNumberOfProtocols;
LPAFPROTOCOLS lpafpProtocols;
LPTSTR lpszQueryString;
DWORD dwNumberOfCsAddrs;
LPCSADDR_INFO lpcsaBuffer;
DWORD dwOutputFlags;
LPBLOB lpBlob;
} WSAQUERYSET, *PWSAQUERYSET;
The dwSize field should be set to the size of the structure. For device queries, the only other
fields that need to be used are the dwNameSpace field, which must be set to NS_BT, and the
lpBlob field, which should point to a BLOB structure. The remaining fields should be set to 0.

The BLOB structure pointed to by the lpBlob field is actually optional for the initial device query
call, but it’s recommended so that the time the Bluetooth stack spends looking for devices can be
defined. If the query time isn’t specified, the Bluetooth stack defaults to a rather long 15 to 20
seconds waiting for devices to respond. To define the query time, lpBlob points to a BLOB
structure that, in turn, points to a blob of a specific type. The generic BLOB structure is defined
as

typedef struct _BLOB {


ULONG cbSize;
BYTE* pBlobData;
} BLOB, LPBLOB;

The two fields are the size of the specific BLOB structure being pointed to a pointer to the
specific BLOB data. For device queries, the blob we’re interested in is an inquiry blob defined as

typedef struct _BTHNS_INQUIRYBLOB {


ULONG LAP;
unsigned char length;
unsigned char num_responses;
} BTHNS_INQUIRYBLOB, *PBTHNS_INQUIRYBLOB;

The first field should be set to BT_ADDR_GIAC, which is the general inquiry access code (GIAC),
defined as 0x9e8b33. The length field should be set to the time the stack should wait for devices
to respond. The unit of time for this field is a rather strange 1.28 seconds, so if you want to wait
approximately 5 seconds, the value 4 in the field will produce a wait of 4 1.28, or 5.12, seconds.
The final field, num_responses, specifies the maximum number of devices that need to respond
to end the query before the timeout value.

So before a call to WSALookupServiceBegin is made to query the available devices, the


WSAQUERYSET, BLOB, and BTHNS_INQUIRYBLOB structures should be initialized with the
T

WSAQUERYSET structure’s lpBlob field pointing to the BLOB structure. The BLOB structure should
T

be initialized so that the cbSize field contains the size of the BTHNS_INQUIRYBLOB structure and
the pBlobData field points to the BTHNS_INQUIRYBLOB structure. The BTHNS_INQUIRYBLOB
structure should be filled in with the search criteria.

When the call to WSALookupServiceBegin returns successfully, a call to WSALookupServiceNext is


made. Whereas the WSALookupServiceBegin call can take a number of seconds, the
WSALookupServiceNext call can return immediately as long as the data being requested has been
cached in the stack by the WSALookupServiceBegin call. The WSALookupServiceNext call is
defined as

INT WSALookupServiceNext (HANDLE hLookup, DWORD dwFlags,


LPDWORD lpdwBufferLength, LPWSAQUERYSET pResults);
The first parameter is the handle returned by WSALookupServiceBegin. The dwFlags parameter
contains a number of different flags that define the data returned by the function. The possible
flags are

• LUP_RETURN_NAME

Return the name of the remote device.

• LUP_RETURN_ADDRESS

Return the address of the remote device.

• LUP_RETURN_BLOB

Return BTHINQUIRYRESULT structure with information about the remote device.

• BTHNS_LUP_RESET_ITERATOR

Reset the enumeration so that the next call to WSALookupServiceNext will return
information about the first device in the list.

• BTHNS_LUP_NO_ADVANCE

Return information about a device but don’t increment the device index so that the next
call to WSALookupServiceNext returns information about the same device.

The final two parameters are the address of a variable that contains the size of the output buffer
and a pointer to the output buffer. Although the output buffer pointer is cast as a pointer to a
WSAQUERYSET structure, the buffer passed to WSALookupServiceNext should be significantly
T

larger than the structure so that the function can marshal any strings into the buffer beyond the
end of the structure itself.

When the function returns without error, the WSAQUERYSET structure pointed to by pResults
T

contains information about a Bluetooth device. The name of the device, if requested with the
LUP_RETURN_NAME flag, is pointed to by the lpszServiceInstanceName field. The address of the
remote device is contained in the CSADDR_INFO structure pointed to by lpcsaBuffer.
CSADDR_INFO provides information about the local and remote device addresses and is defined
as

typedef struct _CSADDR_INFO {


SOCKET_ADDRESS LocalAddr;
SOCKET_ADDRESS RemoteAddr;
INT iSocketType;
INT iProtocol;
} CSADDR_INFO;

The SOCKET_ADDRESS fields are filled in with Bluetooth-specific SOCKADDR_BTH addresses, so


to get the remote address, the RemoteAddr field should be properly cast, as in

bt = ((SOCKADDR_BTH *)
pQueryResult->lpcsaBuffer->RemoteAddr.lpSockaddr)->btAddr;
Each call to WSALookupServiceNext returns information about a single device. The function
should be called repeatedly until it returns SOCKET_ERROR. If GetLastError returns
WSA_E_NO_MORE, there was no error; there are simply no more devices to be found.

After completing the WSALookupServiceNext loop, the program should call


WSALookupServiceEnd to clean up any resources the Winsock stack has maintained during the
search. The function is prototyped as

INT WSALookupServiceEnd (HANDLE hLookup);

The single parameter is the handle returned by WSALookupServiceBegin.

The following routine queries the Bluetooth devices that are in range and returns their names
and addresses in an array.

#define MYBUFFSIZE 16384


typedef struct {
TCHAR szName[256];
BT_ADDR btaddr;
} MYBTDEVICE, *PMYBTDEVICE;
//
// FindDevices - Find devices in range.
//
int FindDevices (PMYBTDEVICE pbtDev, int *pnDevs) {
DWORD dwFlags, dwLen;
HANDLE hLookup;
int i, rc;

// Create inquiry blob to limit time of search


BTHNS_INQUIRYBLOB inqblob;
memset (&inqblob, 0, sizeof (inqblob));
inqblob.LAP = BT_ADDR_GIAC; // Default GIAC
inqblob.length = 4; // 4 * 1.28 = 5 seconds
inqblob.num_responses = *pnDevs;

// Create blob to point to inquiry blob


BLOB blob;
blob.cbSize = sizeof (BTHNS_INQUIRYBLOB);
blob.pBlobData = (PBYTE)&inqblob;

// Init query
WSAQUERYSET QuerySet;
memset (&QuerySet,0,sizeof (WSAQUERYSET));
QuerySet.dwSize = sizeof (WSAQUERYSET);
QuerySet.dwNameSpace = NS_BTH;
QuerySet.lpBlob = &blob;

// Start query for devices


rc = WSALookupServiceBegin (&QuerySet, LUP_CONTAINERS, &hLookup);
if (rc) return rc;

// Allocate output buffer


PBYTE pOut = (PBYTE)LocalAlloc (LPTR, MYBUFFSIZE);
if (!pOut) return -1;
WSAQUERYSET *pQueryResult = (WSAQUERYSET *)pOut;

// Loop through the devices by repeatedly calling WSALookupServiceNext


for (i = 0; i < *pnDevs; i++) {
dwLen = MYBUFFSIZE;
dwFlags = LUP_RETURN_NAME │ LUP_RETURN_ADDR;
rc = WSALookupServiceNext (hLookup, dwFlags, &dwLen, pQueryResult);
if (rc == SOCKET_ERROR) {
rc = GetLastError();
break;
}
// Copy device name
lstrcpy (pbtDev[i].szName, pQueryResult->lpszServiceInstanceName);

// Copy Bluetooth device address


SOCKADDR_BTH *pbta;
pbta = (SOCKADDR_BTH *)
pQueryResult->lpcsaBuffer->RemoteAddr.lpSockaddr;
pbtDev[i].btaddr = pbta->btAddr;
}
// See if we left the loop simply because there were no more devices
if (rc == WSA_E_NO_MORE) rc = 0;

// Return the number of devices found


*pnDevs = i;

// Clean up
WSALookupServiceEnd (hLookup);
LocalFree (pOut);
return rc;
}

The preceding routine uses WSALookupServiceBegin, WSALookupServiceNext, and


WSALookupServiceEnd to iterate through the Bluetooth devices in range. The routine could query
other information about the remote devices by passing the LUP_RETURN_BLOB flag in
WSALookupServiceNext, but the information returned isn’t needed to connect to the device.

Service Discovery

Once the device of interest is found, the next task is to discover whether that device supplies the
service needed. Services are identified in a multilevel fashion. The service can publish itself under
a generic service such as printer or fax service or publish itself under a specific unique identifier,
or GUID.

If you know the specific service as well as its documented GUID, there is no need for service
discovery. Simply connect a Bluetooth socket to the specific service as discussed in the
“Bluetooth” section on page 668. If, however, you don’t know the exact service GUID, you must
take on the task of service discovery.

Querying services is accomplished through the same WSALookupServiceBegin,


WSALookupServiceNext, and WSALookupServiceEnd functions discussed earlier in the device
discovery section. As with device discovery, the initial query is accomplished with a call to
WSALookupServiceBegin. To query the services on a remote device, set the dwFlags parameter
to 0 instead of using the LUP_CONTAINERS flag. To query the service provided by the local
system instead of remote devices, set the LUP_RES_SERVICE flag in the dwFlags parameter.

When you’re querying the services of another device, the WSAQUERYSET structure needs to
T

specify the target device that’s being queried. This is accomplished by referencing a restriction
blob in the WSAQUERYSET structure. The restriction blob is defined as
T
typedef struct _BTHNS_RESTRICTIONBLOB {
ULONG type;
ULONG serviceHandle;
SdpQueryUuid uuids[12];
ULONG numRange;
SdpAttributeRange pRange[1];
} BTHNS_RESTRICTIONBLOB;

The type field specifies whether the query should check for services, attributes of the services, or
both attributes and services by specifying the flags SDP_SERVICE_SEARCH_REQUEST, T

SDP_SERVICE_ATTRIBUTE_REQUEST, and SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST,


T

respectively. The serviceHandle parameter is used in attribute-only searches to specify the


service being queried. If the services are being queried, the uuids array contains up to 12 service
IDs to check. The service IDs are specified in an SdpQueryUuid structure defined as

typedef struct _SdpQueryUuid {


SdpQueryUuidUnion u;
USHORT uuidType;
} SdpQueryUuid;

The SdpQueryUuid structure allows the service IDs to be specified as 16-, 32-, or 128-bit ID
values. The ID values for documented services are provided in the Bluetooth include file Bt_sdp.h
in the SDK.

When you’re querying attributes for a service or services, the pRange array can specify the
minimum and maximum attribute range to query. The size of the pRange array is specified in the
numRange parameter. In the following code, a specific service is queried to see whether it exists
on the device, and if it does, the query also returns the attributes associated with the service.

int QueryService (HWND hWnd, BT_ADDR bta, GUID *pguid) {


DWORD dwFlags, dwLen;
HANDLE hLookup;
TCHAR szDeviceName[256];
LPWSAQUERYSET pQuerySet;
PBYTE pQuery;
int i, rc;

pQuery = (PBYTE)LocalAlloc (LPTR, MYBUFFSIZE);


if (!pQuery) return 0;

pQuerySet = (LPWSAQUERYSET)pQuery;
memset (pQuerySet, 0, MYBUFFSIZE);
pQuerySet->dwSize = sizeof (WSAQUERYSET);
pQuerySet->dwNameSpace = NS_BTH;

// Specify device
CSADDR_INFO csi;
memset (&csi, 0, sizeof (csi));

SOCKADDR_BTH sa;
memset (&sa, 0, sizeof (sa));
sa.btAddr = bta;
sa.addressFamily = AF_BT;

// Specify the remote device address


csi.RemoteAddr.lpSockaddr = (LPSOCKADDR) &sa;
csi.RemoteAddr.iSockaddrLength = sizeof(SOCKADDR_BTH);
pQuerySet->lpcsaBuffer = &csi;
pQuerySet->dwNumberOfCsAddrs = 1;
// Form query based on service class being checked
BTHNS_RESTRICTIONBLOB btrblb;
memset (&btrblb, 0, sizeof (btrblb));
btrblb.type = SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST;
btrblb.numRange = 1;
btrblb.pRange[0].minAttribute = 0;
btrblb.pRange[0].maxAttribute = 0xffff;
btrblb.uuids[0].uuidType = SDP_ST_UUID128; //Define search type
memcpy (&btrblb.uuids[0].u.uuid128, pguid, sizeof (GUID));

// Create blob to point to restriction blob


BLOB blob;
blob.cbSize = sizeof (BTHNS_RESTRICTIONBLOB);
blob.pBlobData = (PBYTE)&btrblb;
pQuerySet->lpBlob = &blob;
dwFlags = 0;

rc = WSALookupServiceBegin (pQuerySet, dwFlags, &hLookup);


if (rc) return rc;

// Setup query set for ServiceNext call


pQuerySet->dwNumberOfCsAddrs = 1;
pQuerySet->lpszServiceInstanceName = szDeviceName;
memset (szDeviceName, 0, sizeof (szDeviceName));

dwFlags = LUP_RETURN_NAME │ LUP_RETURN_ADDR;


dwLen = MYBUFFSIZE;
while ((rc = WSALookupServiceNext (hLookup, dwFlags, &dwLen,
pQuerySet)) == 0) {
ISdpRecord **pRecordArg;
int cRecordArg = 0;

// Setup attribute query


HRESULT hr = ParseBlobToRecs (pQuerySet->lpBlob->pBlobData,
pQuerySet->lpBlob->cbSize,
&pRecordArg, (ULONG *)&cRecordArg);
if (hr == ERROR_SUCCESS) {
// Parse the records

// Clean up records
for (i = 0; i < cRecordArg; i++)
pRecordArg[i]->Release();
CoTaskMemFree(pRecordArg);
}
dwLen = MYBUFFSIZE;
i++;
}
rc = WSALookupServiceEnd (hLookup);
LocalFree (pQuery);
return rc;
}

Notice that in this code, the Service Discovery Protocol (SDP) data for the service is returned in
the buffer pointed to by the lpBlob structure. This data isn’t parsed in the routine. Instead, a
routine named ParseBlobToRecs is called to parse the data. The routine ParseBlobToRecs, shown
here, returns a series of ISdpRecord interface pointers, one for each record in the SDP data.

//
// ParseBlobToRecs - Use ISdpStream object to parse the response from the
// SDP server.
//
HRESULT ParseBlobToRecs (UCHAR *pbData, DWORD cbStream,
ISdpRecord ***pppSdpRecords, ULONG *pcbRec) {
HRESULT hr;
ULONG ulError;
ISdpStream *pIStream = NULL;
*pppSdpRecords = NULL;
*pcbRec = 0;

hr = CoCreateInstance (__uuidof(SdpStream), NULL,


CLSCTX_INPROC_SERVER, __uuidof(ISdpStream),
(LPVOID *)&pIStream);
if (FAILED(hr)) return hr;
// Validate SDP data blob
hr = pIStream->Validate (pbData, cbStream, &ulError);

if (SUCCEEDED(hr)) {
hr = pIStream->VerifySequenceOf (pbData, cbStream,
SDP_TYPE_SEQUENCE, NULL, pcbRec);
if (SUCCEEDED(hr) && *pcbRec > 0) {
*pppSdpRecords = (ISdpRecord **)CoTaskMemAlloc (
sizeof (ISdpRecord*) *
(*pcbRec));
if (pppSdpRecords != NULL) {
hr = pIStream->RetrieveRecords (pbData, cbStream,
*pppSdpRecords, pcbRec);
if (!SUCCEEDED(hr)) {
CoTaskMemFree (*pppSdpRecords);
*pppSdpRecords = NULL;
*pcbRec = 0;
}
}
else
hr = E_OUTOFMEMORY;
}
}
if (pIStream != NULL) {
pIStream->Release();
pIStream = NULL;
}
return hr;
}

The routine returns the data in an array of ISdpRecord pointers. It’s left to the reader to parse
the record data using the other interfaces provided in the Bluetooth API.

Publishing a Service

The other side of service discovery is service publication. Bluetooth applications that want to
provide a service to other applications must do more than simply create a Bluetooth socket, bind
the socket, and call accept as would an IrDA service. In addition to the socket work, the service
must publish the details of the service through the SDP API.

The actual publication of a service is actually quite simple. All that’s necessary is to call
WSASetService, which is prototyped as

INT WSASetService (LPWSAQUERYSET lpqsRegInfo, WSAESETSERVICEOP essoperation,


DWORD dwControlFlags);
The three parameters are a pointer to a WSAQUERYSET structure; a service operation flag, which
T

needs to be set to RNRSERVICE_REGISTER; and a dwControlFlags parameter set to 0.

If only registration were that simple. The problem isn’t calling the function; it’s composing the
SDP data that’s placed in the WSAQUERYSET structure. The dwNameSpace field should be set to
NS_BTH. And, as with the discovery process, the blobs are involved. The blob used in setting the
service is a BTHNS_SETBLOB structure defined as

typedef struct _BTHNS_SETBLOB {


ULONG* pRecordHandle;
ULONG fSecurity;
ULONG fOptions;
ULONG ulRecordLength;
UCHAR pRecord[1];
} BTHNS_SETBLOB, *PBTHNS_SETBLOB;

The first parameter points to a ULONG that will receive a handle for the SDP record being created.
The fSecurity and fOptions fields are reserved and should be set to 0. The ulRecordLength
parameter should be set to the length of the SDP record to publish, whereas pRecord is the
starting byte of the byte array that is the SDP record to publish.

The following code demonstrates publishing an SDP record. The routine is passed an SDP record
and its size. It then initializes the proper structures and calls WSASetService to publish the record.

int PublishRecord (HWND hWnd, PBYTE pSDPRec, int nRecSize, ULONG *pRecord) {
BTHNS_SETBLOB *pSetBlob;
ULONG ulSdpVersion = BTH_SDP_VERSION;
int rc;

// Zero out the record handle that will be returned by the call
*pRecord = 0;

// Allocate and init the SetBlob


pSetBlob = (BTHNS_SETBLOB *)LocalAlloc (LPTR,
sizeof (BTHNS_SETBLOB) + nRecSize);
if (!pSetBlob) return -1;

pSetBlob->pRecordHandle = pRecord;
pSetBlob->pSdpVersion = &ulSdpVersion;
pSetBlob->fSecurity = 0;
pSetBlob->fOptions = 0;
pSetBlob->ulRecordLength = nRecSize;
memcpy (pSetBlob->pRecord, pSDPRec, nRecSize);

// Init the container blob


BLOB blob;
blob.cbSize = sizeof(BTHNS_SETBLOB) + SDP_RECORD_SIZE - 1;
blob.pBlobData = (PBYTE) pSetBlob;

// Init the WSAQuerySet struct


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

// Publish the service


rc = WSASetService(&Service, RNRSERVICE_REGISTER, 0);
if (rc == SOCKET_ERROR) rc = GetLastError();
// Clean up
LocalFree ((PBYTE)pSetBlob);
return rc;
}

When the application no longer wants to support the service, it needs to remove the record from
the SDP database. Removing the record is accomplished by using WSASetService, specifying the
record handle of the service and the flag RNRSERVICE_DELETE. The record handle is passed in
the BTHNS_SETBLOB structure. The other fields of this structure are ignored. The following code
shows a routine that unregisters a service.

int UnpublishRecord (ULONG hRecord) {


ULONG ulSdpVersion = BTH_SDP_VERSION;
int rc;

BTHNS_SETBLOB SetBlob;
memset (&SetBlob, 0, sizeof (SetBlob));
SetBlob.pRecordHandle = &hRecord;
SetBlob.pSdpVersion = &ulSdpVersion;

// Init the container blob


BLOB blob;
blob.cbSize = sizeof(BTHNS_SETBLOB);
blob.pBlobData = (PBYTE) &SetBlob;

// Init the WSAQuerySet struct


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

// Unpublish the service


rc = WSASetService(&Service, RNRSERVICE_DELETE, 0);
return rc;
}

SDP Records

The format of the SDP information that’s published is so complex that Windows CE provides a
special COM control to construct and deconstruct SDP records. Even with the control, parsing
SDP records isn’t easy. The first problem is knowing what’s required in the SDP record. The
information in the SDP record is defined by the Bluetooth specification, and a complete
explanation of this data far exceeds the space available for such an explanation.

As a shortcut, many Bluetooth applications compose a generic record, either hand-assembling


the record or using an example tool named BthNsCreate that’s provided in the Platform Builder.
These hand-generated records are saved as a byte array in the application. The known offsets
where the GUID and the RFCOMM channel are stored are known and are updated in the array at
run time. The record is then published using WSASetService, as shown earlier.

The following code shows a routine that uses a canned SDP record with the GUID of the service
and the channel stuffed into the appropriate places in the record.

int RegisterService (HWND hWnd, GUID *pguid, byte bChannel, ULONG *pRecord) {
// SDP dummy record
// GUID goes at offset 8
// Channel goes in last byte of record.
static BYTE bSDPRecord[] = {
0x35, 0x27, 0x09, 0x00, 0x01, 0x35, 0x11, 0x1C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x09, 0x00, 0x04, 0x35, 0x0C, 0x35, 0x03, 0x19, 0x01,
0x00, 0x35, 0x05, 0x19, 0x00, 0x03, 0x08, 0x00};

// Translate guid into net byte order for SDP record


GUID *p = (GUID *)&bSDPRecord[8];
p->Data1 = htonl (pguid->Data1);
p->Data2 = htons (pguid->Data2);
p->Data3 = htons (pguid->Data3);
memcpy (p->Data4, pguid->Data4, sizeof (pguid->Data4));

// Copy channel value into record


bSDPRecord[sizeof (bSDPRecord)-1] = bChannel;

return PublishRecord (hWnd, bSDPRecord, sizeof (bSDPRecord), pRecord);


}

Bluetooth Communication with Winsock

The hard part of Bluetooth communication is the setup. Once a service is published, the
communication with remote devices is simple regardless of the method, Winsock or virtual COM
port, used by the application.

As with IrDA, using Winsock to communicate over Bluetooth consists of implementing a


client/server design with the server creating a socket that’s bound to an address and a client that
connects to the server socket by specifying the address and port of the server.

Server Side

A Bluetooth application providing a service first must set up a server routine that creates a socket
and performs all the necessary calls to support the server side of a socket communication. The
task starts with creating a socket with the standard socket call. The address format of the socket
should be set to AF_BT, indicating a socket bound to the Bluetooth transport.

Once created, the socket needs to be bound with a call to bind. The following code shows a
socket being created followed by a call to bind the socket. The address the socket is bound to is
left blank, indicating that the system will provide the proper settings. The address format for the
Bluetooth address used in the bind call is set to AF_BT.

// Open a bluetooth socket


s_sock = socket (AF_BT, SOCK_STREAM, BTHPROTO_RFCOMM);
if (s_sock == INVALID_SOCKET)
return -1;

// Fill in address stuff


memset (&btaddr, 0, sizeof (btaddr));
btaddr.addressFamily = AF_BT;
btaddr.port = 0; // Let driver assign a channel

// Bind to socket
rc = bind (s_sock, (struct sockaddr *)&btaddr, sizeof (btaddr));
if (rc) {
closesocket (s_sock);
return -2;
}
// Get information on the port assigned
len = sizeof (btaddr);
rc = getsockname (s_sock, (SOCKADDR *)&btaddr, &len);
if (rc) {
closesocket (s_sock);
return 0;
}
// Tell the world what we've bound to.
printf ("Addr %04x.%08x, port %d", GET_NAP(btaddr.btAddr),
GET_SAP(btaddr.btAddr), btaddr.port)

Once the call to bind succeeds, the code calls getsockname, which fills in the details of the
address of the device and, more important, the Bluetooth RFCOMM channel the socket was
bound to. This RFCOMM channel is important since it will need to be published with the SDP
record so that other devices will know which port to connect to when connecting to the service.
The macros in the printf statement in the preceding code demonstrate the division of the
Bluetooth device address into its two parts: the NAP, or nonsignificant address portion, and the
SAP, or significant address portion.

Once the RFCOMM channel is known, the SDP record can be constructed and published as shown
earlier in this section. The socket is then placed in listen mode, and a call to accept is made,
which blocks until a client application socket connects to the address. When the client does
connect, the accept call returns with the handle of a new socket that’s connected with the client.
This new socket is then used to communicate with the client device.

Client Side

On the client side, the task of connecting starts with device discovery. Once the Bluetooth
address of the client is determined, the client can create a thread that will communicate with the
server. The process mirrors any socket-based client with calls to create the socket, and the client
connects the socket to the remote server by specifying the address of the server. In the case of a
Bluetooth client, the address of the server must include either the RFCOMM channel or the GUID
of the service being connected to. In the following code, a client connects to a remote service
knowing the remote device’s Bluetooth address and the GUID of the client.

// Open a bluetooth socket


t_sock = socket (AF_BT, SOCK_STREAM, BTHPROTO_RFCOMM);
if (t_sock == INVALID_SOCKET)
return 0;

// Fill in address stuff


memset (&btaddr, 0, sizeof (btaddr));
btaddr.btAddr = btaddrTarget;
btaddr.addressFamily = AF_BT;
btaddr.port = 0; // Let driver assign a channel
memcpy (&btaddr.serviceClassId, &guidbthello, sizeof (GUID));

// Connect to remote socket


rc = connect (t_sock, (struct sockaddr *)&btaddr, sizeof (btaddr));
if (rc) {
closesocket (t_sock);
return -4;
}
// Connected...
Once the client is connected, data can be exchanged with the server with the standard socket
routines send and recv. When the conversation is concluded, both client and server should close
their respective sockets with a call to closesocket.

Bluetooth Communication with Virtual COM Ports

If using Winsock for communication isn’t to your liking, the Windows CE Bluetooth stack can also
be accessed by using a serial driver that can be loaded. This method has a number of
shortcomings, but some developers prefer it to using Winsock because of the familiarity of using
a simple serial port compared with the complexity of Winsock. In any case, before I show you
how to use the virtual serial port method, a few of the problems should be discussed.

The first problem is that the Bluetooth driver name is already the most used driver name in
Windows CE. The Windows CE stream driver architecture is such that the operating system is
limited to 10 instances of a given driver name, such as COM or WAV. Since typically 2 to 4
instances of serial drivers are already in a Windows CE system, the available number of virtual
COM ports is limited. Also, since the Bluetooth stack typically exposes some of its profiles through
COM ports, the 2 to 4 number quickly increases to 6 to 8 ports, leaving only 2 to 4 available COM
driver instances for Bluetooth applications that want to use virtual COM ports. An intrepid
programmer could register the Bluetooth driver under a different name, such as BTC for
Bluetooth COM, but this nonstandard name wouldn’t be expected if it were to be passed on to
other applications.

The second problem is that although the virtual COM port method is used on a number of
platforms, the implementation on Windows CE is unique. At least with the Winsock method, an
application can be written to be fairly source code compatible with Windows XP. That isn’t the
case with the virtual COM port method.

Finally, creating COM ports using this method is accomplished using the RegisterDevice function.
Although perfectly functional, this function has been deprecated for quite a while under newer
versions of Windows CE. Drivers loaded with RegisterDevice aren’t listed in the active device list
maintained in the registry by the system. RegisterDevice requires that the application provide the
index value for the driver being loaded. Because there’s no simple method for determining which
instance values are in use, the application must try all 10 instance values until one doesn’t fail
because it’s used by another COM driver. Still, in some circumstances—when legacy support is
needed, for example—using a virtual COM port is necessary.

Creating a virtual COM port is accomplished with the function RegisterDevice, which is prototyped
as

HANDLE RegisterDevice (LPCWSTR lpszType, DWORD dwIndex, LPCWSTR lpszLib,


DWORD dwInfo);

The first parameter is a three-character name of the driver, such as COM or WAV. The second
parameter is the instance value from 1 through 9, or 0 for instance 10. This value can’t already
be in use by another driver of the same name. The third parameter is the name of the DLL that
implements the driver. The final parameter is a DWORD that’s passed to the Init entry point of
the driver.

When used to load a Bluetooth virtual COM port, RegisterDevice is used as follows:

hDev = RegisterDevice (TEXT("COM"), dwIndex, TEXT("btd.dll"), (DWORD) &pp);


where pp is the address of a PORTEMUPortParams structure defined as

typedef struct _portemu_port_params {


int channel;
int flocal;
BD_ADDR device;
int imtu;
int iminmtu;
int imaxmtu;
int isendquota;
int irecvquota;
GUID uuidService;
unsigned int uiportflags;
} PORTEMUPortParams;

The first field is the RFCOMM channel to be used for this port. If the channel is to be assigned
automatically, the field can be set to RFCOMM_CHANNEL_MULTIPLE. The fLocal field should be
set to TRUE for the server application and FALSE for the client application. The device field is
used by client applications to specify the Bluetooth address of the remote server. This field must
be 0 for server applications.

The next three parameters allow the application to specify the maximum transaction unit (MTU).
The first field in this series, imtu, is the suggested value, while iminmtu is the minimum
acceptable MTU and imaxmtu is the maximum acceptable MTU. If all three of these fields are 0,
the driver uses default values for the MTU. The isendquota and irecvquota fields set the buffer
sizes for send and receive operations. Setting these fields to 0 indicates that the driver should
use the default values.

The uuidService field is used by the client application to specify the service being connected to on
the server. If the channel field is 0, this field must be set. If the uuidService is nonzero, the
Bluetooth stack will perform an SDP search to determine the proper channel for the service. The
actual SDP search will take place when the COM port is opened, not when it’s loaded with
RegisterDevice.

The upportflags field can contain a combination of the following flags:

• RFCOMM_PORT_FLAGS_AUTHENTICATE

Perform authentication with the remote device when connecting.

• RFCOMM_PORT_FLAGS_ENCRYPT

Encrypt the stream.

• RFCOMM_PORT_FLAGS_REMOTE_DCB

When this flag is specified, changing the DCB settings of the port results in a negation
with the peer device DCB settings.

• RFCOMM_PORT_FLAGS_KEEP_DCD

If this flag is set, the emulated DCD line will always be set.
Server Side

As when using Winsock to talk to the Bluetooth stack, using virtual COM ports requires that one
device be the server and the other the client. The server’s responsibility includes loading the
driver, opening the driver, determining the RFCOMM channel assigned to the port, and
advertising the port using the SDP process discussed earlier.

The following code fragment demonstrates a server registering a virtual COM port driver. Notice
that the routine makes multiple attempts at registering the driver, starting with instance value 9
and going down. Since the upper instance values are typically less used, this results in a quicker
registration process. Notice that as soon as the registration loop completes, the code saves the
instance value because that value forms the name of the driver. The driver name is then used to
open the driver with CreateFile. Once the driver is opened, the server uses one of the two special
I/O Control (IOCTL) commands available on a virtual COM port to query the RFCOMM channel.
The server then calls its RegisterService routine to advertise the service through an SDP record.

//
// Server process for opening a virtual COM port
//
int i, rc;
PORTEMUPortParams pp;
TCHAR szDrvName[6];

memset (&pp, 0, sizeof (pp));


pp.channel = RFCOMM_CHANNEL_MULTIPLE;
pp.flocal = TRUE;
pp.uiportflags = 0;

// Find free instance number and load Bluetooth virt serial driver
for (i = 9; i >= 0; i--) {
hDev = RegisterDevice (L"COM", i, L"btd.dll", (DWORD)&pp);
if (hDev)
break;
}
// See if driver registered
if (hDev == 0) return -1;

// Form the driver name and save it.


wsprintf (szDrvName, TEXT("COM%d:"), i);

// Open the driver


hDevOpen = CreateFile (szDrvName, GENERIC_READ │ GENERIC_WRITE, 0,
NULL, OPEN_ALWAYS, 0, 0);
if (hDevOpen == INVALID_HANDLE_VALUE) {
DeregisterDevice (hDev);
return -2;
}
DWORD port = 0;
DWORD dwSizeOut;
rc = DeviceIoControl (hDevOpen, IOCTL_BLUETOOTH_GET_RFCOMM_CHANNEL,
NULL, 0, &port, sizeof(port), &dwSizeOut, NULL);

Add2List (hWnd, TEXT("rc = %d Port value is %d"), rc, port);

rc = RegisterService (hWnd, &guidbthello, (unsigned char) port, &hService);

The IOCTL command used in the preceding code, IOCTL_BLUETOOTH_ GET_RFCOMM_CHANNEL,


returns the RFCOMM channel of the COM port. For the call to DeviceIoControl, the output buffer
points to a DWORD value that will receive the port number. The output buffer size must be set to
the size of a DWORD. Once the port is determined, the routine simply calls the RegisterService
routine, shown earlier in this chapter.

Client Side

The client side of the process is similar to the server side, with the exception that the client
needs to know the Bluetooth address of the server and the GUID of the service on the server.
Both of these parameters are specified in the PORTEMUPortParams structure when the device is
registered. The following code shows the COM port initialization process from the client
perspective.

//
// Client side
//
int i, rc;
PORTEMUPortParams pp;
TCHAR szDrvName[6];

int nDevs2 = MAX_DEVICES;


MYBTDEVICE btd2[MAX_DEVICES];

// Find the server's Bluetooth address


rc = FindDevices (btaServ);
if (rc) return -1;

memset (&pp, 0, sizeof (pp));


pp.channel = 0;
pp.flocal = FALSE;
pp.device = btaServ;
pp.uuidService = guidbtService;
pp.uiportflags = 0;

// Find free instance number and load Bluetooth virt serial driver
for (i = 9; i >= 0; i--) {
hDev = RegisterDevice (L"COM", i, L"btd.dll", (DWORD)&pp);
if (hDev)
break;
}
// See if driver registered
if (hDev == 0) return -1;

// Form the driver name and save it.


wsprintf (szDrvName, TEXT("COM%d:"), i);

// Open the driver


hDevOpen = CreateFile (szDrvName, GENERIC_READ │ GENERIC_WRITE, 0,
NULL, OPEN_ALWAYS, 0, 0);
if (hDevOpen == INVALID_HANDLE_VALUE) {
DeregisterDevice (hDev);
return -2;
}
BT_ADDR bt;
DWORD dwSizeOut;
rc = DeviceIoControl (hDevOpen, IOCTL_BLUETOOTH_GET_PEER_DEVICE,
NULL, 0, &bt, sizeof(bt), &dwSizeOut, NULL);
printf ("Connection detected with %04x%08x\r\n", GET_NAP(bt), GET_SAP(bt));
Notice the use of the second IOCTL command provided for Bluetooth support,
IOCTL_BLUETOOTH_GET_PEER_DEVICE. This command returns the Bluetooth address of the
device on the other end of the connected virtual serial port.

Communication between the client and the server is accomplished through the standard Win32
file functions ReadFile and WriteFile. When the conversation has been concluded, the driver
should be closed with a call to CloseHandle and the driver unloaded with a call to
DeregisterDevice, prototyped here:

BOOL DeregisterDevice (HANDLE hDevice);

The only parameter is the handle returned by RegisterDevice.

The BtHello Example Program

The BtHello example demonstrates a fairly complete Bluetooth application that can act as both a
client and a server. BtHello must be running on two Windows CE devices that use the Windows
CE Bluetooth stack for it to work. When started, BtHello searches for other Bluetooth devices in
the area and lists them in the output window. When the user taps the “Say Hello” button, BtHello
connects to the bthello service on the other device. Once connected, the client sends the server a
short string and then closes the connection. The server reads the text and displays it in its
window. Figure 14-4 shows the BtHello example after it has received the message from the other
device.

Figure 14-4. The BtHello example after it has received a message from another device
The source code for BtHello is shown in Listing 14-2. The application is a simple dialog-based
application. The source code is divided into two .cpp files and their associated include files:
BtHello.cpp, which contains the majority of the source code; and MyBtUtil.cpp, which contains
handy Bluetooth routines for finding devices and for registering service GUIDs with the SDP
service.

Listing 14-2. The BtHello source code

MyBtUtil.h
//======================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================

#ifndef _MYBTUTIL_H_
#define _MYBTUTIL_H_

#if defined (__cplusplus)


extern "C" {
#endif
typedef struct {
TCHAR szName[256];
BT_ADDR btaddr;
} MYBTDEVICE, *PMYBTDEVICE;

// Finds Bluetooth devices


int FindDevices (PMYBTDEVICE pbtDev, int *pnDevs);

// Registers a BT service
int RegisterBtService (GUID *pguid, byte bChannel,
ULONG *pRecord);

// Clears a BT service from the SDP database


int UnregisterBtService (HWND hWnd, ULONG hRecord);

#if defined (__cplusplus)


}
#endif
#endif // _MYBTUTIL_H_
MyBtUtil.cpp
//======================================================================
// MyBtUtil - Handy Bluetooth routines
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================

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

#include "MyBtUtil.h"

#define MYBUFFSIZE 16384


//----------------------------------------------------------------------
// FindDevices - Find devices in range.
//
int FindDevices (PMYBTDEVICE pbtDev, int *pnDevs) {
DWORD dwFlags, dwLen;
HANDLE hLookup;
int i, rc, nMax = *pnDevs;
*pnDevs = 0;

// Create inquiry blob to limit time of search


BTHNS_INQUIRYBLOB inqblob;
memset (&inqblob, 0, sizeof (inqblob));
inqblob.LAP = BT_ADDR_GIAC; // Default GIAC
inqblob.length = 4; // 4 * 1.28 = 5 seconds
inqblob.num_responses = nMax;

// Create blob to point to inquiry blob


BLOB blob;
blob.cbSize = sizeof (BTHNS_INQUIRYBLOB);
blob.pBlobData = (PBYTE)&inqblob;

// Init query
WSAQUERYSET QuerySet;
memset(&QuerySet,0,sizeof(WSAQUERYSET));
QuerySet.dwSize = sizeof(WSAQUERYSET);
QuerySet.dwNameSpace = NS_BTH;
QuerySet.lpBlob = &blob;

// Start query for devices


rc = WSALookupServiceBegin (&QuerySet, LUP_CONTAINERS, &hLookup);
if (rc) return rc;

PBYTE pOut = (PBYTE)LocalAlloc (LPTR, MYBUFFSIZE);


if (!pOut) return -1;
WSAQUERYSET *pQueryResult = (WSAQUERYSET *)pOut;

for (i = 0; i < nMax; i++) {


dwLen = MYBUFFSIZE;
dwFlags = LUP_RETURN_NAME │ LUP_RETURN_ADDR;
rc = WSALookupServiceNext (hLookup, dwFlags, &dwLen, pQueryResult);
if (rc == SOCKET_ERROR) {
rc = GetLastError();
break;
}
// Copy device name
lstrcpy (pbtDev[i].szName, pQueryResult->lpszServiceInstanceName);
// Copy bluetooth device address
SOCKADDR_BTH *pbta;
pbta = (SOCKADDR_BTH *)pQueryResult->lpcsaBuffer-
>RemoteAddr.lpSockaddr;
pbtDev[i].btaddr = pbta->btAddr;
}
if (rc == WSA_E_NO_MORE) rc = 0;
*pnDevs = i;
WSALookupServiceEnd (hLookup);
LocalFree (pOut);
return rc;
}
//----------------------------------------------------------------------
// PublishRecord - Helper routine that actually does the registering
// of the SDP record.
//
int PublishRecord (PBYTE pSDPRec, int nRecSize, ULONG *pRecord) {
BTHNS_SETBLOB *pSetBlob;
ULONG ulSdpVersion = BTH_SDP_VERSION;
int rc;

// Zero out the record handle that will be returned by the call
*pRecord = 0;

// Allocate and init the SetBlob


pSetBlob = (BTHNS_SETBLOB *)LocalAlloc (LPTR,
sizeof (BTHNS_SETBLOB) + nRecSize-1);
if (!pSetBlob) return -1;

pSetBlob->pRecordHandle = pRecord;
pSetBlob->pSdpVersion = &ulSdpVersion;
pSetBlob->fSecurity = 0;
pSetBlob->fOptions = 0;
pSetBlob->ulRecordLength = nRecSize;
memcpy (pSetBlob->pRecord, pSDPRec, nRecSize);

// Init the container blob


BLOB blob;
blob.cbSize = sizeof(BTHNS_SETBLOB) + nRecSize - 1;
blob.pBlobData = (PBYTE) pSetBlob;

// Init the WSAQuerySet struct


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

// Publish the service


rc = WSASetService(&Service, RNRSERVICE_REGISTER, 0);
if (rc == SOCKET_ERROR)
rc = GetLastError();

// Clean up
LocalFree ((PBYTE)pSetBlob);
return rc;
}
//----------------------------------------------------------------------
// UnregisterBtService - Remove service from SDP database
//
int UnregisterBtService (HWND hWnd, ULONG hRecord) {
ULONG ulSdpVersion = BTH_SDP_VERSION;
int rc;

BTHNS_SETBLOB SetBlob;
memset (&SetBlob, 0, sizeof (SetBlob));
SetBlob.pRecordHandle = &hRecord;
SetBlob.pSdpVersion = &ulSdpVersion;

// Init the container blob


BLOB blob;
blob.cbSize = sizeof(BTHNS_SETBLOB);
blob.pBlobData = (PBYTE) &SetBlob;

// Init the WSAQuerySet struct


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

// Unpublish the service


rc = WSASetService(&Service, RNRSERVICE_DELETE, 0);
if (rc == SOCKET_ERROR)
rc = GetLastError();
return rc;
}
//----------------------------------------------------------------------
// RegisterBtService - Registers a service with a guid and RFChannel
//
int RegisterBtService (GUID *pguid, byte bChannel, ULONG *pRecord) {

// SDP dummy record


// GUID goes at offset 8
// Channel goes in last byte of record.
static BYTE bSDPRecord[] = {
0x35, 0x27, 0x09, 0x00, 0x01, 0x35, 0x11, 0x1C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x09, 0x00, 0x04, 0x35, 0x0C, 0x35, 0x03, 0x19, 0x01,
0x00, 0x35, 0x05, 0x19, 0x00, 0x03, 0x08, 0x00};

// Update the SDP record


// Translate guid into net byte order for SDP record
GUID *p = (GUID *)&bSDPRecord[8];
p->Data1 = htonl (pguid->Data1);
p->Data2 = htons (pguid->Data2);
p->Data3 = htons (pguid->Data3);
memcpy (p->Data4, pguid->Data4, sizeof (pguid->Data4));

// Copy channel value into record


bSDPRecord[sizeof (bSDPRecord)-1] = bChannel;
return PublishRecord (bSDPRecord, sizeof (bSDPRecord), pRecord);
}
BtHello.h//====================================================================
==
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0]))

// Windows CE Specific defines


#define LPCMDLINE LPWSTR

//----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT { // Structure associates
UINT Code; // messages
// with a function.
LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};
struct decodeCMD { // Structure associates
UINT Code; // menu IDs with a
LRESULT (*Fxn)(HWND, WORD, HWND, WORD); // function.
};

//----------------------------------------------------------------------
// Defines used by application
#define ID_ICON 1

#define IDD_INTEXT 10 // Control IDs


#define IDD_SAYHELLO 11
#define IDD_OUTTEXT 12
#define IDD_SCAN 13

// Error codes used by transfer protocol


#define BAD_TEXTLEN -1
#define BAD_SOCKET -2

#define MYMSG_ENABLESEND (WM_USER+1000)


#define MYMSG_PRINTF (WM_USER+1001)
//----------------------------------------------------------------------
// Function prototypes
//
HWND InitInstance (HINSTANCE, LPCMDLINE, int);
int TermInstance (HINSTANCE, int);
void Add2List (HWND hWnd, LPTSTR lpszFormat, ...);

// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);

// Message handlers
LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoSizeMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoCommandMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoPocketPCShell (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoEnableSendMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoPrintfNotifyMain (HWND, UINT, WPARAM, LPARAM);

// Command functions
LPARAM DoMainCommandSend (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandExit (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandScan (HWND, WORD, HWND, WORD);

// Thread functions
DWORD WINAPI SearchThread (PVOID pArg);
DWORD WINAPI ServerThread (PVOID pArg);
DWORD WINAPI ReceiveThread (PVOID pArg);
DWORD WINAPI SayHelloThread (PVOID pArg);
BtHello.cpp
//======================================================================
// BtHello - A demonstration of a Bluetooth application
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h> // For all that Windows stuff
#include <winsock2.h>
#include <ws2bth.h>
#include <Msgqueue.h>

#if defined(WIN32_PLATFORM_PSPC)
#include <aygshell.h> // Add Pocket PC includes
#pragma comment( lib, "aygshell" ) // Link Pocket PC lib for menubar
#endif

#include "btHello.h" // Program-specific stuff


#include "MyBTUtil.h" // My Bluetooth routines

//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("bthello");
// {26CECFEC-D255-4a5d-AF7C-9CCF840E7A42}
GUID guidbthello =
{ 0x26cecfec, 0xd255, 0x4a5d, { 0xaf, 0x7c, 0x9c, 0xcf,
0x84, 0xe, 0x7a, 0x42} };

HINSTANCE hInst; // Program instance handle


HWND hMain; // Main window handle
BOOL fContinue = TRUE; // Server thread cont. flag
BOOL fFirstSize = TRUE; // First WM_SIZE flag

#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)


SHACTIVATEINFO sai; // Needed for PPC helper funcs
#endif

HANDLE hQRead = 0; // Used for thread safe print


HANDLE hQWrite = 0;
CRITICAL_SECTION csPrintf;

#define MAX_DEVICES 16
MYBTDEVICE btd[MAX_DEVICES]; // List of BT devices
int nDevs = 0; // Count of BT devices

// Message dispatch table for MainWindowProc


const struct decodeUINT MainMessages[] = {
WM_CREATE, DoCreateMain,
WM_SIZE, DoSizeMain,
WM_COMMAND, DoCommandMain,
MYMSG_ENABLESEND, DoEnableSendMain,
MYMSG_PRINTF, DoPrintfNotifyMain,
WM_SETTINGCHANGE, DoPocketPCShell,
WM_ACTIVATE, DoPocketPCShell,
WM_DESTROY, DoDestroyMain,
};
// Command Message dispatch for MainWindowProc
const struct decodeCMD MainCommandItems[] = {
#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)
IDOK, DoMainCommandExit,
#else
IDOK, DoMainCommandSend,
#endif
IDCANCEL, DoMainCommandExit,
IDD_SAYHELLO, DoMainCommandSend,
IDD_SCAN, DoMainCommandScan,
};
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPCMDLINE lpCmdLine, int nCmdShow) {
MSG msg;
int rc = 0;

// Initialize this instance.


hMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
if (hMain == 0)
return TermInstance (hInstance, 0x10);

// Application message loop


while (GetMessage (&msg, NULL, 0, 0)) {
if ((hMain == 0) ││ !IsDialogMessage (hMain, &msg)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
// Instance cleanup
return TermInstance (hInstance, msg.wParam);
}
//----------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPCMDLINE lpCmdLine,
int nCmdShow){
WNDCLASS wc;
HWND hWnd;
HANDLE hThread;
int rc;

hInst = hInstance; // Save program instance handle.

// For all systems, if previous instance, activate it instead of us.


hWnd = FindWindow (szAppName, NULL);
if (hWnd) {
SetForegroundWindow ((HWND)((DWORD)hWnd │ 0x01));
return 0;
}
// Init Winsock
WSADATA wsaData;
rc = WSAStartup (0x0202, &wsaData);
if (rc) {
MessageBox (NULL,TEXT("Error in WSAStartup"), szAppName, MB_OK);
return 0;
}

// Create read and write message queues


MSGQUEUEOPTIONS mqo;
mqo.dwSize = sizeof (mqo);
mqo.dwFlags = MSGQUEUE_ALLOW_BROKEN;
mqo.dwMaxMessages = 16;
mqo.cbMaxMessage = 512;
mqo.bReadAccess = TRUE;
hQRead = CreateMsgQueue (TEXT ("MSGQUEUE\\ThTead"), &mqo);

mqo.bReadAccess = FALSE;
hQWrite = CreateMsgQueue (TEXT ("MSGQUEUE\\ThTead"), &mqo);

// Register application main window class.


wc.style = 0; // Window style
wc.lpfnWndProc = MainWndProc; // Callback function
wc.cbClsExtra = 0; // Extra class data
wc.cbWndExtra = DLGWINDOWEXTRA; // Extra window data
wc.hInstance = hInstance; // Owner handle
wc.hIcon = NULL; // Application icon
wc.hCursor = LoadCursor (NULL, IDC_ARROW);// Default cursor
wc.hbrBackground = (HBRUSH) GetStockObject (LTGRAY_BRUSH);
wc.lpszMenuName = NULL; // Menu name
wc.lpszClassName = szAppName; // Window class name

if (RegisterClass (&wc) == 0) return 0;

// Create main window.


hWnd = CreateDialog (hInst, szAppName, NULL, NULL);

// Return fail code if window not created.


if (!IsWindow (hWnd)) return 0;

// Create secondary thread for server function.


hThread = CreateThread (NULL, 0, ServerThread, hWnd, 0, 0);
if (hThread == 0) {
DestroyWindow (hWnd);
return 0;
}
CloseHandle (hThread);

// Post a message to have device discovery start


PostMessage (hWnd, WM_COMMAND, MAKEWPARAM (IDD_SCAN, BN_CLICKED),0);

ShowWindow (hWnd, nCmdShow); // Standard show and update calls


UpdateWindow (hWnd);
SetFocus (GetDlgItem (hWnd, IDD_OUTTEXT));
return hWnd;
}
//----------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {
WSACleanup ();
return nDefRC;
}
//======================================================================
// Message handling procedures for main window
//----------------------------------------------------------------------
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
INT i;
//
// Search message list to see if we need to handle this
// message. If in list, call procedure.
//
for (i = 0; i < dim(MainMessages); i++) {
if (wMsg == MainMessages[i].Code)
return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
}
return DefWindowProc (hWnd, wMsg, wParam, lParam);
}
//----------------------------------------------------------------------
// DoCreateMain - Process WM_CREATE message for window.
//
LRESULT DoCreateMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {

#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)


SHINITDLGINFO shidi;
SHMENUBARINFO mbi; // For Pocket PC, create
memset(&mbi, 0, sizeof(SHMENUBARINFO)); // menu bar so that we
mbi.cbSize = sizeof(SHMENUBARINFO); // have a sip button
mbi.dwFlags = SHCMBF_EMPTYBAR;
mbi.hwndParent = hWnd;
SHCreateMenuBar(&mbi);
SendMessage(mbi.hwndMB, SHCMBM_GETSUBMENU, 0, 100);

// For Pocket PC, make dialog box full screen with PPC
// specific call.
shidi.dwMask = SHIDIM_FLAGS;
shidi.dwFlags = SHIDIF_DONEBUTTON │ SHIDIF_SIZEDLG │ SHIDIF_SIPDOWN;
shidi.hDlg = hWnd;
SHInitDialog(&shidi);
sai.cbSize = sizeof (sai);
SHHandleWMSettingChange(hWnd, wParam, lParam, &sai);
#endif
return 0;
}
//----------------------------------------------------------------------
// DoSizeMain - Process WM_SIZE message for window.
//
LRESULT DoSizeMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {

#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)


static RECT rectListbox;
RECT rect;

GetClientRect (hWnd, &rect);


if (fFirstSize) {
// First time through, get the position of the listbox for
// resizing later. Store the distance from the sides of
// the listbox control to the side of the parent window
if (IsWindow (GetDlgItem (hWnd, IDD_INTEXT))) {
GetWindowRect (GetDlgItem (hWnd, IDD_INTEXT), &rectListbox);
MapWindowPoints (HWND_DESKTOP, hWnd, (LPPOINT)&rectListbox,2);
rectListbox.right = rect.right - rectListbox.right;
rectListbox.bottom = rect.bottom - rectListbox.bottom;
}
}
SetWindowPos (GetDlgItem (hWnd, IDD_INTEXT), 0, rect.left+5,
rectListbox.top, rect.right-10,
rect.bottom - rectListbox.top - 5,
SWP_NOZORDER);
#endif
if (fFirstSize) {
EnableWindow (GetDlgItem (hWnd, IDD_SAYHELLO), FALSE);
EnableWindow (GetDlgItem (hWnd, IDD_SCAN), FALSE);
fFirstSize = FALSE;
}
return 0;
}
//----------------------------------------------------------------------
// DoCommandMain - Process WM_COMMAND message for window.
//
LRESULT DoCommandMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
WORD idItem, wNotifyCode;
HWND hwndCtl;
INT i;

// Parse the parameters.


idItem = (WORD) LOWORD (wParam);
wNotifyCode = (WORD) HIWORD (wParam);
hwndCtl = (HWND) lParam;

// Call routine to handle control message.


for (i = 0; i < dim(MainCommandItems); i++) {
if (idItem == MainCommandItems[i].Code)
return (*MainCommandItems[i].Fxn)(hWnd, idItem, hwndCtl,
wNotifyCode);
}
return 0;
}
//----------------------------------------------------------------------
// DoEnableSendMain - Process user message to enable send button
//
LRESULT DoEnableSendMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
EnableWindow (GetDlgItem (hWnd, IDD_SAYHELLO), lParam);
EnableWindow (GetDlgItem (hWnd, IDD_SCAN), TRUE);
SetWindowText (hWnd, szAppName);
return 0;
}
//----------------------------------------------------------------------
// DoPrintfNotifyMain - Process printf notify message
//
LRESULT DoPrintfNotifyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
TCHAR szBuffer[512];
int rc;
DWORD dwLen = 0;
DWORD dwFlags = 0;

memset (szBuffer, 0, sizeof (szBuffer));


rc = ReadMsgQueue (hQRead, (LPBYTE)szBuffer, sizeof (szBuffer),
&dwLen, 0, &dwFlags);
if (rc) {
if (dwFlags & MSGQUEUE_MSGALERT)
SetWindowText (hWnd, szBuffer);
else {
rc = SendDlgItemMessage (hWnd, IDD_INTEXT, LB_ADDSTRING, 0,
(LPARAM)(LPCTSTR)szBuffer);
if (rc != LB_ERR)
SendDlgItemMessage (hWnd, IDD_INTEXT, LB_SETTOPINDEX,rc,
(LPARAM)(LPCTSTR)szBuffer);
}
}
return 0;
}
//----------------------------------------------------------------------
// DoPocketPCShell - Process Pocket PC required messages
//
LRESULT DoPocketPCShell (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)
if (wMsg == WM_SETTINGCHANGE)
return SHHandleWMSettingChange(hWnd, wParam, lParam, &sai);
if (wMsg == WM_ACTIVATE)
return SHHandleWMActivate(hWnd, wParam, lParam, &sai, 0);
#endif
return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
fContinue = FALSE; // Shut down server thread.
Sleep (0); // Pass on timeslice.
PostQuitMessage (0);
return 0;
}
//======================================================================
// Command handler routines
//----------------------------------------------------------------------
// DoMainCommandExit - Process Program Exit command.
//
LPARAM DoMainCommandExit (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {

SendMessage (hWnd, WM_CLOSE, 0, 0);


return 0;
}
//----------------------------------------------------------------------
// DoMainCommandSend - Process Program Send File command.
//
LPARAM DoMainCommandSend (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
static TCHAR szName[MAX_PATH];

GetDlgItemText (hWnd, IDD_OUTTEXT, szName, dim(szName));


CreateThread (NULL, 0, SayHelloThread, (PVOID)szName, 0, NULL);
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandScan - Process Device Scan command.
//
LPARAM DoMainCommandScan (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
SetWindowText (hWnd, TEXT("Scanning..."));
EnableWindow (GetDlgItem (hWnd, IDD_SAYHELLO), FALSE);
EnableWindow (GetDlgItem (hWnd, IDD_SCAN), FALSE);
CreateThread (NULL, 0, SearchThread, (PVOID)hWnd, 0, NULL);
return 0;
}
//----------------------------------------------------------------------
// Add2List - Add string to the report list box.
//
void Add2List (HWND hWnd, LPTSTR lpszFormat, ...) {
int nBuf, nLen;
TCHAR szBuffer[512];
va_list args;
if (hWnd == 0)
hWnd = hMain;

EnterCriticalSection (&csPrintf);
va_start(args, lpszFormat);
nBuf = _vstprintf(szBuffer, lpszFormat, args);
va_end(args);

nLen = (lstrlen (szBuffer)+1) * sizeof (TCHAR);


WriteMsgQueue (hQWrite, (LPBYTE)szBuffer, nLen, 0, 0);
PostMessage (hWnd, MYMSG_PRINTF, 0, 0);
LeaveCriticalSection (&csPrintf);
}
//----------------------------------------------------------------------
// MySetWindowText - Set Window title to passed printf style string.
//
void MySetWindowText (HWND hWnd, LPTSTR lpszFormat, ...) {
int nBuf, nLen;
TCHAR szBuffer[512];
va_list args;

EnterCriticalSection (&csPrintf);
va_start(args, lpszFormat);
nBuf = _vstprintf(szBuffer, lpszFormat, args);
va_end(args);

nLen = (lstrlen (szBuffer)+1) * sizeof (TCHAR);


WriteMsgQueue (hQWrite, (LPBYTE)szBuffer, nLen, 0,MSGQUEUE_MSGALERT);
PostMessage (hWnd, MYMSG_PRINTF, 0, 0);
LeaveCriticalSection (&csPrintf);
}
//======================================================================
// SearchThread - Monitors for other devices.
//
DWORD WINAPI SearchThread (PVOID pArg) {
HWND hWnd = (HWND)pArg;
int i, rc, Channel = 0;

Add2List (hWnd, TEXT("Search thread entered"));

// Init COM for the thread.


CoInitializeEx(NULL,COINIT_MULTITHREADED);

// Find the Bluetooth devices


nDevs = MAX_DEVICES;
rc = FindDevices (btd, &nDevs);

// List them.
for (i = 0; i < nDevs; i++)
Add2List (hWnd, TEXT("%d. dev:>%s< "), i, btd[i].szName);

PostMessage (hWnd, MYMSG_ENABLESEND, 0, 1);


CoUninitialize();
Add2List (hWnd, TEXT("Search thread exit"));
return 0;
}
//======================================================================
// ServerThread - Monitors for connections, connects and notifies
// user when a connection occurs.
//
DWORD WINAPI ServerThread (PVOID pArg) {
HWND hWnd = (HWND)pArg;
INT rc, len, nSize;
SOCKADDR_BTH btaddr, t_btaddr;
SOCKET r_sock, s_sock;
ULONG RecordHandle;
HRESULT hr;

Add2List (hWnd, TEXT("Server thread entered"));


CoInitializeEx(NULL,COINIT_MULTITHREADED);

// Open a bluetooth socket


s_sock = socket (AF_BT, SOCK_STREAM, BTHPROTO_RFCOMM);
if (s_sock == INVALID_SOCKET) {
Add2List (hWnd, TEXT("socket failed. rc %d"), WSAGetLastError());
return 0;
}
// Fill in address stuff
memset (&btaddr, 0, sizeof (btaddr));
btaddr.addressFamily = AF_BT;
btaddr.port = 0; // Let driver assign a channel

// Bind to socket
rc = bind (s_sock, (struct sockaddr *)&btaddr, sizeof (btaddr));
if (rc) {
Add2List (hWnd, TEXT("bind failed"));
closesocket (s_sock);
return 0;
}
// Get information on the port assigned
len = sizeof (btaddr);
rc = getsockname (s_sock, (SOCKADDR *)&btaddr, &len);
if (rc) {
Add2List (hWnd, TEXT("getsockname failed"));
closesocket (s_sock);
return 0;
}
Add2List (hWnd, TEXT("Addr %04x.%08x, port %d"),
GET_NAP(btaddr.btAddr), GET_SAP(btaddr.btAddr), btaddr.port);

// Register our service


rc = RegisterBtService (&guidbthello, (unsigned char) btaddr.port,
&RecordHandle);
if (rc) {
Add2List (hWnd, TEXT("RegisterService fail %d %d"), rc,
GetLastError());
closesocket (s_sock);
return 0;
}

// Set socket into listen mode


rc = listen (s_sock, SOMAXCONN);
if (rc == SOCKET_ERROR) {
Add2List (hWnd, TEXT(" listen failed %d"), GetLastError());
closesocket (s_sock);
return 0;
}
// Wait for remote requests
while (fContinue) {
Add2List (hWnd, TEXT("waiting..."));
nSize = sizeof (t_btaddr);
// Block on accept
r_sock = accept (s_sock, (struct sockaddr *)&t_btaddr, &nSize);
if (r_sock == INVALID_SOCKET) {
Add2List (hWnd, TEXT(" accept failed %d"), GetLastError());
}
Add2List (hWnd, TEXT("sock accept..."));
CreateThread (NULL, 0, ReceiveThread, (PVOID)r_sock, 0, NULL);
}
closesocket (s_sock);

// Deregister the service


hr = UnregisterBtService (hWnd, RecordHandle);
CoUninitialize();
Add2List (hWnd, TEXT("Server thread exit"));
return 0;
}
//======================================================================
// ReceiveThread - Sends the file requested by the remote device
//
DWORD WINAPI ReceiveThread (PVOID pArg) {
SOCKET r_sock = (SOCKET)pArg;
HWND hWnd = hMain; // I'm cheating here.
int nCnt, rc = 0;
PBYTE pBuff = 0;
int nBytes;
TCHAR szRcvBuff[256];

Add2List (hWnd, TEXT("receive thread entered"));


SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_ABOVE_NORMAL);

// Read the number of bytes in the text string


nBytes = recv (r_sock, (LPSTR)&nCnt, sizeof (nCnt), 0);
if (nBytes == SOCKET_ERROR) {
Add2List (hWnd, TEXT("failed receiving text length"));
closesocket (r_sock);
return 0;
}
if (sizeof (szRcvBuff) < nCnt)
rc = BAD_TEXTLEN;

// Send ack
nBytes = send (r_sock, (char *)&rc, sizeof (rc), 0);
if (nBytes == SOCKET_ERROR) {
Add2List (hWnd, TEXT("Error %d receiving text length"), GetLastError())
;
closesocket (r_sock);
return 0;
}

// Read the text


nBytes = recv (r_sock, (LPSTR)szRcvBuff, nCnt, 0);
if (nBytes == SOCKET_ERROR) {
Add2List (hWnd, TEXT("failed receiving text"));
closesocket (r_sock);
return 0;
}
Add2List (hWnd, TEXT("Other device says: %s"), szRcvBuff);

// send ack of text


rc = 0;
nBytes = send (r_sock, (char *)&rc, sizeof (rc), 0);
if (nBytes == SOCKET_ERROR) {
Add2List (hWnd, TEXT("Error %d sending ack"), GetLastError());
rc = SOCKET_ERROR;
}
Add2List (hWnd, TEXT("receive thread exit"));
return 0;
}
//----------------------------------------------------------------------
// SayHello - Sends text to the remote device
//
DWORD WINAPI SayHelloThread (PVOID pArg) {
TCHAR szText[] = TEXT("Hello Device");
HWND hWnd = hMain;
SOCKET t_sock;
INT j, rc, nCnt, nBytes;
SOCKADDR_BTH btaddr;
BOOL fSuccess = FALSE;

// Open a bluetooth socket


t_sock = socket (AF_BT, SOCK_STREAM, BTHPROTO_RFCOMM);
if (t_sock == INVALID_SOCKET) {
Add2List (hWnd, TEXT("socket failed. rc %d"), WSAGetLastError());
return 0;
}

// Loop through each device trying to say hello


for (j = 0; j < nDevs; j++) {

Add2List (hWnd, TEXT("Trying device %s"), btd[j].szName);


// Fill in address stuff
memset (&btaddr, 0, sizeof (btaddr));
btaddr.btAddr = btd[j].btaddr;
btaddr.addressFamily = AF_BT;
btaddr.port = 0; // Let driver find the channel
memcpy (&btaddr.serviceClassId, &guidbthello, sizeof (GUID));
//
// Connect to remote socket
//
rc = connect (t_sock, (struct sockaddr *)&btaddr, sizeof (btaddr));
if (rc == 0) {
fSuccess = TRUE;
break;
}
Add2List (hWnd, TEXT("connect failed. rc %d"), WSAGetLastError());
}
if (!fSuccess) {
closesocket (t_sock);
return 0;
}
Add2List (hWnd, TEXT("connected..."));

// send name size


nCnt = (lstrlen (szText) + 1) * sizeof (TCHAR);
nBytes = send (t_sock, (LPSTR)&nCnt, sizeof (nCnt), 0);

// Recv ack of text size


if (recv (t_sock, (char *)&rc, sizeof (rc), 0) == SOCKET_ERROR)
rc = SOCKET_ERROR;

if (rc == 0) {
// Send text name
if (nBytes != SOCKET_ERROR) {
nBytes = send (t_sock, (LPSTR)szText, nCnt, 0);
}
// Recv ack of text send.
if (recv (t_sock, (char *)&rc, sizeof (rc), 0) == SOCKET_ERROR)
rc = SOCKET_ERROR;
}
// Send close code.
if (rc != BAD_SOCKET)
send (t_sock, (LPSTR)&rc, sizeof (rc), 0);

closesocket (t_sock);
if (rc)
Add2List (hWnd, TEXT("SayHello Exit rc = %d"), rc);
else
Add2List (hWnd, TEXT("Text sent successfully"));
return 0;
}

The interesting routines are the search thread routine SearchThread and the server thread
routine ServerThread. The SearchThread calls the FindDevice routine to enumerate the Bluetooth
devices in the immediate area. The search is set to take approximately 5 seconds. Once found,
the device names and addresses are listed in the output window. The names and the addresses
of all the devices are saved in an array. The search can be restarted by tapping on the Scan
button.

The server routine, ServerThread, creates a socket and binds it to an address. The routine then
queries Winsock for the RFCOMM channel assigned to the socket. The RegisterBtService routine
is then called to advertise the bthello service. The RegisterBtService routine uses a prebuilt SDP
record and inserts the GUID for the service and the RFCOMM channel in the appropriate parts of
the record. Once constructed, the SDP packet is registered in the PublishRecord routine.
When the user taps the “Say Hello” button, an attempt is made to connect to the bthello server
on each of the devices found. If one of the connections is successful, the text is sent to the other
device.

Accessing Bluetooth through either Winsock or virtual COM ports provides the most flexible way
to wirelessly communicate with another device. The problem is that with either of these methods
the custom application, such as BtHello, has to be on both machines unless the application
communicates through one of the public services.

If you use one of the public services, the application must implement the proper protocol.
Although directly talking to Bluetooth is the most flexible path, it’s also the most complex. How
about a higher-level standard that will inform the application when devices come in range, that
will work over Bluetooth and IrDA, and that will provide a simple method for transferring files?
There is such a standard. It’s called the Object Exchange (OBEX) standard, and it too is
supported by the Pocket PC and other Windows CE devices.

You might also like