WM蓝牙开发指南 socket版

转自:http://blog.csdn.net/mikenoodle/archive/2009/09/13/4549744.aspx

 

 

一. 开发环境
硬件: HTC3238+WM6.1

SDK: STANDARDSDK_500

二. 目标
学习winsock方式的蓝牙应用开发

三. 背景介绍
In Microsoft® Windows® CE, the primary way an application can use Bluetooth is through the Winsock interface, which exposes RFCOMM protocol. Virtual COM ports are also exposed, but this interface is designed to be enabled under existing OS services such as UNIMODEM, GPS, or terminal emulators. By using various protocols and profiles, Bluetooth can be implemented to perform the following tasks:

Connect to a modem through a cellular phone.
Connect to a local area network (LAN) access point.
Enable object exchange and synchronization using the Object Exchange Protocol (OBEX).
Use wireless headset and hands-free devices to handle calls on a cellular phone.
Transfer data from a desktop computer to a mobile device over a wireless connection.
For more information about Bluetooth technology, see this Official Bluetooth Wireless Info Web site.

摘自:http://msdn.microsoft.com/en-us/library/ms847082.aspx

从上面的文字中可以看出在WM平台上,有两种方式可以操作蓝牙:Winsock和Virtual COM ports.这里我采用了Winsock方式.

四. 基本操作流程
1. 服务端

按MSDN上的说法,蓝牙的WINSOCK操作方式和基本的socket-based差不多.

SOCKET s = socket(...);
bind(s, ...);//绑定到一个RFCOMM通道上,exports the SDP record advertising this channel

listen(s, ...);
for(; ;)
{
   SOCKET s2= accept(s, ...);
   SpinThreadsForConnection(s2);
}

示例代码:

//Function: OpenServerConnection
//Purpose:    Opens a server socket for listening. Registers the service. Creates a thread, ReadThread for reading incoming messages.
//Input:    The SDP record of the service to register, size of the SDP record, channel offset in the record, pointer to the UI function that displays the messages in the UI
//Return: If error occurs, returns the appropriate WSAGetLastError, otherwise returns zero.
//注:调用语句是iRetVal=objBthUtils.OpenServerConnection(rgbSdpRecord, SDP_RECORD_SIZE, SDP_CHANNEL_OFFSET, DisplayMessage);
//rgbSdpRecord,SDP_RECORD_SIZE, SDP_CHANNEL_OFFSET是事先由bthnscreate.exe生成的,bthnscreate.exe的源码在D:/WINCE500/PUBLIC/COMMON/SDK/SAMPLES/BLUETOOTH/BTHNSCREATE,这是PB5的安装目录

int BthUtils::OpenServerConnection(BYTE *rgbSdpRecord, int cSdpRecord, int iChannelOffset, void (*funcPtr)(WCHAR *))
{
    int iNameLen=0;
    if(m_socketServer==INVALID_SOCKET)
    {
        m_socketServer = socket (AF_BT, SOCK_STREAM, BTHPROTO_RFCOMM);
        if (m_socketServer  == INVALID_SOCKET)
        {
            return WSAGetLastError ();
        }

        SOCKADDR_BTH sa;
        memset (&sa, 0, sizeof(sa));
        sa.addressFamily = AF_BT;
        sa.port = 0;
        if (bind (m_socketServer, (SOCKADDR *)&sa, sizeof(sa)))
        {
            return WSAGetLastError ();
        }
        iNameLen = sizeof(sa);
        if (getsockname(m_socketServer, (SOCKADDR *)&sa, &iNameLen))   
        {
            return WSAGetLastError ();
        }

        if(RegisterService(rgbSdpRecord, cSdpRecord, iChannelOffset, (UCHAR)sa.port)!=0)
            return WSAGetLastError();

        if (listen (m_socketServer, SOMAXCONN))
        {
            return WSAGetLastError ();
        }
    }
    pCallBackFunction=funcPtr;
    m_hReadThread= CreateThread(NULL, 0, ReadData, (LPVOID)this, 0, NULL);
    return 0;
}

//Function: RegisterService
//Purpose:    Publishes the SDP record.
//Input:    The SDP record of the service to register, size of the SDP record, channel offset in the record, channel number assigned automatically by OpenServerConnection
//Return: If error occurs, returns the appropriate WSAGetLastError, otherwise returns zero.

//更详细的信息在:http://msdn.microsoft.com/en-us/library/aa450140.aspx
//还有<>http://msdn.microsoft.com/en-us/library/aa450195.aspx

int BthUtils::RegisterService(BYTE *rgbSdpRecord, int cSdpRecord, int iChannelOffset, UCHAR channel)
{
        ULONG recordHandle = 0;
    struct bigBlob
    {
        BTHNS_SETBLOB   b;

    }*pBigBlob;

    pBigBlob = (bigBlob *)malloc(sizeof(struct bigBlob)+cSdpRecord);
    ULONG ulSdpVersion = BTH_SDP_VERSION;
    pBigBlob->b.pRecordHandle   = &recordHandle;
    pBigBlob->b.pSdpVersion     = &ulSdpVersion;
    pBigBlob->b.fSecurity       = 0;
    pBigBlob->b.fOptions        = 0;
    pBigBlob->b.ulRecordLength  = cSdpRecord;

    memcpy (pBigBlob->b.pRecord, rgbSdpRecord, cSdpRecord);
    pBigBlob->b.pRecord[iChannelOffset] = (unsigned char)channel;
    BLOB blob;
    blob.cbSize    = sizeof(BTHNS_SETBLOB) + cSdpRecord - 1;
    blob.pBlobData = (PBYTE) pBigBlob;

    WSAQUERYSET Service;
    memset (&Service, 0, sizeof(Service));
    Service.dwSize = sizeof(Service);
    Service.lpBlob = &blob;
    Service.dwNameSpace = NS_BTH;
    if (WSASetService(&Service,RNRSERVICE_REGISTER,0) == SOCKET_ERROR)
    {
        free(pBigBlob);
        return WSAGetLastError();
    }
    else
    {
        free(pBigBlob);
        return 0;
    }
}

2. 客户端

SOCKET s = socket(...);
connect(s, ...);//connects it to the server using a known server channel obtained from the SDP query or a UUID of the target service
send(s, ...);        // send request
recv(s, ...);        // get response
closesocket(s);    // done示例代码:

首先要查找周边的设备,取得该设备的信息

//Function: DiscoverDevices
//Purpose:    Searches Bluetooth devices in range
//                Populates the link list with the name and address of the devices found
//Return: If error occurs, returns the appropriate WSAGetLastError, otherwise returns zero.
int BthUtils::DiscoverDevices()
{
    WSAQUERYSET        wsaq;
    HANDLE            hLookup;
    DeviceList *    tempDevice;
    union {
        CHAR buf[5000];
        double __unused;    // ensure proper alignment
    };
    LPWSAQUERYSET pwsaResults = (LPWSAQUERYSET) buf;
    DWORD dwSize  = sizeof(buf);
    BOOL bHaveName;

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

    if (ERROR_SUCCESS != WSALookupServiceBegin (&wsaq, LUP_CONTAINERS, &hLookup))
    {
        return WSAGetLastError();
    }

    ZeroMemory(pwsaResults, sizeof(WSAQUERYSET));
    pwsaResults->dwSize = sizeof(WSAQUERYSET);
    pwsaResults->dwNameSpace = NS_BTH;
    pwsaResults->lpBlob = NULL;
    if(m_pStart)
    {
        for(m_pCurrentDevice=m_pStart;m_pCurrentDevice;)
        {
            DeviceList *temp=m_pCurrentDevice;
            m_pCurrentDevice=m_pCurrentDevice->NextDevice;
            free(temp);
        }
    }
    m_pEnd=m_pStart=NULL;
    m_iNumDevices=0;
    while (true)
    {   
        if(WSALookupServiceNext (hLookup, LUP_RETURN_NAME | LUP_RETURN_ADDR, &dwSize, pwsaResults)!=ERROR_SUCCESS)
            break;
        ASSERT (pwsaResults->dwNumberOfCsAddrs == 1);
        //Populate the link list       
        tempDevice=(DeviceList*)malloc(sizeof(DeviceList));
        tempDevice->NextDevice=NULL;
        if(m_pStart==NULL)
        {
            m_pStart = tempDevice;
            m_pEnd=m_pStart;
        }
        else
        {
            m_pEnd->NextDevice =tempDevice;
            m_pEnd=tempDevice;
        }
        m_iNumDevices++;
        m_pEnd->bthAddress = ((SOCKADDR_BTH *)pwsaResults->lpcsaBuffer->RemoteAddr.lpSockaddr)->btAddr;
        bHaveName = pwsaResults->lpszServiceInstanceName && *(pwsaResults->lpszServiceInstanceName);
        //If device name is available, add to node
        StringCchPrintf(m_pEnd->bthName, ARRAYSIZE(m_pEnd->bthName),L"%s",bHaveName ? pwsaResults->lpszServiceInstanceName : L"");
    }
    WSALookupServiceEnd(hLookup);
//    LeaveCriticalSection(&criticalSection);
    return 0;
}

