[Android -- JNI and NDK] Basic knowledge of JNI and how to use it

insert image description here

JNI basics

Let's systematically sort out the basic knowledge involved in JNI.
JNI defines the following data types, which are consistent with the data types in Java:

  1. Java primitive types: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jbooleanThese correspond to the Java ones respectively int, byte, short, long, float, double, char and boolean.
  2. Java reference type: jobject is used to refer to java.lang.Object, in addition, the following subtypes are defined:
    a. jclass for java.lang.Class.
    b. jstring for java.lang.String.
    c. jthrowable for java.lang.Throwable.
    d. jarray对java的array. Java's array is a reference type that points to an array of 8 basic types. Therefore, there are 8 basic types of arrays in JNI: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray and jbooleanArray, and one is the jobjectarray pointing to Object.
    Native functions accept parameters of the above types and return values ​​of the above types. However, native functions (C/C++) need to handle types in their own way (such as string in C, which is char *). Therefore, conversion between JNI types and native types is required. Generally speaking, native functions need to:
  3. Add parameters of JNI type (passed from java code)
  4. For JNI type parameters, it is necessary to convert or copy these data into local data types, for example, convert jstring into char *, and jintArray into C int[]. It should be noted that the original JNI types, such as jint and jdouble, do not need to be converted, but can be used directly and participate in calculations.
  5. Perform data manipulation in a local way
  6. Create a JNI return type, and then copy the result data into the JNI data
  7. returnJNI type data

The most troublesome thing is to convert between JNI types (such as jstring, jobject, jintArray, jobjectArray) and native types (such as C-string, int[]). Fortunately, the JNI environment has defined many interface functions for us to do this annoying conversion.

use

Use C to implement JNI
Step 1, write a java class that uses C to implement functions, HelloJNI.java:

public class HelloJNI {
    
    
   static {
    
    
      System.loadLibrary("hello"); // Load native library at runtime
                                   // hello.dll (Windows) or libhello.so (Unixes)
   }

   // Declare a native method sayHello() that receives nothing and returns void
   private native void sayHello();

   // Test Driver
   public static void main(String[] args) {
    
    
      new HelloJNI().sayHello();  // invoke the native method
   }
}

The static code block of the above code calls the System.loadLibrary() method to load a native library "hello" (the sayHello function is implemented in this library) when the class is loaded by the class loader. This library corresponds to "hello.dll" on the Windows product platform, and "libhello.so" on the UNIX-like platform. This library should be included on Java's library path (indicated by the java.library.path system variable), otherwise the above program will throw an UnsatisfiedLinkError error. You should use the VM parameter -Djava.library.path=path_to_lib to specify the path containing the native library.

Next, we use the native keyword to declare the sayHello() method as a local instance method, which clearly tells the JVM: This method is implemented in another language (C/C++), please go there to find his implementation. Note that a native method does not contain a method body, only declarations. The main method in the above code instantiates an instance of the HelloJJNI class, and then calls the local method sayHello().
Next, we compile HelloJNI.java into HelloJNI.class

javac HelloJNI.java

Next, we use the class file generated above to generate a header file for writing C/C++ code, using the javah tool in jdk to complete:

javah HelloJNI

