我正在尝试使用 c# 中的 win32 API 注册 GATT 特征值更改通知。我能够获取设备的句柄并获取相关的 GATT 服务和特性。当我尝试使用
BluetoothGATTRegisterEvent
注册值更改通知时,问题就出现了:
https://learn.microsoft.com/en-us/windows/win32/api/bluetoothleapis/nf-bluetoothleapis-bluetoothgattregisterevent
调用该方法时,我总是得到 0x57 (E_INVALIDARG)。不幸的是,我没有得到任何关于什么 arg 无效的提示...我尝试了许多不同的变体,但没有运气。以下是用于调用BluetoothGATTRegisterEvent 的结构体。我省略了服务和特征发现,因为这似乎工作正常。
任何对我的定义和调用
BluetoothGATTRegisterEvent
可能有问题的建议将不胜感激。
public enum BTH_LE_GATT_EVENT_TYPE
{
CharacteristicValueChangedEvent = 0
}
[StructLayout(LayoutKind.Sequential)]
public struct BLUETOOTH_GATT_NOTIFICATION_REGISTRATION
{
public ushort NumCharacteristics;
public BTH_LE_GATT_CHARACTERISTIC[] Characteristics;
}
[StructLayout(LayoutKind.Sequential)]
public struct BLUETOOTH_GATT_EVENT_HANDLE
{
public IntPtr handle;
}
// Define the callback delegate
internal delegate void PFNBLUETOOTH_GATT_EVENT_CALLBACK(
BTH_LE_GATT_EVENT_TYPE EventType,
IntPtr EventOutParameter,
IntPtr Context
);
[DllImport("BluetoothApis.dll", SetLastError = true)]
public static extern uint BluetoothGATTRegisterEvent(
ushort Service,
BTH_LE_GATT_EVENT_TYPE eventType,
ref BLUETOOTH_GATT_NOTIFICATION_REGISTRATION eventParameterIn,
PFNBLUETOOTH_GATT_EVENT_CALLBACK callback,
IntPtr context,
ref BLUETOOTH_GATT_EVENT_HANDLE eventHandle,
uint Flags
);
public void RegisterEvents(
SafeFileHandle deviceHandle,
BTH_LE_GATT_SERVICE service,
BTH_LE_GATT_CHARACTERISTIC characteristic,
PFNBLUETOOTH_GATT_EVENT_CALLBACK eventHandler
)
{
var eventHandle = new BLUETOOTH_GATT_EVENT_HANDLE();
var eventParameters = new BLUETOOTH_GATT_NOTIFICATION_REGISTRATION
{
NumCharacteristics = 1,
Characteristics = new[] { characteristic }
};
BluetoothGATTRegisterEvent(
characteristic.ServiceHandle,
BTH_LE_GATT_EVENT_TYPE.CharacteristicValueChangedEvent,
ref eventParameters,
eventHandler,
IntPtr.Zero,
ref eventHandle,
BLUETOOTH_GATT_FLAG_NONE
);
}
我尝试使用设备句柄和从特征中获取的服务句柄作为第一个参数。改变这个并没有什么区别
这是我的蓝牙框架的一部分,展示了如何使用旧版 GATT API 订阅特征更改通知。
private enum BTH_LE_GATT_EVENT_TYPE : uint
{
CharacteristicValueChangedEvent
};
[StructLayout(LayoutKind.Sequential)]
private struct BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION
{
[MarshalAs(UnmanagedType.U2)]
public UInt16 NumCharacteristics;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public BTH_LE_GATT_CHARACTERISTIC[] Characteristics;
};
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void PFNBLUETOOTH_GATT_EVENT_CALLBACK(
[param: MarshalAs(UnmanagedType.U4), In] BTH_LE_GATT_EVENT_TYPE EventType,
[param: In, Out] ref BLUETOOTH_GATT_VALUE_CHANGED_EVENT EventOutParameter,
[param: MarshalAs(UnmanagedType.SysInt), In] IntPtr Context);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.I4)]
private delegate Int32 PBluetoothGATTRegisterEvent(
[param: MarshalAs(UnmanagedType.SysInt), In] IntPtr hService,
[param: MarshalAs(UnmanagedType.U4), In] BTH_LE_GATT_EVENT_TYPE EventType,
[param: In, Out] ref BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION EventParameterIn,
[param: MarshalAs(UnmanagedType.FunctionPtr), In] PFNBLUETOOTH_GATT_EVENT_CALLBACK Callback,
[param: MarshalAs(UnmanagedType.SysInt), In] IntPtr CallbackContext,
[param: MarshalAs(UnmanagedType.SysInt), Out] out IntPtr pEventHandle,
[param: MarshalAs(UnmanagedType.U4), In] UInt32 Flags);
private Int32 GetDevicePath(Guid Service, out String Path)
{
Path = "";
Int32 Result = wclBluetoothErrors.WCL_E_BLUETOOTH_DEVICE_NOT_FOUND;
IntPtr DevInfo = Setup.SetupDiGetClassDevs(Service, null, IntPtr.Zero, Setup.DIGCF_PRESENT | Setup.DIGCF_DEVICEINTERFACE);
if (DevInfo == Common.INVALID_HANDLE_VALUE)
{
Result = DecodeError((UInt32)Marshal.GetLastWin32Error());
if (Result == wclBluetoothErrors.WCL_E_BLUETOOTH_ACCESS_DENIED)
return wclBluetoothErrors.WCL_E_BLUETOOTH_LE_ACCESS_DENIED;
return Result;
}
String AddrStr = Address.ToString("X12").ToLower();
UInt32 Ndx = 0;
while (true)
{
Setup.SP_DEVICE_INTERFACE_DATA Data = new Setup.SP_DEVICE_INTERFACE_DATA();
Data.cbSize = (UInt32)Marshal.SizeOf(typeof(Setup.SP_DEVICE_INTERFACE_DATA));
if (!Setup.SetupDiEnumDeviceInterfaces(DevInfo, IntPtr.Zero, Service, Ndx, ref Data))
break;
UInt32 DetailsSize;
IntPtr Details;
Setup.SetupDiGetDeviceInterfaceDetail(DevInfo, ref Data, IntPtr.Zero, 0, out DetailsSize, IntPtr.Zero);
if (Marshal.GetLastWin32Error() == Common.ERROR_INSUFFICIENT_BUFFER)
{
Details = wclHelpers.AllocHGlobal((Int32)DetailsSize); // PSP_DEVICE_INTERFACE_DETAIL_DATA
if (Details != IntPtr.Zero)
{
Int32 StructSize = 6;
if (IntPtr.Size == 8)
StructSize = 8; // On 64 bit structure has 8 bytes size.
Marshal.WriteInt32(Details, StructSize);
Boolean Res = Setup.SetupDiGetDeviceInterfaceDetail(DevInfo, ref Data, Details, DetailsSize, out DetailsSize, IntPtr.Zero);
if (Res)
{
IntPtr p = wclHelpers.IncPtr(Details, 4); // String always has 4 bytes offset.
String DevPath = Marshal.PtrToStringAuto(p);
DevPath = DevPath.ToLower();
if (DevPath.IndexOf(AddrStr) >= 0)
{
Path = DevPath;
Result = wclErrors.WCL_E_SUCCESS;
}
}
Marshal.FreeHGlobal(Details);
}
}
if (Path != "")
break;
Ndx++;
}
Setup.SetupDiDestroyDeviceInfoList(DevInfo);
return Result;
}
private Int32 OpenServiceHandle(wclGattOperationFlag Flag, UInt16 SvcHdl, wclGattService Service, out IntPtr DevHdl)
{
DevHdl = Common.INVALID_HANDLE_VALUE;
Guid Guid;
if (Services[i].Uuid.IsShortUuid)
Guid = new Guid("{0000" + Services.Uuid.ShortUuid.ToString("X4") + "-0000-1000-8000-00805F9B34FB}");
else
Guid = Services.Uuid.LongUuid;
String Path;
Result = GetDevicePath(Guid, out Path);
if (Result != wclErrors.WCL_E_SUCCESS)
return Result;
// First try to open with read-write access.
DevHdl = Io.CreateFile(Path, Io.GENERIC_READ | Io.GENERIC_WRITE, Io.FILE_SHARE_READ | Io.FILE_SHARE_WRITE, IntPtr.Zero, Io.OPEN_EXISTING, Io.FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
// If failed, try to open with only read access.
if (DevHdl == Common.INVALID_HANDLE_VALUE)
DevHdl = Io.CreateFile(Path, Io.GENERIC_READ, Io.FILE_SHARE_READ, IntPtr.Zero, Io.OPEN_EXISTING, Io.FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
// If failed again - return error.
if (DevHdl == Common.INVALID_HANDLE_VALUE)
return DecodeAttErrorCode(wclHelpers.HResultFromWin32(Marshal.GetLastWin32Error()));
return wclErrors.WCL_E_SUCCESS;
}
protected override Int32 HalSubscribe(wclGattCharacteristic Characteristic, out IntPtr Hdl)
{
Hdl = IntPtr.Zero;
IntPtr Dev;
Int32 Result = OpenServiceHandle(wclGattOperationFlag.goNone, Characteristic.ServiceHandle, out Dev);
if (Result != wclErrors.WCL_E_SUCCESS)
return Result;
BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION Reg = new BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION();
Reg.NumCharacteristics = 1;
Reg.Characteristics = new BTH_LE_GATT_CHARACTERISTIC[1];
Reg.Characteristics[0].ServiceHandle = Characteristic.ServiceHandle;
Reg.Characteristics[0].AttributeHandle = Characteristic.Handle;
Reg.Characteristics[0].CharacteristicValueHandle = Characteristic.ValueHandle;
// On Windows 10 we need only handles. On Windows 8 and 8.1 we need all characteristic properties.
Reg.Characteristics[0].CharacteristicUuid = UuidToGattUuid(Characteristic.Uuid);
Reg.Characteristics[0].IsBroadcastable = Characteristic.IsBroadcastable;
Reg.Characteristics[0].IsReadable = Characteristic.IsReadable;
Reg.Characteristics[0].IsWritable = Characteristic.IsWritable;
Reg.Characteristics[0].IsWritableWithoutResponse = Characteristic.IsWritableWithoutResponse;
Reg.Characteristics[0].IsSignedWritable = Characteristic.IsSignedWritable;
Reg.Characteristics[0].IsNotifiable = Characteristic.IsNotifiable;
Reg.Characteristics[0].IsIndicatable = Characteristic.IsIndicatable;
Reg.Characteristics[0].HasExtendedProperties = Characteristic.HasExtendedProperties;
if (FCb == null)
FCb = new PFNBLUETOOTH_GATT_EVENT_CALLBACK(GattCharChangeCallback);
if (FCharChangeHdl == IntPtr.Zero)
FCharChangeHdl = (IntPtr)GCHandle.Alloc(Receiver);
IntPtr TmpHdl;
return DecodeAttErrorCode(BluetoothGATTRegisterEvent(Dev, BTH_LE_GATT_EVENT_TYPE.CharacteristicValueChangedEvent, ref Reg, FCb, FCharChangeHdl, out TmpHdl, BLUETOOTH_GATT_FLAG_NONE));
}