//Function: GetDeviceInfo
//Purpose:    Returns name and address of all the devices in the link list in DeviceInfo. This is used by the UI to display the names and addresses of the devices found
//Output:    DeviceInfo: name and address
//Return: Success returns zero.

int BthUtils::GetDeviceInfo(DeviceInfo *pPeerDevicesInfo)
{
    int iCtr=0;
    for (m_pCurrentDevice = m_pStart;(m_pCurrentDevice);m_pCurrentDevice=m_pCurrentDevice->NextDevice,iCtr++)
    {
        StringCchPrintf(pPeerDevicesInfo[iCtr].szDeviceNameAddr, ARRAYSIZE(pPeerDevicesInfo[iCtr].szDeviceNameAddr),  L"%s:(%04x%08x)", m_pCurrentDevice->bthName, GET_NAP(m_pCurrentDevice->bthAddress), GET_SAP(m_pCurrentDevice->bthAddress));       
    }
    return 0;
}

//Function: SendMessageToServer
//Purpose:    Opens a client socket to connect to the server. Called when the local device initiates the chat.
//Input:    string containing the GUID of the service running on the server that the client wants to connect.
//            iSelectedDeviceIndex is the selected device in the UI that the local device wants to connect. If the peer device initiates the chat, the this parameter is set to -1.                           
//Return: If error occurs, returns the appropriate WSAGetLastError, otherwise returns zero.

//注:在连接前还要枚举设备,取得某个设备的信息
//strGUID为预先定义的一个字符串,rgbSdpRecord好像就是由它生成的(网上有人这么说,暂时还不确定)

int BthUtils::SendMessageToServer(WCHAR *strGUID, WCHAR *szMessage, int iSelectedDeviceIndex)
{
    int iRetVal=0, iLenMessage=0, iBytesSent=0 ;
    if(m_socketClient==INVALID_SOCKET)
    {
        iRetVal=OpenClientConnection(strGUID, iSelectedDeviceIndex);
        if(iRetVal!=0)
        {
            return iRetVal;
        }
    }
    iLenMessage = (wcslen (szMessage) + 1) * sizeof(WCHAR);

    if (iLenMessage > sizeof (WCHAR))
    {
        iBytesSent = send (m_socketClient, (char *)szMessage, iLenMessage, 0);
        if (iBytesSent != iLenMessage)
        {
            return WSAGetLastError ();
        }
    }
    return 0;
}

//Function: OpenClientConnection
//Purpose:    Opens a client socket to connect to the server.
//Input:    string containing the GUID of the service running on the server that the client wants to connect.
//            iSelectedDeviceIndex is the selected device in the UI that the local device wants to connect. If the peer device initiates the chat, the this parameter is set to -1.                           
//Return: If error occurs, returns the appropriate WSAGetLastError, otherwise returns zero.

