C sharp 蓝牙模块设计
C#CF
Visual Studio 2008 win CE测试通过,仅需根据自己境况调用不同参数,请仔细使用。
关键代码汇总:
// 查找本地蓝牙DeviceID 是一串以“:”分隔的16进制数字
[DllImport("Btdrt.dll", SetLastError=true)]
public static extern int BthReadLocalAddr(byte[] pba);
// 初始化查找周围设备
[DllImport("Btdrt.dll", SetLastError=true)]
public static extern int BthNsLookupServiceBegin(
byte[] pQuerySet,// Pointer to the search criteria
LUP_CONTAINERS or LUP_RES_SERVICE string dwFlags,//
ref int lphLookup);//[out] Handle to be used when calling the BthNsLookupServiceNext
function to start retrieving the results set
// 开始查找蓝牙设备
[DllImport("Btdrt.dll", SetLastError=true)]
public static extern int BthNsLookupServiceNext(
int hLookup,// Handle obtained from BthNsLookupServiceBegin
string dwFlags,// LUP_RETURN_NAME, LUP_RETURN_ADDR, LUP_RETURN_BLOB,
BTHNS_LUP_RESET_ITERATOR or BTHNS_LUP_NO_ADVANCE
ref int lpdwBufferLength,//[in, out] On input, the number of bytes contained in the
buffer that pResults points to
//On output, this parameter contains the minimum number of bytes the pResults
parameter uses to retrieve the record,
//providing the function fails and the error is BthNsEFAULT.
byte[] pResults);// Points to a block of memory where the result set is stored as
a int structure on return.
// 查找周围蓝牙设备结束
[DllImport("Btdrt.dll", SetLastError = true)]
public static extern int BthNsLookupServiceEnd(int hLookup);// Handle obtained from
BthNsLookupServiceBegin
// 获取配对码请求
[DllImport("Btdrt.dll", SetLastError=true)]
public static extern int BthGetPINRequest(byte[] pba);// Pointer to the Bluetooth address
// 设置配对码
Tangmei007@foxmail.com
[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);
//BthGetHardwareStatus
[DllImport("Btdrt.dll", SetLastError=true)]
public static extern int BthGetHardwareStatus(ref int pistatus);
//BthRevokePin
[DllImport("Btdrt.dll", SetLastError=true)]
public static extern int BthRevokePIN(byte[] pba);
[DllImport("ws2.dll", EntryPoint="WSAGetLastError", SetLastError=true)]
public static extern int CeGetLastError();
[DllImport("ws2_32.dll", EntryPoint="WSAGetLastError", SetLastError=true)]
public static extern int XpGetLastError();
//SetService
[DllImport("Btdrt.dll",SetLastError=true)]
public static extern int BthNsSetService(
byte[] lpqsRegInfo,
int essoperation,
int dwControlFlags);
一. 基本要点
1本文档时在VS2008中通过,其他VISUAL STUDIO 版本暂不知结果怎样。
2:需要熟悉非托管代码的调用。其实很简单,直接用就是的。
第一步:c#创建智能设备,目标平台windows CE
第二步:添加引用using System.Runtime.InteropServices;
二. 关于蓝牙
网上关于蓝牙开发的文章,很多人在.net CF开发中把蓝牙通信当作一个串行通信来处理,这也是不
Tangmei007@foxmail.com
错的,但是我不是很喜欢,因为这样做的话,并不是针对蓝牙来开发的,换言之,在使用过程中,需要先
手动开启蓝牙,配对,连接,建立串行通道,然后开启应用程序使用,你还要在应用程序中设置串行端口,
对最终用户来讲,这是非常麻烦的。我觉得,这样的解决
冠上蓝牙通信的名头简直就是……不多说了,
书归正传。
蓝牙设备的DeviceID是一串以“:”分隔的16进制数字。
蓝牙有开启、关闭、可发现三种状态。
蓝牙有安全设置,所以我们需要对蓝牙设备进行配对。
在.net的Socket地址族里有IrDA,但是没有蓝牙相关的地址族,这是我们需要解决的问题。
三. 获取设备ID
1.获取本地设备的ID
IDBthReadLocalAddr
第三步:用托管代码进行包装:
[DllImport("Btdrt.dll", SetLastError=true)]
public static extern int BthReadLocalAddr(byte[] pba);
2.获取远程设备的ID
其实谈到获取远程设备的ID就涉及到如何去发现远程设备了,所以这里就一并把发现设备的
也
说明了吧。
发现设备需要用到三个Winsock API,分别是WSALookupServiceBegin、
WSALookupServiceNext和WSALookupServiceEnd,最多有七个可以连接。
WSALookupServiceBegin的
数原形是这样的:
INT WSALookupServiceBegin(
LPWSAQUERYSET lpqsRestrictions,
DWORD dwControlFlags,
LPHANDLE lphLookup
);
第四步:用托管代码进行包装:
[DllImport("ws2.dll", EntryPoint="WSALookupServiceBegin", SetLastError=true)]
public static extern int CeLookupServiceBegin(byte[] pQuerySet, string dwFlags, ref int
lphLookup);
可以看到,本来lpqsRestrictions是一个struct,经过包装后在托管代码中成为了byte[],我们计
算好该struct大概要占用多少个byte,struct中每一个成员在byte数组中的位置是怎样的,装配出来就好了。
装配pQuerySet:
byte[] buffer1 = new byte[0x400];
BitConverter.GetBytes(60).CopyTo(buffer1, 0);
GCHandle handle1 = GCHandle.Alloc(blob1.ToByteArray(), GCHandleType.Pinned);
Tangmei007@foxmail.com
IntPtr ptr1 = handle1.AddrOfPinnedObject();
BitConverter.GetBytes((int) (ptr1.ToInt32() + 4)).CopyTo(buffer1, 0x38);
另外的两个API也照类似方法调用即可。
在调用了WSALookupServiceNext之后,bytes数组pQuerySet中便包含了远程设备的地址信息,
下面我们需要把它找出来:
int num5 = BitConverter.ToInt32(buffer1, 0x30);
int num6 = Marshal.ReadInt32((IntPtr) num5, 8);
int num7 = Marshal.ReadInt32((IntPtr) num5, 12);
SocketAddress address1 = new SocketAddress(AddressFamily.Unspecified, num7);
因为.net框架的地址族里面没有蓝牙,所以我们这里用的是AddressFamily.Unspecified。然后的
工作就是从中获取远程设备的ID了:
前面我们已经计算出,这个Address里面的前六个字节是byte数组形式的设备ID,第七到第二十二
个字节是蓝牙的Service Guid,在后面四个字节是端口号,所以我们只需要分别提取出来即可。
四. 监听服务
监听服务调用的是非托管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.
Tangmei007@foxmail.com
五. 连接
我们知道,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);
Tangmei007@foxmail.com
然后是配对码验证:
[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);
Tangmei007@foxmail.com