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 Bltand (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[2]. 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 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.The WSAQUERYSET structure is defined as
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 WSAQUERYSET structure's lpBlob field pointing to the BLOB structure. The BLOB structure should 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 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 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 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
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, SDP_SERVICE_ATTRIBUTE_REQUEST, and SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST, 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 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_DCDIf 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.[2] For those wondering, the tide came in anyway.