int BthUtils::OpenClientConnection(WCHAR *strGUID, int iSelectedDeviceIndex)
{

    if (m_socketClient==INVALID_SOCKET)
    {
        GUID ServerGuid;

        if(GetGUID(strGUID, &ServerGuid))
            return -1;
        m_socketClient = socket (AF_BT, SOCK_STREAM, BTHPROTO_RFCOMM);

        if (m_socketClient == INVALID_SOCKET)
        {
            return WSAGetLastError();
        }
        SOCKADDR_BTH sa;

        memset (&sa, 0, sizeof(sa));
        sa.addressFamily = AF_BT;           
        //Search for the selected device in the list box in the link list
        m_pCurrentDevice=m_pStart;
       sa.serviceClassId=ServerGuid;

        if(iSelectedDeviceIndex==-1)
        {
            sa.btAddr=m_saClient.btAddr;
        }
        else
        {
            for (int iCount = 0 ;(m_pCurrentDevice)&&iCount!=iSelectedDeviceIndex;m_pCurrentDevice=m_pCurrentDevice->NextDevice,iCount++);
            sa.btAddr = m_pCurrentDevice->bthAddress;
        }

        if (connect (m_socketClient, (SOCKADDR *)&sa, sizeof(sa)) == SOCKET_ERROR)
        {
            m_socketClient=INVALID_SOCKET;
            return WSAGetLastError();
        }
    }
    return 0;
}

//Function: GetGUID
//Purpose:    Conversts a string containing the GUID into a GUID datatype.
//Input:        string cotaining the GUID
//Output:    GUID type
//Return: Returns -1 in case of an error, otherwise returns zero.

int BthUtils::GetGUID(WCHAR *psz, GUID *pGUID)
{
    int data1, data2, data3;
    int data4[8];

    if (11 ==  swscanf(psz, L"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x/n",
                    &data1, &data2, &data3,
                    &data4[0], &data4[1], &data4[2], &data4[3],
                    &data4[4], &data4[5], &data4[6], &data4[7])) {
        pGUID->Data1 = data1;
        pGUID->Data2 = data2 & 0xffff;
        pGUID->Data3 = data3 & 0xffff;

        for (int i = 0 ; i < 8 ; ++i)
            pGUID->Data4[i] = data4[i] & 0xff;

        return 0;
    }
    return -1;
}

五.编程中使用的基本函数和数据结构
  查询周围设备:http://msdn.microsoft.com/en-us/library/ms881713.aspx

WSAQUERYSET structure
WSALookupServiceBegin function
WSALookupServiceNext function
WSALookupServiceEnd function
注册服务:http://msdn.microsoft.com/en-us/library/aa450140.aspx

BTHNS_SETBLOB
WSASetService

           WINSOCK:http://msdn.microsoft.com/en-us/library/ms885826.aspx

socket (Bluetooth)
bind (Bluetooth)
getsockname (Bluetooth)
getpeername (Bluetooth)
connect (Bluetooth)
accept (Bluetooth)
getsockopt (Bluetooth)
setsockopt (Bluetooth)
 

 

注:

a. 取得设备Owner信息: 读取HKEY_CURRENT_USER/ControlPanel/Owner的Name
    相关函数:RegOpenKeyEx,RegQueryValueEx,RegCloseKey

b. 蓝牙的配置是通过注册表进行的,存储在common.reg里,其路径是%WINCEROOT%/Public/Common/Oak/Files.
相关项目为:

Stack Registry Settings
Audio Gateway Registry Settings
Modem Gateway Registry Settings
Personal Area Network (PAN) Profile Registry Settings
Human Interface Device (HID) Profile Registry Settings
相关键值如下:
HKEY_LOCAL_MACHINE/Software/Microsoft/Bluetooth/l2cap
HKEY_LOCAL_MACHINE/Software/Microsoft/Bluetooth/rfcomm
HKEY_LOCAL_MACHINE/Software/Microsoft/Bluetooth/sys
等等...
更详细信息参见http://msdn.microsoft.com/en-us/library/ms880971.aspx

c. Bluetooth Security http://msdn.microsoft.com/en-us/library/ms880973.aspx

d. Bluetooth Reference http://msdn.microsoft.com/en-us/library/ms900590.aspx

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值