Tuesday, 11 February 2020

C++Builder's Android location sensor woes

There is an unfortunate problem with RAD Studio 10.3.3 for anyone using C++Builder building Android applications. However it has a quite straightforward resolution, so let's look at what the issue is.

If you make a new Multi-Device Application in C++Builder and build it, all will be well, of course. The basics work very nicely.

If you now drop a TLocationSensor component on the form and try to build now, the result will be different. Compilation goes fine. Linking looks like it is going fine.... until it fails with a rather unhelpful message:

[ldandroid Error] "ld" exited with code 1.

Let's look at how we can get more useful feedback from this rather opaque and somewhat 1990s error message and see how to resolve the underlying problem.

If you examine the Message window closely you will see immediately above that error message there is an expandable item that says:

> ldandroid command line

No need to expand it, just right-click on that entry and choose Copy. Paste that into a text editor (preferably something better than Notepad). Depending on the editor and the options enabled within it you'll be looking at something approximately like this:


You'll notice that we have a command-line broken into multiple shorter lines with a prefix line that is not part of the command-line proper. You'll need to delete the top line, then conjoin all the subsequent lines into one long command line, leaving you with a single long line, which when wrapped in my same editor looks like this:


Now launch a RAD Studio Command Prompt (hit the Windows Start button and start typing in RAD and it should be offered to you as an option).

Use the CD /D command to change to the drive and folder in which your project resides

Go back to the long command-line in your editor and copy it into the clipboard and then paste it into the command prompt window.


Pressing  Enter. will run the same command outside the IDE giving you a more representative set of error messages that the IDE hasn't managed to parse and list for you.

Because of the long lines, another screenshot of the resultant command window isn't the best way to read the errors. Instead I have redirected the error output (the linker's stderr output) to a text file so I can paste it here making it easier to read. stderr can be redirected to file by adding this to the end of the command line:

2> a.txt

