Java Bindings for Anjay
We’ve been receiving requests about using Anjay on Android for quite some time now, so we’ve decided to do something with it and write Java bindings for Anjay. They’re now available in Anjay-java GitHub repository repository and in Maven Central. Below you can read about difficulties we’ve encountered doing this task.
Java platform includes a foreign function interface called Java Native Interface (JNI). However, it's often considered to be too complicated to use, so many libraries intended to simplify calling native code have been created, most notably Java Native Access (JNA), which was used by our first integration. It requires only to write Java code calling functions from native library, but because of that, it’s hard to notice if the API of the native library has been changed. As the consequence, this makes such code hard to maintain and requires extensive testing just to ensure that functions are properly called. Because of that, we decided to use JNI, which requires writing additional code between Java and the native library, but, on the other hand, allows us to detect immediately that the native library’s API has changed, as the additional code must be compiled.
However, using plain JNI isn’t easy, as there is a lot of places, where bugs may appear, for example:
DeleteLocalRefneeds to be called manually to avoid memory leaks.
- Calling Java methods is error-prone, because we need to write the method signature as string, for example
“(I)V”for the method taking int as an argument and returning void.
Fortunately, we’ve found jni.hpp library, which wraps JNI in an interface written in C++, which simplifies development process by:
- Providing automated memory management (but it’s still possible to do dangerous things, like keeping
jni::Local<>outside of the scope of the function called from Java) underneath.
- Supporting both throwing exceptions and handling exceptions thrown by Java methods. In many cases, an exception might be thrown by a Java method called from C code that has been initially called from Java, and it is especially important to always catch it properly. The fact that Anjay is written in C and does not expect callback functions to throw C++ exceptions calls for additional carefulness.
Obviously, nothing is perfect, and the biggest problem with using jni.hpp is the documentation, which basically doesn’t exist, so while writing the integration layer, we were relying on examples and tests. Some cases were not covered by these – for example, exporting native static methods to Java, which also proved to be rather unfortunate, as it uses raw JNI
jclass type directly instead of jni.hpp wrapper.
We also found a bug in it, which leads to invalid passing of integers to Java methods expecting arguments of type long. This is noticeable on 32-bit architectures and the problem is probably related to the 4-bytes stack alignment, but it can be easily avoided by casting the values to the proper type before calling Java methods.
Another thing we have discovered is that Android 10’s JVM doesn’t allow access to fields or methods of classes, which don’t belong to the public API. For example, it’s impossible to access the constructor of DatagramChannel. As a consequence, we could not operate on raw file descriptors in the native code, as the corresponding Java socket couldn’t be created, and we needed to implement avs_net methods, which call Java methods to perform all of the operations on sockets.
We also had a problem with setting a timeout on the socket.
DatagramSocket underneath and it’s possible to get a
DatagramSocket object using its public API. However, timeout set on a
DatagramSocket isn’t respected in a call to
DatagramChannel if it’s in the blocking mode. For this reason, we had to use Selector to be able to receive and send packets with timeout.
Another problem we’ve encountered was related to locale. We use
strtod in Anjay to convert floating point numbers from their string representation.
Unfortunately, its behavior depends on the currently set locale and the decimal separator is dot or comma. By default JVM sets system’s locale, so when we have Polish locale in system, comma is used as the decimal separator, which isn’t compliant with the LwM2M specification. To avoid this problem, it was required to call setlocale at the beginning of the native library.
Although developing these bindings was not easy, it was definitely worth the effort to make the development of LwM2M clients using Anjay on Android easier.