.NET Compact Framework (NET CF) 3.5 內建 System.Net.Sockets.IrDAClient 支援紅外通訊,但**缺乏藍牙 API**。在 Windows Mobile / Windows CE 設備上開發藍牙應用,必須透過 **P/Invoke 呼叫非受控 DLL (btdrt.dll, ws2.dll, bthutil.dll)** 實作完整藍牙協定棧:本地位址查詢、裝置發現 (Inquiry)、服務發現 (SDP)、RFCOMM 連線、安全配對、無線電狀態控制。本文系統整理完整開發流程、關鍵 API 簽章、資料結構對應、已知相容性問題與現代替代方案。
Managed Code (C#)
│
▼ P/Invoke (DllImport)
┌─────────────────────────────────────┐
│ btdrt.dll │ 本地位址、配對、ACL、PIN、無線電模式 │
│ ws2.dll │ Winsock 2: Inquiry、SDP、Socket 連線 │
│ bthutil.dll │ 無線電開關、模式切換 │
└─────────────────────────────────────┘
│
▼ Windows CE / Windows Mobile 藍牙協定棧
│
▼ 藍牙硬體控制器 (HCI)
[DllImport("btdrt.dll", SetLastError = true, EntryPoint = "BthReadLocalAddr")]
public static extern int BthReadLocalAddr(byte[] pba); // pba: 6 bytes output
// 使用範例
public static string GetLocalBluetoothAddress()
{
byte[] addr = new byte[6];
int hr = BthReadLocalAddr(addr);
if (hr != 0) throw new System.ComponentModel.Win32Exception(hr);
// 位元組順序:addr[5]:addr[4]:...:addr[0] (大端顯示)
return string.Join(":", addr.Reverse().Select(b => b.ToString("X2")));
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct WSAQUERYSET
{
public int dwSize;
public string lpszServiceInstanceName;
public IntPtr lpServiceClassId;
public IntPtr lpVersion;
public string lpszComment;
public int dwNameSpace;
public IntPtr lpNSProviderId;
public string lpszContext;
public int dwNumberOfProtocols;
public IntPtr lpafpProtocols;
public string lpszQueryString;
public int dwNumberOfCsAddrs;
public IntPtr lpcsaBuffer;
public int dwOutputFlags;
public IntPtr lpBlob; // 指向 BLOB 結構
}
[DllImport("ws2.dll", EntryPoint = "WSALookupServiceBegin", SetLastError = true)]
public static extern int WSALookupServiceBegin(
ref WSAQUERYSET lpqsRestrictions,
int dwControlFlags,
out IntPtr lphLookup);
[DllImport("ws2.dll", EntryPoint = "WSALookupServiceNext", SetLastError = true)]
public static extern int WSALookupServiceNext(
IntPtr hLookup,
int dwControlFlags,
ref int lpdwBufferLength,
byte[] lpqsResults);
[DllImport("ws2.dll", EntryPoint = "WSALookupServiceEnd", SetLastError = true)]
public static extern int WSALookupServiceEnd(IntPtr hLookup);
// 旗標常數
public const int LUP_FLUSHCACHE = 0x1000;
public const int LUP_RETURN_NAME = 0x10;
public const int LUP_RETURN_ADDR = 0x100;
public const int NS_BTH = 16; // Bluetooth namespace
// WSAQUERYSET 的 lpBlob 指向 BTH_QUERY_DEVICE (Inquiry) 或 BTH_SDP_RECORD (SDP)
// SDP 查詢需指定 Service Class UUID (如 SerialPort: 0x1101)
// 解析 WSALookupServiceNext 回傳的 BLOB 中 CSADDR_INFO 陣列
// CSADDR_INFO 前 6 bytes = 遠端 MAC,接著 16 bytes = Service Class UUID,最後 4 bytes = RFCOMM Channel (port)
// .NET CF Socket 不支援 AddressFamily.Bluetooth (值 32)
// 必須用反射或強制轉型建立 Socket
Socket sock = new Socket((AddressFamily)32, SocketType.Stream, (ProtocolType)3); // AF_BTH=32, BTHPROTO_RFCOMM=3
// 連線需 SOCKADDR_BTH 結構
[StructLayout(LayoutKind.Sequential)]
public struct SOCKADDR_BTH
{
public short addressFamily; // AF_BTH = 32
public long btAddr; // 遠端 MAC (ulong)
public int serviceClassId; // Service Class UUID (低 32 bits) 或 0
public int port; // RFCOMM Channel (1-30) 或 0 表自動 SDP
}
// 連線範例
public static void Connect(string remoteMac, int channel)
{
var sock = new Socket((AddressFamily)32, SocketType.Stream, (ProtocolType)3);
var addr = new SOCKADDR_BTH
{
addressFamily = 32,
btAddr = ParseMac(remoteMac), // ulong
port = channel
};
sock.Connect(SerializeSockAddr(addr)); // 需手動序列化為 byte[]
}
[DllImport("btdrt.dll", SetLastError = true)]
public static extern int BthGetPINRequest(byte[] pba); // 等待 PIN 請求,pba 輸出遠端 MAC
[DllImport("btdrt.dll", SetLastError = true)]
public static extern int BthSetPIN(byte[] pba, int cPinLength, byte[] ppin); // 設定 PIN 碼
[DllImport("btdrt.dll", SetLastError = true)]
public static extern int BthCreateACLConnection(byte[] pba, out ushort phandle); // 建立 ACL 連線
[DllImport("btdrt.dll", SetLastError = true)]
public static extern int BthAuthenticate(byte[] pba); // 發起認證 (配對)
[DllImport("btdrt.dll", SetLastError = true)]
public static extern int BthCloseConnection(ushort handle); // 關閉 ACL
// 完整配對流程
public static bool PairDevice(string remoteMac)
{
byte[] mac = ParseMacBytes(remoteMac);
// 1. 等待/取得 PIN 請求 (或主動設定 PIN)
BthSetPIN(mac, pin.Length, Encoding.ASCII.GetBytes(pin));
// 2. 建立 ACL
if (BthCreateACLConnection(mac, out ushort handle) != 0) return false;
// 3. 認證
if (BthAuthenticate(mac) != 0) { BthCloseConnection(handle); return false; }
// 4. 關閉 ACL
BthCloseConnection(handle);
return true;
}
public enum RadioMode { PowerOff = 0, Connectable = 1, Discoverable = 2 }
[DllImport("bthutil.dll", SetLastError = true)]
public static extern int BthSetMode(RadioMode dwMode);
[DllImport("bthutil.dll", SetLastError = true)]
public static extern int BthGetMode(out RadioMode dwMode);
// 使用範例
BthSetMode(RadioMode.Discoverable); // 可被發現
BthSetMode(RadioMode.Connectable); // 可連線不可被發現
BthSetMode(RadioMode.PowerOff); // 關閉藍牙省電
public static byte[] SerializeSockAddr(SOCKADDR_BTH addr)
{
// SOCKADDR_BTH 在記憶體佈局:short(2) + long(8) + int(4) + int(4) = 18 bytes
// 但 Windows CE 可能要求對齊至 20 bytes
byte[] buf = new byte[20];
BitConverter.GetBytes(addr.addressFamily).CopyTo(buf, 0);
BitConverter.GetBytes(addr.btAddr).CopyTo(buf, 2);
BitConverter.GetBytes(addr.serviceClassId).CopyTo(buf, 10);
BitConverter.GetBytes(addr.port).CopyTo(buf, 14);
return buf;
}
public static ulong ParseMac(string mac)
{
string[] parts = mac.Split(':', '-');
ulong addr = 0;
for (int i = 0; i < 6; i++)
addr = (addr << 8) | Convert.ToByte(parts[i], 16);
return addr;
}
public static byte[] ParseMacBytes(string mac)
=> ParseMac(mac).ToBytesLittleEndian().Take(6).Reverse().ToArray(); // 注意位元組序
| 問題 | 原因 | 對策 |
|---|---|---|
| 特定廠商裝置 (Lenovo ET560, Asus A730) 無法發現/連線 | 廠商自訂藍牙堆疊 (Widcomm/Broadcom/BlueSoleil) 取代 Microsoft 堆疊,封鎖/未實作 MS Winsock Bluetooth API | 1. 偵測堆疊類型 (Registry: HKLM\Software\Microsoft\BluetoothStack) 2. 改用廠商 SDK (Widcomm BTW SDK, BlueSoleil SDK) 3. 降級需求:僅支援 Microsoft 堆疊裝置 |
| WSALookupServiceNext 回傳 WSAEFAULT (緩衝區太小) | 查詢結果包含多筆裝置/服務,單次緩衝區不足 | 迴圈呼叫,依錯誤碼動態擴大緩衝區 (初始 1KB, 失敗則 x2) |
| Socket.Connect 拋出 10022 (Invalid argument) | SOCKADDR_BTH 結構對齊/大小不符,或 port=0 但未正確 SDP | 固定 port (如 1=SPP) 或確實實作 SDP 取得 channel |
| 配對對話框不彈出 / PIN 碼無效 | 系統藍牙 UI 進程 (btsetup.exe) 未啟動或被替換 | 手動呼叫 BthSetPIN 設定固定 PIN (如 "0000" 或 "1234") |
Plugin.BLE (Xamarin/MAUI) - 封裝 Android/iOS/UWP BLE APIBluetoothLE (Windows 10+) - Windows.Devices.Bluetooth.GenericAttributeProfileBlueZ D-Bus (Linux) - org.bluez 介面BluetoothSocket (UUID: 00001101-0000-1000-8000-00805F9B34FB)RfcommDeviceService + StreamSocketrfcomm bind + /dev/rfcomm0 序列埠.NET CF 3.5 藍牙開發屬於**考古級技術**,核心在於大量 P/Invoke 對接 Microsoft 藍牙堆疊 (btdrt.dll, ws2.dll)。關鍵挑戰:廠商堆疊相容性、SOCKADDR_BTH 序列化、SDP 解析、配對流程同步。現代專案**嚴禁新增投資**此技術棧,應規劃遷移至支援中的平台與標準 API (Android Bluetooth API, Windows.Devices.Bluetooth, BlueZ)。若必須維護既有專案,建議封裝成獨立 Native DLL (C++), 由 Managed 代碼薄包裝呼叫,隔離非受控複雜度。