How to make friends Java and C ++. Part one

    Hello.

    As you probably already guessed, we will talk about JNI. For those who do not know what it is, I explain: JNI (or java native interface) is such a thing that allows you to make native code calls from a java machine and vice versa.

    Why might this be required? There are several reasons: the need to use code that has already been written for the native platform, the need to implement something that cannot be done with a single JVM (for example, working with some specific pieces of iron), well, and accelerating the execution of critical pieces of code (though , this is a very controversial point).

    How JNI Works

    Suppose we have some kind of java class from which we need to call a method written in c ++ and located in a dynamically linked library (for example, in windows it will be a dll). What should we do for this?

    First, we declare a method of some class as native. This will mean that the JVM, when calling this method, will transfer control to the native code.

    Then, we need to load the native library. To do this, you can call System.loadLibrary(String), which takes as a parameter the name of the library. After this call, the library will be loaded into the JVM address space.

    Now, suppose we have the following java class:
    package my.mega.pack;
     
    public class NativeCallsClass
    {
        static
        {
           System.loadLibrary ("megalib");
        }
     
        native public static void printOne ();
        native public static void printTwo ();
    }
    Here, for convenience, we moved loadLibrary()to the static region of the class.

    Suppose now that we call NativeCallsClass.printOne(). Then JVM will look in the libraries method with the following name: Java_my_mega_pack_NativeCallsClass_printOne(...).

    Declaring JNI Functions in C ++

    We wrote a class in java that has methods marked as native. Now we need to create headers with declarations of the C ++ functions that we want to call.

    Of course, you can write them manually. But there is a more convenient method:

    javac -d bin / src / my / mega / pack / NativeCallsClass.java
    cd bin
    javah my.mega.pack.NativeCallsClass
    

    We compile the class, and then use the javah utility. After that, we will have a file called my_mega_pack_NativeCallsClass.h. This is our header. It looks something like this:
    / * DO NOT EDIT THIS FILE - it is machine generated * /
    #include 
    / * Class for my_mega_pack_NativeCallsClass Header * /
     
    #ifndef _Included_my_mega_pack_NativeCallsClass
    #define _Included_my_mega_pack_NativeCallsClass
    #ifdef __cplusplus
    extern "C" {
    #endif
    / *
     * Class: my_mega_pack_NativeCallsClass
     * Method: printOne
     * Signature: () V
     * /
    JNIEXPORT void JNICALL Java_my_mega_pack_NativeCallsClass_printOne
      (JNIEnv *, jclass);
     
    / *
     * Class: my_mega_pack_NativeCallsClass
     * Method: printTwo
     * Signature: () V
     * /
    JNIEXPORT void JNICALL Java_my_mega_pack_NativeCallsClass_printTwo
      (JNIEnv *, jclass);
     
    #ifdef __cplusplus
    }
    #endif
    #endif
    The most important thing here is the signatures of 2 functions: Java_my_mega_pack_NativeCallsClass_printOne(JNIEnv *env, jclass myclass)and Java_my_mega_pack_NativeCallsClass_printTwo(JNIEnv *env, jclass myclass).

    We need to realize them. First, let's figure out their signatures. env is an interface to a virtual machine. All operations with the JVM are performed using it. Later we will analyze this in more detail. myclass is the identifier of a java class that has a native method identified with this function, that is, in our case, this NativeCallsClass. Note that jclass is passed as the second parameter when the method is declared as static. If it were a regular method, then we would be given a jobject that would identify the object whose method we called (in fact, this is an analog of this).

    We can only implement these functions:
        #include 
        #include "my_mega_pack_NativeCallsClass.h"
     
     
        JNIEXPORT void JNICALL Java_my_mega_pack_NativeCallsClass_printOne (JNIEnv * env, jclass myclass)
        {
            std :: cout << "One" << std :: endl;
        }
     
        JNIEXPORT void JNICALL Java_my_mega_pack_NativeCallsClass_printTwo (JNIEnv * env, jclass myclass)
        {
            std :: cout << "Two" << std :: endl;
        }

    We transfer data to the native code and vice versa

    Let's now implement more complex behavior. Let us have 2 methods: inputInt and outputInt. One of them will read the number from the console, and the second will output. Our java class will look like this:
    package my.mega.pack;
     
    public class NativeCallsClass
    {
        static
        {
            System.loadLibrary ("megalib");
        }
     
        native public static int inputInt ();
        native public static void outputInt (int v);
    }
    We launch javah and see that the method signatures have changed a bit. Now they are:
    JNICALL Java_my_mega_pack_NativeCallsClass_inputInt (JNIEnv *, jclass);
    JNIEXPORT void JNICALL Java_my_mega_pack_NativeCallsClass_outputInt (JNIEnv *, jclass, jint);
    jint is a typedef. In fact, it denotes some primitive type (for example, int), which corresponds to int in java. As you can see, the task was not much more complicated than the previous one :) Our functions will look like this:
    #include 
    #include "my_mega_pack_NativeCallsClass.h"
     
    JNIEXPORT jint JNICALL Java_my_mega_pack_NativeCallsClass_inputInt (JNIEnv * env, jclass myclass)
    {
        int ret;
     
        std :: cin >> ret;
     
        return ret;
    }
     
    JNIEXPORT void JNICALL Java_my_mega_pack_NativeCallsClass_outputInt (JNIEnv * env, jclass myclass, jint v)
    {
        std :: cout << v << std :: endl;
    }

    To summarize

    So, in the first part, we looked at how JNI works, how to write java classes, with which you can make native calls when and how to write C ++ functions called through JNI. In the next part (or parts), we will consider interacting with the JVM from C ++ code, working with classes, objects, fields and methods, creating proxy java classes that would represent C ++ classes and launching the JVM from C ++ code.

    Naturally, the continuation will be only if it is interesting to someone :)


    Also popular now: