0% found this document useful (0 votes)
3 views

Lecture 26 Java Native Interface

Uploaded by

Abcd Efgh
Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

Lecture 26 Java Native Interface

Uploaded by

Abcd Efgh
Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 12

Java Native Interface

UNIT IV
Need For JNI

• As we know, one of the main strengths of Java is its portability – meaning that once we write
and compile code, the result of this process is platform-independent bytecode.
• Simply put, this can run on any machine or device capable of running a Java Virtual
Machine, and it will work as seamlessly as we could expect.
• However, sometimes we do actually need to use code that's natively-compiled for a
specific architecture.
• There could be some reasons for needing to use native code:
• The need to handle some hardware
• Performance improvement for a very demanding process
• An existing library that we want to reuse instead of rewriting it in Java.
• To achieve this, the JDK introduces a bridge between the bytecode running in our JVM and
the native code (usually written in C or C++).
• The tool is called Java Native Interface.
Native Methods: The JVM Meets Compiled Code
• Java provides the native keyword that's used to indicate that the method implementation
will be provided by a native code.
• Normally, when making a native executable program, we can choose to use static or shared
libs:
• Static libs – all library binaries will be included as part of our executable during the linking process.
Thus, we won't need the libs anymore, but it'll increase the size of our executable file.
• Shared libs – the final executable only has references to the libs, not the code itself. It requires that the
environment in which we run our executable has access to all the files of the libs used by our program.
• bytecode and natively compiled code CANNOT mix into the same binary file.
• shared lib will be kept separately in native file (.so/.dll/.dylib) file (depending on OS)
• The native keyword transforms our method into a sort of abstract method:
• private native void aNativeMethod();
• it will be implemented in a separated native shared library.
• A table with pointers in memory to the implementation of all of our native methods will be
constructed so they can be called from our Java code.
COMPONENTS
• Java Code – our classes. They will include at least one native method.
• Native Code – the actual logic of our native methods, usually coded in C or C+
+.
• JNI header file – this header file for C/C++ (include/jni.h into the JDK
directory) includes all definitions of JNI elements that we may use into our
native programs.
• C/C++ Compiler – we can choose between GCC, Clang, Visual Studio, or any
other we like as far as it's able to generate a native shared library for our
platform.
JNI Elements in Code (Java And C/C++)
Java Elements
• “native” keyword – as we've already covered, any method marked as native must be
implemented in a native, shared lib.
• System.loadLibrary(String libname) – a static method that loads a shared library from the
file system into memory and makes its exported functions available for our Java code.
C/C++ elements (many of them defined within jni.h)
• JNIEXPORT- marks the function into the shared lib as exportable so it will be included in
the function table, and thus JNI can find it
• JNICALL – combined with JNIEXPORT, it ensures that our methods are available for the
JNI framework
• JNIEnv – a structure containing methods that we can use our native code to access Java
elements
• JavaVM – a structure that lets us manipulate a running JVM (or even start a new one)
adding threads to it, destroying it, etc
HelloWorld Jni

package com.mait.jni; we load the shared library in a static block. This ensures that it
will be ready when we need it and from wherever we need it.
public class HelloWorldJNI {

static {
System.loadLibrary("native");
}

public static void main(String[] args) {


new HelloWorldJNI().sayHello();
}

// Declare a native method sayHello() that receives no arguments and returns void
private native void sayHello();
}
Implementing Method in C
• First, to create the definition of the method, we have to use the -h flag of the Java compiler:
• javac -h . HelloWorldJNI.java
• This will generate a com_mait_jni_HelloWorldJNI.h file with all the native methods included in the
class passed as a parameter, in this case, only one:
JNIEXPORT void JNICALL Java_com_mait_jni_HelloWorldJNI_sayHello
(JNIEnv *, jobject);

• Function name is automatically generated using the fully qualified package, class and method name.
• Two parameters passed to our function; a pointer to the current JNIEnv; and also the Java object that
the method is attached to, the instance of our HelloWorldJNI class.
• Now, create a new .cpp file for the implementation of the sayHello function.
• We'll name our .cpp file with theJNIEXPORT
same name as the .h
void JNICALL one containing the header and add this code to
Java_com_mait_jni_HelloWorldJNI_sayHello
implement the native function: (JNIEnv* env, jobject thisObject) {
std::cout << "Hello from C++ !!" << std::endl;
}
Compiling And Linking
To Connect
We need to build our shared library from the C++ code and run it!
To do so, we have to use G++ compiler, not forgetting to include the JNI headers from our Java JDK
installation.
Ubuntu version: g++ -c -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux com_mait_jni_HelloWorldJNI.cpp -o com_mait_jni_HelloWorldJNI.o
Windows version: g++ -c -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 com_mait_jni_HelloWorldJNI.cpp -o com_mait_jni_HelloWorldJNI.o
MacOS version; g++ -c -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin com_mait_jni_HelloWorldJNI.cpp -o
com_mait_jni_HelloWorldJNI.o

After compiling for native code in com_mait_jni_HelloWorldJNI.o, include it in a new shared library.
Whatever we decide to name it is the argument passed into the method System.loadLibrary.
We named ours “native”, and we'll load it when running our Java code.
The G++ linker then links the C++ object files into our bridged library.
Ubuntu version: g++ -shared -fPIC -o libnative.so com_mait_jni_HelloWorldJNI.o -lc
Windows version: g++ -shared -o native.dll com_mait_jni_HelloWorldJNI.o -Wl,--add-stdcall-alias
MacOS version: g++ -dynamiclib -o libnative.dylib com_mait_jni_HelloWorldJNI.o -lc
We can now run our program from the command line.
we need to add the full path to the directory containing the library we've just generated.
java -cp . -Djava.library.path=/NATIVE_SHARED_LIB_FOLDER com.mait.jni.HelloWorldJNI
Console output:
Hello from C++ !!
Adding Parameters To Our Native
Methods
• We'll add some parameters to our native methods. Let's create a new class called
ExampleParametersJNI with two native methods using parameters and returns of different types:
private native long sumIntegers(int first, int second);
private native String sayHelloToMe(String name, boolean isFemale);

• And then, repeat the procedure to ...


JNIEXPORT jlong JNICALL Java_com_mait_jni_ExampleParametersJNI_sumIntegers
create a new .h file with “javac -h” as (JNIEnv* env, jobject thisObject, jint first, jint second) {
we did before. std::cout << "C++: The numbers received are : " << first << " and " << second << std::endl;
• Now create the corresponding .cpp return (long)first + (long)second;
file with the implementation of the }
JNIEXPORT jstring JNICALL Java_com_mait_jni_ExampleParametersJNI_sayHelloToMe
new C++ method: (JNIEnv* env, jobject thisObject, jstring name, jboolean isFemale) {
• the pointer *env of type JNIEnv to const char* nameCharPointer = env->GetStringUTFChars(name, NULL);
access the methods provided by the std::string title;
if(isFemale) {
JNI environment instance. title = "Ms. ";
• JNIEnv allows us to pass Java Strings }
TEST IT!
into our C++ code and back out else {
without worrying about the title = "Mr. ";
}
implementation.
• We can check the equivalence of Java std::string fullName = title + nameCharPointer;
types and C JNI types into Oracle return env->NewStringUTF(fullName.c_str());
}
official documentation. ...
Using Objects and Calling Java Methods From Native Code

• In this last example, we're going to see how we can manipulate Java objects into our native C++ code.
• We'll start creating a new class UserData that we'll use to store some user info:
package com.mait.jni;
public class UserData {
public String name;
public double balance;
public String getUserInfo() {
return "[name]=" + name + ", [balance]=" + balance;
}
}
• Then, we'll create another Java class called ExampleObjectsJNI with some native methods with which
we'll manage objects of type UserData:
...
public native UserData createUser(String name, double balance); Create .h header
public native String printUserData(UserData user);
C++ implementation of our native methods
JNIEXPORT jobject JNICALL Java_com_mait_jni_ExampleObjectsJNI_createUser C++ File
(JNIEnv *env, jobject thisObject, jstring name, jdouble balance) {

// Create the object of the class UserData


jclass userDataClass = env->FindClass("com/mait/jni/UserData"); using the JNIEnv *env
jobject newUserData = env->AllocObject(userDataClass); pointer to access the
// Get the UserData fields to be set
needed classes, objects,
jfieldID nameField = env->GetFieldID(userDataClass , "name", "Ljava/lang/String;"); fields and methods from
jfieldID balanceField = env->GetFieldID(userDataClass , "balance", "D"); the running JVM.
env->SetObjectField(newUserData, nameField, name); Creating an instance of
env->SetDoubleField(newUserData, balanceField, balance);
the class to manipulate
return newUserData; all its properties and
} methods in a way similar
to Java reflection
JNIEXPORT jstring JNICALL Java_com_mait_jni_ExampleObjectsJNI_printUserData
(JNIEnv *env, jobject thisObject, jobject userData) {
JNIEnv
// Find the id of the Java method to be called
jclass userDataClass=env->GetObjectClass(userData);
jmethodID methodId=env->GetMethodID(userDataClass, "getUserInfo", "()Ljava/lang/String;");

jstring result = (jstring)env->CallObjectMethod(userData, methodId);


return result;
}
Disadvantages
JNI bridging does have its pitfalls.
The main downside being the dependency on the underlying platform; we
essentially lose the “write once, run anywhere” feature of Java. This means that
we'll have to build a new lib for each new combination of platform and
architecture we want to support. Imagine the impact that this could have on the
build process if we supported Windows, Linux, Android, MacOS…
JNI not only adds a layer of complexity to our program. It also adds a costly layer
of communication between the code running into the JVM and our native code:
we need to convert the data exchanged in both ways between Java and C++ in a
marshaling/unmarshaling process.
Sometimes there isn't even a direct conversion between types so we'll have to
write our equivalent.

You might also like