[Turn] JNI programming - let C++ and Java call each other

JNI is actually the abbreviation of Java Native Interface, which is the java native interface. It provides several APIs to communicate with Java and other languages ​​(mainly C&C++). Maybe many people think that Java is powerful enough, why do you need something like JNI? We know that Java is a platform-independent language, and the platform is transparent to the upper-level java code, so we don't need JNI most of the time, but what if you encounter one of the following three situations?

Your Java code needs to get the properties of a file. But you can't find the relevant API in the JDK help documentation.
There is another system locally, but it is not implemented in Java language. At this time, your boss asks you to integrate the two systems together.
Your Java code needs to use some kind of algorithm, but the algorithm is implemented in C and encapsulated in a dynamic link library file (DLL).
For the above three cases, if there is no JNI, it will become extremely tricky. Even if a solution is found, it is time-consuming and labor-intensive. In the final analysis, it will increase the cost of development and maintenance.

Having said so much nonsense, let's get to the point now. Anyone who has seen the JDK source code will definitely notice that there are many methods marked as native in the source code. These methods have only method signatures but no method bodies. In fact, these naive methods are what we call the java native interface. It provides an interface for calling (invoke), and then implements it in C or C++. Let's write this "bridge" first. My own development environment is j2sdk1.4.2_15 + eclipse 3.2 + VC++ 6.0, first create a HelloFore Java project in eclipse, and then write the following code.
Java code
package com.chnic.jni; 
 
public class SayHellotoCPP { 
     
    public SayHellotoCPP(){ 
    } 
    public native void sayHello(String name); 


The usual first program is always HelloWorld. For a change of taste today, change world to a name. My native method has a String parameter. Will pass a name to the background. The local method has been completed. Now let's introduce the javah method. Next, we will use the javah method to generate a corresponding .h header file.

javah is a command that generates header files specifically for JNI. After CMD opens the console, enter javah and press Enter to see some parameters of javah. I won't introduce the parameter -jni here. This parameter is also the default parameter. It will generate a JNI-style .h header file. Enter the root directory of the project in the console, which is the HelloFore directory, and enter the command.
After the Java code
javah -jni com.chnic.jni.SayHellotoCPP 

command is executed, the header file com_chnic_jni_SayHellotoCPP.h will be found in the root directory of the project. It is necessary to say more words here. When executing javah, enter the complete package name + class name. Otherwise, the exception java.lang.UnsatisfiedLinkError will occur during subsequent test calls.

At this point, the java part is basically completed. Next, let's write the back-end C++ code. (C can also be used, but cout is faster to use than printf, so I am lazy to use C++ here.) Open VC++, first create a new Win32 Dynamic-Link library project, and then select An empty DLL project. Here my C++ project is HelloEnd, copy the header file just generated to the root directory of this project. Open this header file with any text editor and find that there is a method signature as follows.
Cpp code
/*
* Class: com_chnic_jni_SayHellotoCPP
* Method: sayHello
* Signature: (Ljava/lang/String;)V
*/ 
JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello 
  (JNIEnv *, jobject, jstring);  Take

a closer look at this method and mark the class on the annotation Name, method name, signature (Signature), as for what this signature is used for, we will talk about it later. The most important thing here is the method signature of Java_com_chnic_jni_SayHellotoCPP_sayHello. After we execute the sayHello(String name) method on the Java side, the JVM will help us wake up the Java_com_chnic_jni_SayHellotoCPP_sayHello method in the DLL. So we create a new C++ source file to implement this method.
Cpp code
#include <iostream.h> 
#include "com_chnic_jni_SayHellotoCPP.h" 
 
 
JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello  
  (JNIEnv* env, jobject obj, jstring name) 

    const char* pname = env->GetStringUTFChars(name, NULL); 
    cout << "Hello , " << pname << endl; 


Because the header file we generated is in the root directory of the C++ project and not in the environment directory, so we need to change the angle brackets to single quotes. As for the VC++ environment directory, you can find it in Tools-> Settings in Options->Directories. The F7 compilation project found that the header file jni.h was missing. This header file can be found in the %JAVA_HOME%\include directory. Copy this file to the C++ project directory, continue to compile and find that it is still not found. It turns out that in the header file we just generated, the file jni.h was referenced by #include <jni.h>, so we changed the angle brackets to double quotes #include "jni.h" and continued to compile It is found that the jni_md.h file is missing, then find the header file under %JAVA_HOME%\include\win32, put it in the project root directory, and F7 compiles successfully. In the Debug directory, you will find that the file HelloEnd.dll is generated.

At this time, the back-end C++ code has also been completed, and the next task is how to connect them together. In order for the front-end java program to "recognize and find" this dynamic link library, the DLL must be placed in the windows path environment variable under. There are two ways to do it:

put this DLL under the sysytem32 folder under windows, this is the default path of windows to
copy the Debug directory of your project, here is C:\Program Files\Microsoft Visual Studio\MyProjects\HelloEnd \Debug is the directory, and configure this directory to the Path of the User variable. Restart eclipse and let eclipse re-read the path variable at startup.

In comparison, the second method is more flexible. It is not necessary to copy the dll file back and forth during development, which saves a lot of work. Therefore, the second method is recommended during development. Here we are also using the second type, open the SayHellotoCPP class after eclipse restarts. In fact, what we have done above is to allow the JVM to find those DLL files. Next, we need to let our own java code "know" this dynamic link library. Add System.loadLibrary("HelloEnd"); to the static initialization block.

Java code
package com.chnic.jni; 
 
public class SayHellotoCPP { 
     