After the above command is executed, HelloJNI.h is generated:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
    
    
#endif
/*
 * Class:     HelloJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

We see that a Java_HelloJNI_sayHello C function is generated in the above header file:

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

The rules for converting java native methods into C function declarations are as follows: Java_{package_and_classname}_{function_name}(JNI arguments). Dots in package names are replaced with single underscores. What needs to be explained is the two parameters in the generator function:

  1. JNIEnv *: This is a pointer to the JNI runtime environment, as we will see later, we access the JNI function through this pointer
  2. jobject: here refers to the this object in java

The above two parameters are not used in the example we give below, but we will use them in our examples later. So far, you can ignore the two things JNIEXPORT and JNICALL.
There is an extern "C" in the above header file, and there are C++ conditional compilation statements above, so everyone will understand that the function declaration here is to tell the C++ compiler: this function is a C function, please use the C function The signature protocol rules go compile! Because we know that C++'s function signature protocol rules are different from those of C, because C++ supports object-oriented function syntax such as rewriting and overloading.
Next, we give the implementation of the C language to realize the above functions:

#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"

// Implementation of native method sayHello() of HelloJNI class
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
    
    
   printf("Hello World!\n");
   return;
}

Save the above code as HelloJNI.c. The jni.h header file is in the "\include" and "\include\win32" directories, where JAVA_HOME refers to your JDK installation directory.
The function of this C code is very simple, it is to print Hello Word on the terminal! This sentence.
Below we compile this code, using the GCC compiler:
for MinGW on windows:

> set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_{xx}
      // Define and Set environment variable JAVA_HOME to JDK installed directory
      // I recommend that you set JAVA_HOME permanently, via "Control Panel" ⇒ "System" ⇒ "Environment Variables"
> echo %JAVA_HOME%
      // In Windows, you can refer a environment variable by adding % prefix and suffix 
> gcc -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o hello.dll HelloJNI.c
      // Compile HellJNI.c into shared library hello.dll

It is also possible to compile in steps:

// Compile-only with -c flag. Output is HElloJNI.o
> gcc -c -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" HelloJNI.c

// Link into shared library "hello.dll"
> gcc -Wl,--add-stdcall-alias -shared -o hello.dll HelloJNI.o

Next, we use the nm command to view the functions in the generated hello.dll:

> nm hello.dll | grep say
624011d8 T _Java_HelloJNI_sayHello@8

For Cygwin on Windows:
First, you need to define __int64 as "long long" type, through the -D _int64="long long option.
For gcc-3, please include the option -nmo -cygwin to compile the dll library, these The library is not dependent on the Cygwin dll.

> gcc-3 -D __int64="long long" -mno-cygwin -Wl,--add-stdcall-alias 
  -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o hello.dll HelloJNI.c

Using C/C++ to implement JNI
The first step: write a java class that uses native code: HelloJNICpp.java

public class HelloJNICpp {
    
    
   static {
    
    
      System.loadLibrary("hello"); // hello.dll (Windows) or libhello.so (Unixes)
   }

   // Native method declaration
   private native void sayHello();

   // Test Driver
   public static void main(String[] args) {
    
    
      new HelloJNICpp().sayHello();  // Invoke native method
   }
}

Again, we use javac to compile this code:

> javac HelloJNICpp.java

Step 2: Generate C/C++ header files

> javah HelloJNICpp

The above command will generate a HelloJNICpp.h file, and this local function is declared in this file:

JNIEXPORT void JNICALL Java_HelloJNICpp_sayHello(JNIEnv *, jobject);

Step 3: C/C++ coding implementation, HelloJNICppImpl.h, HelloJNICppImpl.cpp, and HelloJNICpp.c
Here, we use C++ to implement the real functions ("HelloJNICppImpl.h" and "HelloJNICppImpl.cpp"), and use C to and java to interact. (Translator's Note: In this way, the code logic of JNI can be separated from our real business logic!)
C++ header file: "HelloJNICppImpl.h"

#ifndef _HELLO_JNI_CPP_IMPL_H
#define _HELLO_JNI_CPP_IMPL_H

#ifdef __cplusplus
        extern "C" {
    
    
#endif
        void sayHello ();
#ifdef __cplusplus
        }
#endif

#endif

C++ code implementation: "HelloJNICppImpl.cpp"

#include "HelloJNICppImpl.h"
#include  <iostream>

using namespace std;

void sayHello () {
    
    
    cout << "Hello World from C++!" << endl;
    return;
}

C code realizes the interaction with Java: "HelloJNICpp.c"

#include <jni.h>
#include "HelloJNICpp.h"
#include "HelloJNICppImpl.h"

JNIEXPORT void JNICALL Java_HelloJNICpp_sayHello (JNIEnv *env, jobject thisObj) {
    
    
    sayHello();  // invoke C++ function
    return;
}

Compile the above code into a shared library (hello.dll on windows).
Using MinGW GCC on windows:

> set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_{xx}
> g++ -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" 
      -shared -o hello.dll HelloJNICpp.c HelloJNICppImpl.cpp

Step 4: Run java code

> java HelloJNICpp
or
> java -Djava.library.path=. HelloJNICpp

Guess you like

Origin blog.csdn.net/duoduo_11011/article/details/131196132