监听服务调用的是非托管API WSASetService,其原型是
INT WSASetService(
LPWSAQUERYSET lpqsRegInfo,
WSAESETSERVICEOP essoperation,
DWORD dwControlFlags
);
可以看到关键也是第一个参数,lpqsRegInfo,这也是一个struct,我们的包装方法与前面的发现设备采用的方法类似,做蓝牙通信时要注意其成员要如下设置:
lpqsRegInfo | dwSize | sizeof(WSAQUERYSET) |
lpszServiceInstanceName | Not supported on Windows CE. Set to 0. | |
lpServiceClassId | Not supported on Windows CE. Set to 0. | |
dwNameSpace | NS_BTH. | |
dwNumberOfCsAddrs | Not supported on Windows CE. Set to 0. | |
IpcsaBuffer | Not supported on Windows CE. Set to 0. | |
lpBlob | Points to a BTHNS_SETBLOB structure, containing information about the service to be added. | |
* | All other WSAQUERYSET fields are ignored. |
五. 连接
我们知道,IrDA中连接远程服务是使用方法System.Net.Sockets.IrDAClient类中的Connect方法。而这个方法又是调用的Socket类中的Connect方法。而Socket类是一个比较抽象的类,它并不绑定某个具体的地址族、SocketType和protocolType,所以在实例化的时候,需要指定这三个参数。我们也知道,在IrDA中,这三个参数分别是AddressFamily.Irda, SocketType.Stream,和ProtocolType.IP,那么在蓝牙中这三个参数分别是什么呢?我们好像找不到。
且慢,真是这样吗?
我们知道在.net中,这三个参数都是枚举值,而枚举在默认情况下,你可以认为就是int值的替代表现。
我们该如何知道这三个参数到底是什么呢?
还是先看Socket类的Connect方法。
我们查查有关资料,可以知道这个方法实际上是调用的一个非托管函数: [DllImport("mscoree", EntryPoint="@339")]
public static extern int connect(int s, byte[] name, int namelen);
也就是非托管的Socket API。
我们看Windows CE 4.2的SDK,可以看到,在使用蓝牙进行连接的时候,需要使用WinSock扩展。我们还可以看到,在使用蓝牙进行连接的时候,三个参数分别应当是AF_BTH、SOCK_STREAM和BTHPROTO_RFCOMM,至于这三个参数分别代表什么,我们就要查看相关的头文件了。
我们找到ws2bth.h头文件,可以看到AF_BTH代表十进制数32,而BTHPROTO_RFCOMM代表十六进制数0x0003,恰好和ProtocolType.Ggp代表的数值是一致的。所以,我们在实例化Socket时是这么写的: new Socket((AddressFamily) 0x20, SocketType.Stream, ProtocolType.Ggp);
Socket实例化出来了,其他的当然就都好说了,这里不再赘述。
六. 蓝牙的安全设置
蓝牙比红外多了安全方面的设置,所以就需要多一些代码来处理这些。具体也就不多说了,其实也就是一些非托管代码的包装调用,这些API在Btdrt.dll中:
获取配对码请求: [DllImport("Btdrt.dll", SetLastError=true)]
public static extern int BthGetPINRequest(byte[] pba);
设置配对码: [DllImport("btdrt.dll", SetLastError=true)]
public static extern int BthSetPIN(byte[] pba, int cPinLength, byte[] ppin);
比较麻烦点的是配对,总共有三步操作:
首先是创建ACL连接: [DllImport("Btdrt.dll", SetLastError=true)]
public static extern int BthCreateACLConnection(byte[] pbt, ref ushort phandle);
然后是配对码验证: [DllImport("Btdrt.dll", SetLastError=true)]
public static extern int BthAuthenticate(byte[] pbt);
然后一定要关闭连接: [DllImport("Btdrt.dll", SetLastError=true)]
public static extern int BthCloseConnection(ushort handle);
七. 设置蓝牙无线电状态
我们知道,蓝牙无线电有打开、关闭、可发现三种状态,那么我们如何实现编程控制呢?
我想这个一定大家都知道了,因为网上有很多关于这个的文章:
先写一个枚举: public enum RadioMode
{
Connectable = 1,
Discoverable = 2,
PowerOff = 0
}
然后写一个函数调用非托管代码即可: [DllImport("BthUtil.dll", SetLastError=true)]
public static extern int BthSetMode(RadioMode dwMode);
获取无线电状态的话就用下面的函数: [DllImport("BthUtil.dll", SetLastError=true)]
public static extern int BthGetMode(ref RadioMode dwMode);
八. 已知的问题
可能是因为蓝牙控制软件还没有实现标准化或者还是其他的问题,我们发现根据Windows CE 4.2 SDK 使用Winsock 扩展做的蓝牙开发有一个问题,而且不论是本文中所述的托管代码还是其他的非托管代码,只要是用的这种思路用Winsock 2做的开发都会存在这样一个问题,那就是不是在所有的Windows Mobile设备上都能正常运行。经过我的测试,我发现在很多使用另行开发的蓝牙控制软件的设备上,如联想ET560、华硕MyPAL A730上都无法运行,而在没有另行开发蓝牙控制软件的设备上是可以正常运行的,我不知道这是什么原因,初步推测可能是厂商另行开发的蓝牙控制软件屏蔽了微软的API的缘故,到底是不是这样,还得请高人指点。