EngEd Community

Section’s Engineering Education (EngEd) Program fosters a community of university students in Computer Science related fields of study to research and share topics that are relevant to engineers in the modern technology landscape. You can find more information and program guidelines in the GitHub repository. If you're currently enrolled in a Computer Science related field of study and are interested in participating in the program, please complete this form .

Java Native Interface

September 29, 2021

The Java Native Interface (JNI) standard is part of the Java platform since Java 1.1. It allows Java code to communicate with code scripted in different languages.

Although the JNI was developed primarily for natively compiled languages, it does not restrict you from utilizing other languages as long as there is proper implementation of calling conventions.

As we all know, one of Java’s key advantages is flexibility, which means that when we create a code compilation, the outcome is a bytecode that is platform agnostic. Simply put, the bytecode can be executed on every system or gadget that can run java programs.

However, there are situations when we have to use natively executable code for a particular infrastructure.

We employ native code for a variety of reasons, including the following:

  • When there is the handling of hardware.
  • Every time a challenging procedure is carried out, its performance must be enhanced.
  • When we wish to reuse a library rather than recreating it in Java.

This tool is what we call the Java Native Interface. JNI is a coded model for contacting and invoking applications outside its scope (packages specific to a platform’s hardware and operating system), and from Java programs in a Java virtual machine.

Also, libraries developed in other languages like C, C++, and assembly, are generated by JVM.

Prerequisites

To follow along, you need to:

  • Have a basic understanding of creating objects using C++.
  • Have a basic knowledge of the Java programming language and how to create classes.
  • Be able to execute the code, use the G++ compiler. For my case, I used oracle terminal online to run the code.

Goal

By the end of this tutorial, you should be able to understand the following concepts:

  • Java Native Interface in general, and elements in Java Native Interface.
  • Implementation of Java native methods using a native language (C++ for our case).
  • Application of Java methods from Native Code using objects.
  • Advantages and limitations of using JNI.

Table of contents

Native methods

The native keyword in Java language specifies that such a procedure is implemented in the foreign code.

During the creation of a foreign or native executable application, we usually have the option of using static or shared libraries:

  • Static libraries - We will include all library binaries as part of our executable during the linking process. As a result, we will no longer require the libs, but our executable file will grow in size.
  • Shared libraries - Only the libraries are referenced in the final executable, not the code itself. The configuration in which our program runs must have accessibility to every file in the libraries which our software uses.

Since a binary file cannot mix bytecode and native code in the same file, the latter makes perfect sense for Java Native Interface.

private native void theNativeMethod();

In the code above, the keyword (native) transforms our method function to a sort of abstract procedure, with the exception that it will be deployed in a distinct, shared foreign package rather than other Java classes being responsible for implementing it.

JNI elements in code (Java)

Elements found in java code:

  • As we have already discussed, a term is known as native, which is any function highlighted as foreign that should be used in an unfamiliar, sharing library.
  • A string library name System.loadLibrary is a constant procedure that links a sharable library into an address within the address of a system, making its exported functionalities accessible to our Java code.
  • Java virtual machine - a framework that allows us to control a current JVM (or even construct one from scratch) by introducing threads, terminating it, and so forth.
  • Java native interface environment (JNIEnv) - a structure having methods for accessing Java objects from a foreign code
  • The java native interface Export (JNIEXPORT) - Identifies a function in a shared library as exportable. It will appear in the method table, and the Java native interface will discover it.
  • Java native interface Call (JNICALL) - guarantees that our techniques are available for the JNI framework when paired with JNIEXPORT.

Java native interface hello world

Let us take a look at how JNI operates. The article will employ C++ as the primary programming language, using G++ as the compiler and linker.

We may choose any compiler we like, but this is what to do to get G++ up and running on Ubuntu operating system, Windows OS, and Mac OS:

  • In Ubuntu (Linux), we will have to run Sudo apt-get to install build-essential code in the terminal.
  • In a Windows operating system, we will install the MinGW.
  • Run a command g++ in the terminal of a macOS, if it is not present, you need to install it first.

Step one: Creating the Java Class

By writing our first Java Native Interface program, we must implement the class “Hello World”. The first thing is to develop a Java class that contains the native methods used to complete the task.

package example.java;
public class HelloJavaJNI {

