Self-binding C ++ classes to JVM via JNI

It so happened that recently at work I needed to port an old native application for Android. The application is written primarily in C / C ++. I wanted to do it competently and in a civilized manner. Actually, under Cat, the

application was written mainly in C and C ++. But in places even assembler was used. Since I myself prefer to always use the object language, there was a desire to bind Java code to a plus library.

But there were difficulties. The officially supported way to bind native code in both the original jvm and Dalvik is JNI, and it is known to be implemented through C-functions. In this connection, I had to twist a little and convert the execution thread twice: from Java objects to a callback and from a code to plus objects.

Of course, I looked towards ready-made solutions, but as a rule, their license did not allow using them in a proprietary product. The task was facilitated by the fact that I did the Java part and the interface for it myself, and therefore I could simplify my life.

In the end, this is what I invented (of course, you can notice the old wheel in this). All Java objects, when binding them to the native, are arranged in a hierarchy, that is, they are inherited from one NativeObject ancestor class. This class carries the basic logic for serving common functions for all bound classes. He is capitalizing on a class with the same name in the plus part. Priority methods for implementation:
  • native long initNative(String klass);
    - returns a pointer to a native object and then saves it in a special field, this is a common approach for many.
  • native void finalizeNative();
    - reports the need to deploy the native object.
  • native void setFields(Object ... params);
    - serves to optimize the work with the native environment.

The latter method is necessary, since inverse methods for accessing the fields of an object are 3 times less productive than a direct call. After that, in each successor class, we implement constructor()(by itself) a static initializer for loading class metadata into the native, it is also mapped.

In C ++, we implement a constructor with a list of parameters pre-negotiated for all descendants, among them it is easiest to immediately pass a link to a Java object. In general, for all the readers we have prepared a template factory function for native objects. We also define it (specialize).

Now the whole process of creating an object proceeds as follows:
  • Java class is loading
  • Static Initializer Called
  • The class metadata is initialized in native mode: permanent links are made to the class itself, the fields we need first, etc.
  • The Java constructor is called, the NativeObject constructor is calling
  • InitNative is called
  • A positive object is instantiated through the factory
  • The pointer to it is saved.

After that, we have a Java object ready for work, its native methods automatically go through the JNI, and there, in turn, we get a pointer to the native object, it is casted and calls the corresponding C ++ method.

This is how binding works in general terms.

Now I’ll tell you a little about other aspects of working with pluses in Java in general and in Android in particular.
In Java, it is customary to send erroneous situations via the exception channel. Therefore, the error handling system style is initially very difficult to combine with this approach. Naturally, native exception support was obvious. Which was done. Unfortunately, the pluses in Android are still very poorly supported. The same exceptions are not supported by the default library. Therefore, I immediately recommend switching to gnu-libstdc ++. If you use GNU tools, then its license is quite suitable for any projects.

So, all Java exceptions in the native are caught and wrapped by the heirs of std :: exception. Similarly, all system errors are wrapped in std :: exception and sent out. Here, on the way to the system calls, their flight is interrupted by the anti-aircraft catch installation and turns into an Java exception. It is worth reminding those who write mostly in Java that plus exceptions must be handled very carefully, otherwise anti-aircraft installations will undermine themselves.

And finally, I wanted to scold Android support, or rather, its absence, for string classes with wide characters. Here I just had to redefine std :: wstring by switching standard headers. After this surgery, there were no problems with wide lines so far.

Well, of course, when using the pros, the management of Java machine resources is greatly simplified, and this is not only memory, but also links to objects - analogues of various handles in different systems. The use of boost to control the lifetime helped a lot: as you know, objects in Java must live until the garbage collector kills them.

Good luck.

Also popular now: