PS_Win_BLE : fix crash shutdown + cache caractéristiques GATT

Crash shutdown (Index >= 0) :
- OnPreExit se déclenche encore trop tard (GC déjà en cours).
- Remplacé par OnEnginePreExit qui fire avant le shutdown des core
  modules, donc avant toute destruction d'UObject. RemoveFromRoot()
  est maintenant appelé au bon moment.

Cache caractéristiques GATT :
- Bug : Read/Write/Subscribe rappelaient GetCharacteristicsAsync(Cached)
  à chaque opération, ce qui peut retourner un ordre différent du
  discovery initial (Uncached) → mauvaise caractéristique ciblée.
- Fix : les GattCharacteristic sont maintenant stockées dans
  FPS_GattServiceHandle::Characteristics (std::vector) lors du
  ConnectDevice, et réutilisées directement via [CI] dans toutes
  les opérations ultérieures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
j.foucher 2026-02-18 19:28:05 +01:00
parent d53dd36194
commit 7c2cb387f2

View File

@ -65,6 +65,8 @@ struct FPS_BLEDeviceHandle
struct FPS_GattServiceHandle struct FPS_GattServiceHandle
{ {
winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::GattDeviceService Service{ nullptr }; winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::GattDeviceService Service{ nullptr };
// Characteristics cached at discovery time (same order as ActiveServices[SI].Characteristics)
std::vector<winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::GattCharacteristic> Characteristics;
}; };
#endif // PLATFORM_WINDOWS #endif // PLATFORM_WINDOWS
@ -95,9 +97,9 @@ void UPS_BLE_Module::StartupModule()
LocalBLEManager->AttachModule(this); LocalBLEManager->AttachModule(this);
// Cleanup UObjects before Unreal's UObject array is destroyed. // Cleanup UObjects before Unreal's UObject array is destroyed.
// ShutdownModule() is called too late (UObjectArray already partially torn down) // Both ShutdownModule() and OnPreExit fire too late (UObjectArray already
// → we do RemoveFromRoot here, during pre-exit, which is still safe. // partially torn down). OnEnginePreExit fires earlier, before GC teardown.
FCoreDelegates::OnPreExit.AddLambda([this]() FCoreDelegates::OnEnginePreExit.AddLambda([this]()
{ {
if (LocalBLEManager) if (LocalBLEManager)
{ {
@ -390,6 +392,12 @@ bool UPS_BLE_Module::ConnectDevice(UPS_BLE_Device* Device)
for (uint32_t ci = 0; ci < Chars.Size(); ci++) for (uint32_t ci = 0; ci < Chars.Size(); ci++)
{ {
auto Ch = Chars.GetAt(ci); auto Ch = Chars.GetAt(ci);
// Cache the characteristic object so all subsequent operations
// (Read / Write / Subscribe) use the exact same instance and index
// as what was discovered here — no re-fetch needed.
SvcHandle->Characteristics.push_back(Ch);
FPS_CharacteristicItem ChItem; FPS_CharacteristicItem ChItem;
GUID cg = Ch.Uuid(); GUID cg = Ch.Uuid();
ChItem.CharacteristicUUID = UPS_BLE_Device::GUIDToString(&cg); ChItem.CharacteristicUUID = UPS_BLE_Device::GUIDToString(&cg);
@ -481,11 +489,9 @@ bool UPS_BLE_Module::ReadCharacteristic(UPS_BLE_Device* Device, uint8 SI, uint8
PS_BLE_WINRT_NS PS_BLE_WINRT_NS
try try
{ {
auto* SvcH = static_cast<FPS_GattServiceHandle*>(Device->NativeGattServices[SI]); auto* SvcH = static_cast<FPS_GattServiceHandle*>(Device->NativeGattServices[SI]);
auto Chars = SvcH->Service.GetCharacteristicsAsync(WinBT::BluetoothCacheMode::Cached).get(); if (CI >= SvcH->Characteristics.size()) return;
if (Chars.Status() != WinGAP::GattCommunicationStatus::Success) return; auto Ch = SvcH->Characteristics[CI];
auto Ch = Chars.Characteristics().GetAt(CI);
auto Result = Ch.ReadValueAsync(WinBT::BluetoothCacheMode::Uncached).get(); auto Result = Ch.ReadValueAsync(WinBT::BluetoothCacheMode::Uncached).get();
EPS_GATTStatus Status = (Result.Status() == WinGAP::GattCommunicationStatus::Success) EPS_GATTStatus Status = (Result.Status() == WinGAP::GattCommunicationStatus::Success)
@ -520,11 +526,9 @@ bool UPS_BLE_Module::WriteCharacteristic(UPS_BLE_Device* Device, uint8 SI, uint8
PS_BLE_WINRT_NS PS_BLE_WINRT_NS
try try
{ {
auto* SvcH = static_cast<FPS_GattServiceHandle*>(Device->NativeGattServices[SI]); auto* SvcH = static_cast<FPS_GattServiceHandle*>(Device->NativeGattServices[SI]);
auto Chars = SvcH->Service.GetCharacteristicsAsync(WinBT::BluetoothCacheMode::Cached).get(); if (CI >= SvcH->Characteristics.size()) return;
if (Chars.Status() != WinGAP::GattCommunicationStatus::Success) return; auto Ch = SvcH->Characteristics[CI];
auto Ch = Chars.Characteristics().GetAt(CI);
auto Writer = WinStr::DataWriter(); auto Writer = WinStr::DataWriter();
for (uint8 B : DataCopy) Writer.WriteByte(B); for (uint8 B : DataCopy) Writer.WriteByte(B);
@ -554,11 +558,9 @@ bool UPS_BLE_Module::SubscribeCharacteristic(UPS_BLE_Device* Device, uint8 SI, u
PS_BLE_WINRT_NS PS_BLE_WINRT_NS
try try
{ {
auto* SvcH = static_cast<FPS_GattServiceHandle*>(Device->NativeGattServices[SI]); auto* SvcH = static_cast<FPS_GattServiceHandle*>(Device->NativeGattServices[SI]);
auto Chars = SvcH->Service.GetCharacteristicsAsync(WinBT::BluetoothCacheMode::Cached).get(); if (CI >= SvcH->Characteristics.size()) return;
if (Chars.Status() != WinGAP::GattCommunicationStatus::Success) return; auto Ch = SvcH->Characteristics[CI];
auto Ch = Chars.Characteristics().GetAt(CI);
auto WriteStatus = Ch.WriteClientCharacteristicConfigurationDescriptorAsync( auto WriteStatus = Ch.WriteClientCharacteristicConfigurationDescriptorAsync(
WinGAP::GattClientCharacteristicConfigurationDescriptorValue::Notify).get(); WinGAP::GattClientCharacteristicConfigurationDescriptorValue::Notify).get();
@ -604,11 +606,9 @@ bool UPS_BLE_Module::UnsubscribeCharacteristic(UPS_BLE_Device* Device, uint8 SI,
PS_BLE_WINRT_NS PS_BLE_WINRT_NS
try try
{ {
auto* SvcH = static_cast<FPS_GattServiceHandle*>(Device->NativeGattServices[SI]); auto* SvcH = static_cast<FPS_GattServiceHandle*>(Device->NativeGattServices[SI]);
auto Chars = SvcH->Service.GetCharacteristicsAsync(WinBT::BluetoothCacheMode::Cached).get(); if (CI >= SvcH->Characteristics.size()) return;
if (Chars.Status() != WinGAP::GattCommunicationStatus::Success) return; auto Ch = SvcH->Characteristics[CI];
auto Ch = Chars.Characteristics().GetAt(CI);
uint64 Key = ((uint64)SI << 8) | CI; uint64 Key = ((uint64)SI << 8) | CI;
if (winrt::event_token** TokenPtr = reinterpret_cast<winrt::event_token**>(Device->NotifyTokens.Find(Key))) if (winrt::event_token** TokenPtr = reinterpret_cast<winrt::event_token**>(Device->NotifyTokens.Find(Key)))