在Windows Mobile軟件開(kāi)發(fā)中.Net正扮演著(zhù)日益重要的角色,我們已經(jīng)可以看到很多用.Net CF開(kāi)發(fā)的軟件,這些軟件涉及到了日常應用的方方面面。在智能設備的軟件開(kāi)發(fā)中,無(wú)線(xiàn)互聯(lián)是一個(gè)相當重要的一塊,我們可以看到,紅外幾乎是所有智能設備的標配,而藍牙也日益在越來(lái)越多的智能設備上出現,有了硬件,顯然要有相應的軟件相關(guān)的應用。
我們也知道,用.NET CF開(kāi)發(fā)紅外通信應用時(shí)相當輕松的,因為.NET CF中有一個(gè)命名空間System.Net.IrDA就是用于紅外通信的通信模塊。但是,.NET CF中還沒(méi)有關(guān)于藍牙通信的模塊,所以目前來(lái)講做這方面的開(kāi)發(fā)還有一定的困難。下面,就談?wù)勅绾斡肅#開(kāi)發(fā).NET CF藍牙通信模塊。
一. 基本要點(diǎn) 首先明確一點(diǎn),因為涉及到驅動(dòng)硬件的問(wèn)題,所以?xún)H靠了解C#開(kāi)發(fā)的相關(guān)知識顯然是無(wú)法完成開(kāi)發(fā)的,我們必須對C++開(kāi)發(fā)有所了解。但是為了簡(jiǎn)單起見(jiàn),我們不希望用C++寫(xiě)半行代碼,所有的編碼工作全部使用C#,也就是說(shuō),使用的開(kāi)發(fā)環(huán)境只需要使用Visual Studio.net,不需要用其他的編輯器。
作為開(kāi)發(fā)這類(lèi)驅動(dòng)硬件的程序的知識準備,您需要了解C++的基本知識,知道頭文件是怎么一回事,知道托管代碼如何與非托管代碼交互。因為本文的核心是說(shuō)明如何開(kāi)發(fā).net CF藍牙通信模塊,所以前述這些準備知識并不作講述。
二. 關(guān)于藍牙 做藍牙通信模塊開(kāi)發(fā),自然先要知道藍牙通信是怎么一回事。在我看來(lái),藍牙通信應該和紅外通信模塊類(lèi)似,當然我是從開(kāi)發(fā)者的角度來(lái)講,抽象化以后應該就是這樣,當然藍牙和紅外通信也有很多不一樣的地方,這在面向對象設計里面怎么講,我想一定有很多人理解的比我透徹。好了,這就是我們的基本思路了。我曾經(jīng)在網(wǎng)上查過(guò)關(guān)于藍牙開(kāi)發(fā)的文章,很多人在.net CF開(kāi)發(fā)中把藍牙通信當作一個(gè)串行通信來(lái)處理,這也是不錯的,但是我不是很喜歡,因為這樣做的話(huà),并不是針對藍牙來(lái)開(kāi)發(fā)的,換言之,在使用過(guò)程中,需要先手動(dòng)開(kāi)啟藍牙,配對,連接,建立串行通道,然后開(kāi)啟應用程序使用,你還要在應用程序中設置串行端口,對最終用戶(hù)來(lái)講,這是非常麻煩的。我覺(jué)得,這樣的解決方案冠上藍牙通信的名頭簡(jiǎn)直就是……不多說(shuō)了,書(shū)歸正傳。
在紅外通信中,我們知道,設備的DeviceID是一個(gè)Byte數組,那么藍牙設備的DeviceID什么樣子呢?我想這個(gè)大家都很清楚,是一串以“:”分隔的16進(jìn)制數字。
紅外通信中,一般而言紅外并沒(méi)有開(kāi)啟、關(guān)閉之類(lèi)的狀態(tài),但是藍牙有開(kāi)啟、關(guān)閉、可發(fā)現三種狀態(tài)。
紅外沒(méi)有安全設置,而藍牙有安全設置,所以我們需要對藍牙設備進(jìn)行配對,而紅外通信這部需要。
我們查看.net的Socket地址族里有IrDA,但是沒(méi)有藍牙相關(guān)的地址族,這是我們需要解決的問(wèn)題。
三. 獲取設備ID 1.獲取本地設備的ID
我們查看Window CE 4.2的SDK文檔,得知獲取本地設備ID的函數是BthReadLocalAddr,在btdrt.dll中。SDK文檔中的英文原文是這樣的:“This function retrieves the Bluetooth address of the current device.”好了,知道了這個(gè)就好說(shuō)了:
首先封裝本地托管函數:
[DllImport("Btdrt.dll", SetLastError=true)]
public static extern int BthReadLocalAddr(byte[] pba); |
這個(gè)函數得到的本地DeviceID也是一組byte數組,為了向人們顯示出來(lái),我們要把它變?yōu)镾tring:
string text1 = "";
text1 = text1 + pba[5].ToString("X2") + ":";
text1 = text1 + pba [4].ToString("X2") + ":";
text1 = text1 + pba [3].ToString("X2") + ":";
text1 = text1 + pba [2].ToString("X2") + ":";
text1 = text1 + pba [1].ToString("X2") + ":";
return (text1 + pba [0].ToString("X2")); |
2.獲取遠程設備的ID
其實(shí)談到獲取遠程設備的ID就涉及到如何去發(fā)現遠程設備了,所以這里就一并把發(fā)現設備的方法也說(shuō)明了吧。 發(fā)現設備需要用到三個(gè)Winsock API,分別是WSALookupServiceBegin、WSALookupServiceNext和WSALookupServiceEnd,這三個(gè)API到底起什么作用可以去查看Windows CE 4.2的SDK,這里就不詳細解釋了,只談一下幾個(gè)需要注意的地方。
WSALookupServiceBegin的函數原形是這樣的:
INT WSALookupServiceBegin(
LPWSAQUERYSET lpqsRestrictions,
DWORD dwControlFlags,
LPHANDLE lphLookup
); |
我們用托管代碼進(jìn)行包裝:
[DllImport("ws2.dll", EntryPoint="WSALookupServiceBegin", SetLastError=true)]
public static extern int CeLookupServiceBegin(byte[] pQuerySet, LookupFlags dwFlags, ref int lphLookup); |
可以看到,本來(lái)lpqsRestrictions是一個(gè)struct,經(jīng)過(guò)包裝后在托管代碼中成為了byte[],我們計算好該struct大概要占用多少個(gè)byte,struct中每一個(gè)成員在byte數組中的位置是怎樣的,裝配出來(lái)就好了。
由于是針對藍牙作的開(kāi)發(fā),所以我們要查看一下這些參數應該是哪些值。Windows CE 4.2的SDK中說(shuō),藍牙開(kāi)發(fā)時(shí),struct LPWSAQUERYSET中的如下成員應當為這些值:
The dwSize member must be sizeof(WSAQUERYSET).
The lpBlob member (itself a pointer to a BLOB structure) is optional, but if used, the device inquire parameters valid for LUP_FLUSHCACHE are the following:
The cbSize member of the BLOB structure must be sizeof(BTH_QUERY_DEVICE).
The pBlobData member is a pointer to a BTH_QUERY_DEVICE structure, for which the LAP member is the Bluetooth inquiry access code, and the length member is the length of the inquiry, in seconds.
The dwNameSpace member must be NS_BTH.
All other WSAQUERYSET members are ignored. |
具體什么意思各位可以自己去理解,我想比我翻譯出來(lái)要好些,畢竟我英語(yǔ)很差的。根據以上要求,我們這樣裝配pQuerySet:
byte[] buffer1 = new byte[0x400];
BitConverter.GetBytes(60).CopyTo(buffer1, 0);
GCHandle handle1 = GCHandle.Alloc(blob1.ToByteArray(), GCHandleType.Pinned);
IntPtr ptr1 = handle1.AddrOfPinnedObject();
BitConverter.GetBytes((int) (ptr1.ToInt32() + 4)).CopyTo(buffer1, 0x38); |
另外的兩個(gè)API也照類(lèi)似方法調用即可。
在調用了WSALookupServiceNext之后,bytes數組pQuerySet中便包含了遠程設備的地址信息,下面我們需要把它找出來(lái)。通過(guò)閱讀SDK中WSAQUERYSET結構的說(shuō)明和計算每個(gè)成員的位置之后,我們寫(xiě)出如下代碼:
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框架的地址族里面沒(méi)有藍牙,所以我們這里用的是AddressFamily.Unspecified。
然后的工作就是從中獲取遠程設備的ID了:
前面我們已經(jīng)計算出,這個(gè)Address里面的前六個(gè)字節是byte數組形式的設備ID,第七到第二十二個(gè)字節是藍牙的Service Guid,在后面四個(gè)字節是端口號,所以我們只需要分別提取出來(lái)即可。