Agamnentzar / bluetooth-serial-port

multi-platform bluetooth serial port library for C++
Other
89 stars 27 forks source link

Bugfix/mac device search #14

Closed TobiSchluter closed 4 years ago

TobiSchluter commented 4 years ago

Thanks for responding so quickly.

This patch is a bit more involved. DeviceInquiry didn't work on the Mac outside of GUI applications, in particular for the bundled example. Before I describe the fix, let me quickly apologize that I included the big whitespace change, so you will have to look at the commits separately to actually see the change. Unfortunately, the file was a fairly random mix of tabs and spaces, which confused my editor, leading to some unrelated indentation changes already creeping into the main commit. In order to prevent this from happening again, I harmonized the indentation to use the sane solution (spaces, as if anybody would doubt that :)), but this of course messes up the pull request.

Anyway, the problem is that IOBluetoothDeviceInquire has an undocumented requirement: it relies on the RunLoop of the main thread. For a person unexperienced with the Mac this means as much as that the bluetooth search uses the OS's event handling in a way that makes assumptions about the application that uses it. The original version of this code was bundled with node.js where those assumptions are fulfilled, but since this library can't make those assumptions, another solution had to be found. The only solution that is guaranteed to function always and not interfere with whatever the application is doing, is to use a separate executable.

This comes with a new problem: how to find that executable? While MacOS allows installing and defining so-called XPC services this doesn't really solve the problem, as then the library would have to set that up somewhere, find it and whatnot. Instead, I made it a requirement that the helper application be in the same directory as the library and / or the resulting binary. Not ideal, but since this is open source the user can adapt this to their needs.

Short version: with this change, device inquiry works on the Mac, but it means the user may have to tweak their build system.

TobiSchluter commented 4 years ago

ps I forgot to say this: I exchange the data between the helper executable and the library in json format and for this I added the nlohmann::json library. An alternative would be to use the built-in NSJSON class and serializers from ObjectiveC, but I really didn't want to learn more ObjectiveC than I already did in the past few days.

Agamnentzar commented 4 years ago

Would it be possible to just check if the event loop is running and, if not, start one for the duration of the inquiry and then shut it down after it's done ?

TobiSchluter commented 4 years ago

I'm not very experienced with the Mac, but let me explain how I understand things: each thread has an associated RunLoop. This RunLoop is executed when CFRunLoopRun() is called within that thread. I.e., each thread has complete control of when it is executed. In particular it can queue up things that will be done during the RunLoop and it can delay the RunLoop as much as it wants if it has more urgent stuff to do.

Now imagine that I were to go ahead, and (assuming there is a way to do this) somehow entered into the main thread and executed its RunLoop. Then the main thread would lose control of when things happen, which could of course break things. If one could use an arbitrary thread, the solution would be simple enough: start my own thread, use its RunLoop. But since one has to use a specific, special thread, I don't believe there's a sane solution to drive the RunLoop from the library in a way that makes no assumptions about the underlying application. Sometimes of course you can make assumptions: the original code was executed in node.js where the main thread is guaranteed to drive its RunLoop, so the problem didn't exist. When you cannot make assumptions about the application, I don't think there's a way to ensure that everything behaves in a defined way, unless you define it yourself, i.e. use a separate process.

I think this is a bug in the Mac API, and I found this solution by trial and error. So it's not impossible that I'm mistaken. How does one find out if one's wrong? One makes a claim on the internet and five minutes later someone corrects it. I tried that approach here: https://stackoverflow.com/questions/1723337/listing-bluetooth-devices-natively-in-objective-c/ So far no one has told me that I'm wrong :)

TobiSchluter commented 4 years ago

Thanks!