The content of the file is 9 errors. Each error consists of a line highlighting the problem module and a brief error message, and a line with a fuller message. The problem module in each case (so I don't need to repetitively include it) is:

c:\program files (x86)\embarcadero\studio\20.0\lib\Android\debug\librtl.a(Androidapi.Sensor.o)

The rest of the error message text looks like this:

In function `Androidapi::Sensor::ASensorManager_getInstanceForPackage(char const*)':
Androidapi.Sensor:(.text._ZN10Androidapi6Sensor36ASensorManager_getInstanceForPackageEPKc[_ZN10Androidapi6Sensor36ASensorManager_getInstanceForPackageEPKc]+0x4): undefined reference to `ASensorManager_getInstanceForPackage'
In function `Androidapi::Sensor::ASensorManager_getDefaultSensorEx(ASensorManager*, int, bool)':
Androidapi.Sensor:(.text._ZN10Androidapi6Sensor33ASensorManager_getDefaultSensorExEP14ASensorManagerib[_ZN10Androidapi6Sensor33ASensorManager_getDefaultSensorExEP14ASensorManagerib]+0x4): undefined reference to `ASensorManager_getDefaultSensorEx'
In function `Androidapi::Sensor::ASensorManager_createSharedMemoryDirectChannel(ASensorManager*, int, unsigned int)':
Androidapi.Sensor:(.text._ZN10Androidapi6Sensor46ASensorManager_createSharedMemoryDirectChannelEP14ASensorManagerij[_ZN10Androidapi6Sensor46ASensorManager_createSharedMemoryDirectChannelEP14ASensorManagerij]+0x4): undefined reference to `ASensorManager_createSharedMemoryDirectChannel'
In function `Androidapi::Sensor::ASensorManager_createHardwareBufferDirectChannel(ASensorManager*, Androidapi::Sensor::AHardwareBuffer_*, unsigned int)':
Androidapi.Sensor:(.text._ZN10Androidapi6Sensor48ASensorManager_createHardwareBufferDirectChannelEP14ASensorManagerPNS0_16AHardwareBuffer_Ej[_ZN10Androidapi6Sensor48ASensorManager_createHardwareBufferDirectChannelEP14ASensorManagerPNS0_16AHardwareBuffer_Ej]+0x4): undefined reference to `ASensorManager_createHardwareBufferDirectChannel'
In function `Androidapi::Sensor::ASensorManager_destroyDirectChannel(ASensorManager*, int)':
Androidapi.Sensor:(.text._ZN10Androidapi6Sensor35ASensorManager_destroyDirectChannelEP14ASensorManageri[_ZN10Androidapi6Sensor35ASensorManager_destroyDirectChannelEP14ASensorManageri]+0x4): undefined reference to `ASensorManager_destroyDirectChannel'
In function `Androidapi::Sensor::ASensorManager_configureDirectReport(ASensorManager*, ASensor*, int, int)':
Androidapi.Sensor:(.text._ZN10Androidapi6Sensor36ASensorManager_configureDirectReportEP14ASensorManagerP7ASensorii[_ZN10Androidapi6Sensor36ASensorManager_configureDirectReportEP14ASensorManagerP7ASensorii]+0x4): undefined reference to `ASensorManager_configureDirectReport'
In function `Androidapi::Sensor::ASensorEventQueue_registerSensor(ASensorEventQueue*, ASensor*, int, long long)':
Androidapi.Sensor:(.text._ZN10Androidapi6Sensor32ASensorEventQueue_registerSensorEP17ASensorEventQueueP7ASensorix[_ZN10Androidapi6Sensor32ASensorEventQueue_registerSensorEP17ASensorEventQueueP7ASensorix]+0x16): undefined reference to `ASensorEventQueue_registerSensor'
In function `Androidapi::Sensor::ASensor_isDirectChannelTypeSupported(ASensor*, int)':
Androidapi.Sensor:(.text._ZN10Androidapi6Sensor36ASensor_isDirectChannelTypeSupportedEP7ASensori[_ZN10Androidapi6Sensor36ASensor_isDirectChannelTypeSupportedEP7ASensori]+0x4): undefined reference to `ASensor_isDirectChannelTypeSupported'
In function `Androidapi::Sensor::ASensor_getHighestDirectReportRateLevel(ASensor*)':
Androidapi.Sensor:(.text._ZN10Androidapi6Sensor39ASensor_getHighestDirectReportRateLevelEP7ASensor[_ZN10Androidapi6Sensor39ASensor_getHighestDirectReportRateLevelEP7ASensor]+0x4): undefined reference to `ASensor_getHighestDirectReportRateLevel'

What we are looking at here are a bunch of symbols that are defined in the Delphi RTL file Androidapi.Sensor.pas as symbols to be imported from the Android library libandroid.so, but which are not actually present in the one being linked to from the NDK platform folder specified in your SDK definition, which is likely to be 22.

Huh?

OK, the important thing here is what we can do about the problem. The answer is that you need to take a copy of the files Androidapi.Sensor.pas and Androidapi.inc from C:\Program Files (x86)\Embarcadero\Studio\20.0\source\rtl\android\ and put them in your project folder (or somewhere you can reference).

Add the file to your project using the Project Manager's context menu (remembering to choose Pascal unit (*.pas) from the list of file type filters in order to see the Delphi unit.

Now open the unit in the editor and hunt down these 14 symbols. For each of them comment out the entirety of the definition (select the whole definition and press Ctrl+/):

  • ASensorManager_getInstanceForPackage
  • ASensorManager_getDefaultSensorEx
  • ASensorManager_createSharedMemoryDirectChannel
  • ASensorManager_createHardwareBufferDirectChannel
  • ASensorManager_destroyDirectChannel
  • ASensorManager_configureDirectReport
  • ASensorEventQueue_registerSensor
  • ASensor_isDirectChannelTypeSupported
  • ASensor_getHighestDirectReportRateLevel

You'll notice that these APIs are pretty much all commented as new in API 26 (though one seems to be missed, ASensorEventQueue_registerSensor). It is this fact that causes the issue. Your application is linking against the API 22 library, which does not expose these symbols and so they should not be present in the unit.

You should find that you can now rebuild your project successfully.

No comments:

Post a comment