I just ran the side by side tests and the console version (using the same DLL) ran without any error while the “UE” version resulted in EXCEPTION_ACCESS_VIOLATION. Here’s a snippet of the code from both versions:
UE version:
try {
if (!getAdapter())
return false;
adapter->set_callback_on_scan_found([this](SimpleBLE::Peripheral peripheral) {
if (!peripheral.is_connectable())
return;
FString peripheralIdentifier(peripheral.identifier().c_str());
FString peripheralMAC(peripheral.address().c_str());
if (peripheralIdentifier.StartsWith("CAD-BLE") || peripheralIdentifier.StartsWith("SPD-BLE")) {
CSCPeripheralsMap.Emplace(peripheralMAC, &peripheral);
}
});
UE_LOG(LogTemp, Display, TEXT("Scanning for BLE peripherals."));
adapter->scan_for(10000); //Scan for 10 seconds.
//adapter->scan_start();
//FPlatformProcess::Sleep(10);
//adapter->scan_stop();
UE_LOG(LogTemp, Display, TEXT("Found %d CSC BLE peripherals: "), CSCPeripheralsMap.Num()); //throws EXCEPTION_ACCESS_VIOLATION 5 seconds after adapter->scan_for(10000) statement is executed; 5 seconds short of the scan duration
//if (CSCPeripherals.size() == 0)
if (CSCPeripheralsMap.Num() == 0)
return false;
for (auto& Elm : CSCPeripheralsMap) {
...
Console version:
try {
if (!getAdapter())
return false;
adapter->set_callback_on_scan_found([this](SimpleBLE::Peripheral peripheral) {
if (!peripheral.is_connectable())
return;
std::string peripheralIdentifier = peripheral.identifier();
std::string peripheralMAC = peripheral.address();
if (peripheralIdentifier._Starts_with("CAD-BLE") || peripheralIdentifier._Starts_with("SPD-BLE")) {
CSCPeripheralsMap.emplace(peripheral.address(), &peripheral);
}
});
printf("Scanning for BLE peripherals...\n");
adapter->scan_for(10000);
//adapter->scan_start();
//FPlatformProcess::Sleep(10);
//adapter->scan_stop();
printf("Found %d CSC BLE peripherals: \n", CSCPeripheralsMap.size());
if (CSCPeripheralsMap.size() == 0)
return false;
for (auto& [key, value] : CSCPeripheralsMap) {
...
The adapter->scan_for(10000)
function in the 3rd party library calls an async “start scan” function, then proceeds to std::this_thread::sleep_for()
for the specified duration then finally calls the “stop scan” function. For this to work, windows runtime needs to be running in multi-threaded mode which the DLL handles internally by calling RoInitialize(RO_INIT_MULTITHREADED)
. I have posted in another thread where UE5 seems to be blocking any attempts at switching Windows runtime to multi-threaded mode via RoInitialize
, hence I am not surprised this didn’t work.
I could probably resolve the error by
- making sure the TMap is thread-safe (this is a good practice which I will implement) and
- use delegates or mutex to ensure the scan has indeed stopped before checking the TMap.Num(),
- use UE’s FPlatformProcess::Sleep() and calling the start scan and stop scan functions in AsyncTasks
- other “hand holding”?
However, such exercise, IMO, completely negates the advantage of using a 3rd party library because it requires me to have in-depth knowledge of the 3rd party library’s inner workings and this is just the tip of the iceberg (e.g. I have not even shared tests I ran where I suspect data retrieved from the 3rd party library is getting garbage collected by UE prematurely due to the use of std::shared_ptr internally)… TBF, this library was not created for use in UE so the author(s) could not have anticipated these problems.