    static{ 
        System.loadLibrary("HelloEnd"); 
    } 
    public SayHellotoCPP(){ 
    } 
    public native void sayHello(String name); 
     


This way our code can recognize and load this dynamic link library file. Everything is ready, only the test code is left, and then the test code is written.
Java code
SayHellotoCPP shp = new SayHellotoCPP(); 
shp.sayHello("World"); 

We don't let him directly Hello, World. We pass in World and execute the code. It is found that the console prints out the sentence Hello, World. In this regard, one of the simplest JNI programs has been developed. Some friends may have doubts about the
Cpp code
const char* pname = env->GetStringUTFChars(name, NULL);  in the CPP code.

This GetStringUTFChars is the API provided by JNI to the developer, and we will talk about it later. I have to say a lot here.
Because JNI has a Native feature, some projects use JNI, which means that this project basically cannot be cross-platform.
JNI calls are quite slow, so be sure to figure out if this is necessary before actually using it.
Because languages ​​such as C++ and C are very flexible, they are prone to errors if they are not careful. For example, in the code I just wrote, I did not write a destructor string to release memory. For java developers, because of the GC garbage collection mechanism, most people do not Write a concept like destructor. Therefore, JNI will also increase the risk in the program and increase the instability of the program.
 

In fact, in Java code, in addition to marking the native keyword for local methods and adding dynamic link libraries to be loaded, JNI is basically transparent to the upper-layer coder, and the upper-layer coder does not know the method of this method when calling those local methods. Where exactly is the body, this principle is the same as we use the API provided by JDK. So using JNI in Java is still very simple, in contrast, calling java in C++ is much more complicated than the former.

Now let's introduce the data types in JNI. In C++, the compiler will allocate lengths for some basic data types according to the platform, thus causing platform inconsistency, but this problem does not exist in Java, because of the JVM, so The basic data types in Java get the same length on all platforms, for example, the width of int is always 32 bits. For this reason, the basic data types of java and c++ need to implement some mapping to maintain consistency. The following table summarizes:

    Java types native types aliases defined in JNI   
int long jint
long _int64 jlong
​​byte signed char jbyte
boolean unsigned char jboolean
char unsigned short jchar
short short jshort
float float jfloat
double double jdouble
Object _jobject* jobject

The above table is what I searched on the Internet, and put it up for everyone to compare. For each mapped data type, the designers of JNI have actually taken the corresponding alias for us to facilitate memory. If you want to know some more detailed information, you can look at the header file jni.h. The definitions and aliases of various data types are defined in this file.

Knowing the data types in JNI, let's take a look at this example. This time we use Java to implement a front-end market (replaced by Foreground below) and CPP to implement a back-end factory (replaced by backend below). Let's start by writing a java class that contains native methods.

Java code
package com.chnic.service; 
 
import com.chnic.bean.Order; 
 
public class Business { 
    static{ 
        System.loadLibrary("FruitFactory"); 
    } 
     
    public Business(){ 
         
    } 
     
    public native double getPrice(String name); 
    public native Order getOrder(String name, int amount); 
    public native Order getRamdomOrder(); 
    public native void analyzeOrder(Order order); 
     
    public void notification(){ 
        System.out.println("Got a notification."); 
    } 
     
    public static void notificationByStatic(){ 
        System.out.println("Got a notification in a static method."); 
    } 


This class It contains 4 native methods, a static initialization block to load the dll file to be generated. The rest of the methods are very common java methods, which will be called back in the backend. This class requires a JavaBean named Order.

Java code
package com.chnic.bean; 
 
public class Order { 
     
    private String name = "Fruit"; 
    private double price; 
    private int amount = 30; 
     
    public Order(){ 
         
    } 
 