    static
        {
        System.loadOurLibrary("javanative");
          }

    public static void main(String args[])
           {
       new JavaNative().remarkHi();
    }

    // remark() is used as a native method that gets no argument and returns it void
    protected native void remark();
}

The static library is loaded in the static block, enabling it to be accessible and in any location.

We could also load the package only before using our native function in this tiny script because the native library has no other purpose.

Step two: Implementing our native language method in C++

We can implement our native method in another programming language for it to be in a native case.

In this case, we are going to utilize the C++ language to implement it. In C++, the definition and application will be in the following order .hand, .ccpfile extensions.

To start with, we will use the java compiler’s -h flag to build the method definition.

javac -h. HelloJavaJNI.java

The code above creates a new file named example_java's_HelloJavaJNI.h that holds all the native functions in the class.

JNIEXPORT void JNICALL Java_example_java_HelloJavaJNI.h_remarkHi
  (JNIEnv *, kobject);

The example above shows that the method class name is instantly produced, utilizing the properly approved application, class, and function names.

Also, we can see that we are obtaining two entities supplied to the method: a marker to the existing JNI environment and java instance to which the function is associated, and which is an instance of our HelloJavaJNI class.

For the implementation of our remarkHi method, we must construct a new cpp directory, where we will do things like printing “Welcome Back” to such a console.

JNIEXPORT void JNICALL Java_example_java_HelloJavaJNI.h_remarkHi
  (JNIEnv* enviroment, kobject thisObject)
  {
    std::cout << "Welcome back from C++ language" << std::endl;
}

In the illustration above, we have called .cpp document which will be very much similar to the .h file containing the name.

We shall include the code above to execute the native function.

Step three: Compiling and linking

At this moment, we have the necessary components we require at hand and have a link involving them.

We will have to construct our shared library and run it from the C++ code. To accomplish that, we use the G++ compiler.

Note: JNI headers from the Java JDK should be included in the installation kit.

Java Native Interface headers from the JDK installation are shown in the code below.

g++ -c -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win64 example_java_HelloJavaJNI.cpp -o example_java_HelloJavaJNI.o

When compiled in our platform, we must include the code in a new shared library into the file example java HelloJavaJNI.o. The point of contention or argument supplied into our method is system.loadLibrary.

We named our argument java native, so we will need to load it whenever we run the java code:

g++ -shared -o javanative.dll example_java_HelloJavaJNI.o -W0l,--insert-stdreturn-alias

We can then utilize the command line to run our script.

However, the entire path to the location hosting the library we just produced must be added, telling Java where to find our native libraries, shown below:

java -cpp . -Directoryjava.library.path=/JAVANATIVE_SHARED_LIBRARY_FOLDER example_java_HelloJavaJNI

Once we have shown the path, the console output will be:

Welcome back from the C++ language

How we add attributes to native methods

It is lovely to say hello, but it is not particularly beneficial. In most cases, we want to control data transmission amongst Java and C++ in our software.

Some parameters must be included in the foreign method. We will make another new class named ParameterIllustrationJNI with two foreign methods that uses distinct types of parameters and returns them.

private native short totalIntegers(int one, int two);
protected native String remarkILoveCoding(String names, boolean isMale);

After that, we will need to use javac-h to produce another.h file, just like we did in the previous steps.

After that, we must construct an a.cpp file containing these installations of the new C++ technique:

JNIEXPORT pshort JNICALL Java_example_java_HelloJavaJNI_totalIntegers
  (JNIEnv* enviroment, pobject thisObject, pint one, pint two)
  {
    std::cout << "C++:The digits got are!" << one << " plus " << two << std::endl;
    return (short)one + (short)two;
    }
JNIEXPORT pstring JNICALL Java_example_java_ParameterIllustrationJNI_remarkILoveCoding
  (JNIEnv* environment, pobject thisObject, pstring name, pboolean isMale)
     {
    const char* idenifyCharPointer = enviroment->GetStringUTFChar(names, NULL);
    std::string content;
    if(isMale)
         {
        content = "Sir ";
            }
    else {
        content = "Madam ";
    }

    std::string fullNames = content + nameCharacterPointer;
    return environment->NewStringUTF(fullNames.c_str());
}

Explanation

