I have been struggling with random EXCEPTION_ACCESS_VIOLATIONS as I tried to create a c++ plugin that connects to (and subscribe to be notified by) BLE peripherals in win64. Every time I encountered one of these, I tried to resolve by changing my implementation including using 3rd party (BLE) libraries and converting my standard c++ classes and pointers to UC lass and TSharedPtr, etc. However, each time after I made changes, a new EXCEPTION_ACCESS_VIOLATION pops up at different places; sometimes they occur in places that worked fine in my previous iterations. This time, I am getting the above mentioned exception at a completely unexpected locations – break; statement inside a for loop!! Here’s the stack trace when the engine crashed:
Unhandled exceptions that seems to occurs at "random"or “unexpected” places are generally caused by some undefined behaviors, such as buffer overflows. The symptom you have, each time you change a single line of code the errors happens elsewhere, really make me think of an UB.
What is difficult is that the cause is generally unrelated to where the error seems to appears.
You should proofread your code to be sure you are not writing somewhere you are not expected to (typically, an index is out of bounds). This may OR may not be near the break statement, it really depends.
Maybe enabling the Address Sanitizer could help you hunt for the issue.
BTW, I failed to mention that most, if not all (iirc), of the EXCEPTION_ACCESS_VIOLATION errors occur in loops where I am simply reading values from objects from the 3rd party libraries (dll) I called. I checked the source code, it seems that the objects returned from the API is a wrapper with a std::shared_ptr to the underlying object, would this cause problems with UE garbage collection resulting in random EXCEPTION_ACCESS_VIOLATION errors?
After removing bulk of the code I wrote and leaving only 2-3 calls to the third party library APIs and logging the output from those calls (ie no data manipulation in my project) I am still getting EXCEPTION_ACCESS_VIOLATION errors. These errors typically occur when looping collection objects returned from the library API calls (ie error on the for (auto& someObj : somObjCollection) statement after 1 or 2 loops) as well as accessing those objects’ properties.
I suspect many of the strange and seemingly random EXCEPTION_ACCESS_VIOLATION errors are caused by the 3rd party library’s use of std::this_thread::sleep_for and std::shared_ptr in the “DAO” object graph returned from the API calls. I also tried to utilize the API’s callback hooks to create my own simplified data structs via lambda functions; unfortunately, this also failed with more EXCEPTION_ACCESS_VIOLATION.
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.
As a last resort, maybe you could use a separate program which uses the 3rd party library, and have it communicate with UE using an IPC. This is a bit heavyweight, but sometimes this can be helpful.
Indeed, the thought of using MQTT as a mediator has crossed my mind… I have become fairly familiar with the underlying windows SDK calls used by the 3rd party library thanks to all the trouble shooting so I am currently pursuing the option of using the windows SDK directly. I still need to learn how multithreading works in UE due to UE reversing/blocking RoInitialize(RO_INIT_MULTITHREADED) attempts but I think it will yield a better result with less lag (reading data from BLE peripherals) in the long run. If I have more time later on, I may work with the creator of the 3rd party lib or fork the code to add UE adaptation.