    public int getAmount() {      return 
        amount; 

  
    public void setAmount(int amount) { 
        this.amount = amount; 
    } 
 
    public String getName() { 
        return name; 
    } 
 
    public void setName(String name) { 
        this.name = name; 
    } 
 
    public double getPrice() {      return 
        price;      public void setPrice(double price) {          this.price = price;      }  In JavaBean, we assign values ​​to two private properties for the convenience of demonstration in later examples. So far, the code on the Java side except the test code is all high-profile. Next, the work of generating the .h header file and building the C++ project will be covered here. If you are not familiar with it, please look back at the first article. In the project, we create a new C++ source file named Factory to implement those native methods. The specific code is as follows. Cpp code #include <iostream.h>  #include <string.h> 

 










#include "com_chnic_service_Business.h" 
 
jobject getInstance(JNIEnv* env, jclass obj_class); 
 
JNIEXPORT jdouble JNICALL Java_com_chnic_service_Business_getPrice(JNIEnv* env,  
                                                                   jobject obj,  
                                                                   jstring name) 

    const char* pname = env->GetStringUTFChars(name, NULL); 
    cout << "Before release: "  << pname << endl; 
 
    if (strcmp(pname, "Apple") == 0) 
    { 
        env->ReleaseStringUTFChars(name, pname); 
        cout << "After release: " << pname << endl; 
        return 1.2; 
    }  
    else 
    { 
        env->ReleaseStringUTFChars(name, pname); 
        cout << "After release: " << pname << endl; 
        return 2.1; 
    }    

 
 
JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getOrder(JNIEnv* env,  
                                                                   jobject obj,  
                                                                   jstring name,  
                                                                   jint amount) 

    jclass order_class = env->FindClass("com/chnic/bean/Order"); 
    jobject order = getInstance(env, order_class); 
     
    jmethodID setName_method = env->GetMethodID(order_class, "setName", "(Ljava/lang/String;)V"); 
    env->CallVoidMethod(order, setName_method, name); 
 
    jmethodID setAmount_method = env->GetMethodID(order_class, "setAmount", "(I)V"); 
    env->CallVoidMethod(order, setAmount_method, amount); 
 
    return order; 

 
JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getRamdomOrder(JNIEnv* env,  
                                                                         jobject obj) 

    jclass business_class = env->GetObjectClass(obj); 
    jobject business_obj = getInstance(env, business_class); 
 
    jmethodID notification_method = env->GetMethodID(business_class, "notification", "()V"); 
    env->CallVoidMethod(obj, notification_method); 
 
    jclass order_class = env->FindClass("com/chnic/bean/Order"); 
    jobject order = getInstance(env, order_class); 
    jfieldID amount_field = env->GetFieldID(order_class, "amount", "I"); 
    jint amount = env->GetIntField(order, amount_field); 
    cout << "amount: " << amount << endl; 
    return order; 

 
 
JNIEXPORT void JNICALL Java_com_chnic_service_Business_analyzeOrder (JNIEnv* env,  
                                                                     jclass cls,  
                                                                     jobject obj) 

    jclass order_class = env->GetObjectClass(obj); 
    jmethodID getName_method = env->GetMethodID(order_class, "getName", "()Ljava/lang/String;"); 
    jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method)); 
    const char* pname = env->GetStringUTFChars(name_str, NULL); 
 
    cout << "Name in Java_com_chnic_service_Business_analyzeOrder: " << pname << endl; 
    jmethodID notification_method_static = env->GetStaticMethodID(cls, "notificationByStatic", "()V"); 
    env->CallStaticVoidMethod(cls, notification_method_static); 
 

 
jobject getInstance(JNIEnv* env, jclass obj_class) 

    jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V"); 
    jobject obj = env->NewObject(obj_class, construction_id); 
    return obj; 


As you can see, in my Java The four native methods are all implemented here. Next, we will explain the usage of some JNI-related APIs for these four methods. Let's start with the first method:

1.getPrice(String name)

This method is to pass a parameter of type string from foreground to backend, and then backend judges and returns the corresponding price. In the cpp code, we use the GetStringUTFChars method to convert the incoming jstring into a UTF-8 encoded char string. Because the actual type of jstring is jobject, there is no direct comparison.
The GetStringUTFChars method contains two parameters, the first parameter is the jstring object you want to process, and the second parameter is whether you need to generate a copy object in memory. After converting the jstring into a const char*, we use the strcmp function in string.h to compare the two strings, and if the incoming string is "Apple", we return 1.2. Otherwise, return to 2.1. Here I would like to say more about the function ReleaseStringUTFChars. This function is not difficult to understand literally, it is used to release memory. It's a bit like the destructor in cpp, but Sun has already packaged it for us. Since there is GC in the JVM, most java coders do not have the habit of writing destructors, but it is necessary in JNI, otherwise it is easy to cause memory leaks. Here we type this string before and after release to see the effect.

After a rough explanation of some APIs, we write test code.

Java code
Business b = new Business();         
System.out.println(b.getPrice("Apple")); 

Run this test code and print

Before release: Apple
After release:

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326681873&siteId=291194637