In our illustration above, we have utilized these methods given through these JNI environment instances. Moreover, we use the pointer *environment of the type JNIEnvironment.

In this scenario, JNIEnv allows us to feed java strings on our C++ function and then back without having concerns about implementation.

Applying Java methods from native code and using objects

Looking at this final illustration, we will explore how to incorporate Java objects within native source codes.

Suppose we begin by making a new class and name it UtilizeInformation; we will store user information:

package example.java;

public class UtilizeInformation
  {

    public String names;
    public float symmetry;

    public String getClientInformation()
     {
        return "[identity]=" + names + ", [weigh]=" + symmetry;
    }
}

The code script above shows how we can use the desired class to store the user’s information.

Next, we will have to create another java class. This time, the class will manage objects of the class used. We will use ObjectIllustrationJNI as our class:

public native UtilizeInformation createUsers(String name, float symmetry);
public native String printUtilizeInformation(UtilizeInformation users);

We have just created a java class that contained a native method or function in the code above. The function manages our objects in the java class we have just created.

In the final step, we will construct the .h header and a C++ implementation on the new .cpp file of our foreign operations:

JNIEXPORT pobject JNICALL Java_example_java_ObjectIllustrationJNI_createUser
  (JNIEnv *environment, pobject thisObject, pstring name, pfloat symmetry)

   {
    //Here we create an object of the class UtilizeInformation
    pclass utilizeInformationClass = environment->FindClass("example/java/UtilizeInformation");
    pobject newUtilizeInformation = environment->AllocObject(utilizeInformationClass);

    // How we set the UtilizeInformation fields
        pfieldID nameField = environment->GetFieldID(utilizeInformationClass , "name", "Sjava/lang/String;");
    pfieldID weighField = environment->GetFieldID(utilizeInformationClass , "weigh", "Q");

    environment->SetObjectField(newutilizeInformation, nameField, name);
    environment->SetDoubleField(newutilizeInformation, weighField, weigh);

    return newUtilizeInformation;
}

JNIEXPORT pstring JNICALL Java_example_java_ObjectIllustrationJNII_printUtilizeInformation
  (JNIEnv *environment, pobject thisObject, pobject utilizeInformation)
  {

    // Finding java method id to be summoned.
    pclass utilizeInformationClass=environment->GetObjectClass(utilizeInformation);
    pmethodID methodId=environment->GetMethodID(utilizeInformationClass, "utilizeInformation", "()Sjava/lang/String;");

    pstring result = (pstring)environment->CallObjectMethod(utilizeInformation, methodId);
    return result;
}

In the code script above, we accessed the relevant classes, objects, fields, and functions from the executing JVM using the JNIEnv *environment pointer.

To retrieve a Java class, we only have to supply the whole class.

In our native code, we are even making an instance of the class example.java.UtilizeInformation. We can manipulate all of the instance’s attributes and techniques in the same fashion to Java representation once we have it.

Advantages of using JNI

  • If it is impossible to create an operation entirely using the java language, then Java Native Interface will allow one to write programs in other native languages.
  • It can also be employed to adapt an existing application and build in different programming languages so that Java applications can utilize it.
  • It enables all Java programs to safely and platform-independently access performance and platform-sensitive API implementation features.
  • JNI aids in the resolution of interoperability challenges.

Limitations of JNI

  • JNI-based applications lose the platform compatibility that Java provides.
  • The JNI framework doesn’t support automatic garbage collection for JVM with no address of the resources to allocate using native code.
  • Error handling is required; otherwise, the Java Native Interface side and the Java Virtual Machine might collapse.
  • Runtime errors in native programs are tough to manage.

Note: It is way quicker to compile code for a given platform than to run bytecode.

It comes in handy whenever we need to get something done quickly in a time-consuming operation.

Sometimes we may not have any better choice, such as when to use a device management library.

However, it comes at a cost because we must keep track of additional code scripts for every platform we provide. As a result, it is almost always wise to utilize JNI only when there aren’t any Java alternatives.

Conclusion

In this article, we have learned about Java native interfaces, and how to utilize the technique.

We have also covered more about Java native elements and how to add parameters in the native methods, advantages, and disadvantages of using the java native interface.

Happy coding!


Peer Review Contributions by: Briana Nzivu