首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

.NET Compact Framework 3.5 藍牙通訊模組開發全攻略:P/Invoke、Winsock、SDP、RFCOMM、安全配對

     編輯:關於C語言

前言

.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)

關鍵 P/Invoke 簽章完整定義

1. 本地藍牙位址 (MAC)

[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")));
}

2. 裝置發現 (Inquiry) - Winsock 2 Bluetooth Namespace

[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

3. 服務發現 (SDP) - 取得 RFCOMM Channel


// 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)

4. RFCOMM Socket 連線

// .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[]
}

5. 安全配對流程

[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;
}

6. 無線電狀態控制

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 API1. 偵測堆疊類型 (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")

現代替代方案 (強烈建議)

  1. 升級平台:Windows Mobile/CE 已停止支援。遷移至 **Android (Kotlin/Java)**、**iOS (Swift)**、**Windows 10/11 (UWP/WinUI 3 + Windows.Devices.Bluetooth)**、**Linux (BlueZ + D-Bus)**。
  2. 跨平台庫:
    • Plugin.BLE (Xamarin/MAUI) - 封裝 Android/iOS/UWP BLE API
    • BluetoothLE (Windows 10+) - Windows.Devices.Bluetooth.GenericAttributeProfile
    • BlueZ D-Bus (Linux) - org.bluez 介面
  3. 經典藍牙 (RFCOMM/SPP) 現代實作:
    • Android: BluetoothSocket (UUID: 00001101-0000-1000-8000-00805F9B34FB)
    • Windows: RfcommDeviceService + StreamSocket
    • Linux: rfcomm 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 代碼薄包裝呼叫,隔離非受控複雜度。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved