美文网首页
JNI开发的一般流程

JNI开发的一般流程

作者: 漫游之光 | 来源:发表于2018-11-07 20:48 被阅读0次
    • 定义好本地的native方法。
    • javah生成xxx.h头文件。
    • 拷贝xxx.h个jni.h和jni_md.h文件添加到C++工程中。
    • 实现xxx.h头文件中定义的native方法。
    • 生成dll动态库,java引入.dll动态库运行即可。

    下面以一个例子来说明这个流程,先在java层中定义好方法:

    package jni;
    
    public class JniExample {
        
        public static void main(String[] args) {
            JniExample example = new JniExample();
            example.setField();
            System.out.println("name = "+name+"\tage = "+example.age);
            example.callMethod();
            System.out.println("name = "+name+"\tage = "+example.age);
            Point point = getPoint();
            System.out.println("x = "+point.getX()+"\ty = "+point.getY());
        }
        
        public static String name = "abcdef";
        private int age = 18;
            
        public void setField(String name,int age) {
            this.name = name;
            this.age = age;
        }
        
        public native void setField();
        
        public native void callMethod();
        
        public static native Point getPoint(); 
        
        static {
            System.load("G:\\Workspace\\VSCode\\jni\\jni.dll");
        }
    }
    
    package jni;
    
    public class Point {
        private int x;
        private int y;
        
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
        
        public int getX() {
            return x;
        }
        
        public int getY() {
            return y;
        }
    }
    

    可以看出,定义了3个本地方法。在我看来,java和本地方法之间进行绑定,很依赖字符串,所以,在C/C++中会使用很多常量字符串,用于寻找java的类,和java中定义的属性和方法。所以,在这里定义的本地方法,在C/C++中并不是这个名称,需要带上一串前缀,用于标识类。规则这里就不说了,因为一般我们使用下面的命令来生成头文件,这个命令要在src目录下输入。

    javah -classpath . -jni jni.JniExample
    

    这样,就会生成一个头文件jni_JniExample.h:

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include "jni.h"
    /* Header for class jni_JniExample */
    
    #ifndef _Included_jni_JniExample
    #define _Included_jni_JniExample
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     jni_JniExample
     * Method:    setField
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_jni_JniExample_setField
      (JNIEnv *, jobject);
    
    /*
     * Class:     jni_JniExample
     * Method:    callMethod
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_jni_JniExample_callMethod
      (JNIEnv *, jobject);
    
    /*
     * Class:     jni_JniExample
     * Method:    getPoint
     * Signature: ()Ljni/Point;
     */
    JNIEXPORT jobject JNICALL Java_jni_JniExample_getPoint
      (JNIEnv *, jclass);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    值得注意的是,原来生成的#include<jni.h>,如果没有把jni.h的路径加入到系统头文件的路径的话,需要把两个头文件使用双引号引入,然后手工把这两个头文件拷贝到工程目录下。

    然后,就可以创建一个同名的C文件,然后实现头文件中定义的方法:

    #include <string.h>
    #include "jni_JniExample.h"
    
    JNIEXPORT void JNICALL Java_jni_JniExample_setField(JNIEnv *env, jobject jobj){
    
      jclass j_class = (*env)->GetObjectClass(env, jobj);
      jfieldID field_id = (*env)->GetStaticFieldID(env, j_class, "name", "Ljava/lang/String;");
      jstring name = (*env)->GetStaticObjectField(env, j_class, field_id);
      const char *s = (*env)->GetStringUTFChars(env, name, NULL);
      char temp[strlen(s) + 1];
      strcpy(temp, s);
      char *p = temp;
      while (*p != '\0'){
        *p = *p + 1;
        p++;
      }
    
      jfieldID f1 = (*env)->GetFieldID(env, j_class, "age", "I");
      jint age = (*env)->GetIntField(env, jobj, f1);
      age += 1;
      (*env)->SetIntField(env, jobj, f1, age);
    }
    
    JNIEXPORT void JNICALL Java_jni_JniExample_callMethod(JNIEnv *env, jobject jobj){
      jclass jclz = (*env)->GetObjectClass(env, jobj);
      jmethodID method = (*env)->GetMethodID(env, jclz, "setField", "(Ljava/lang/String;I)V");
      jstring name = (*env)->NewStringUTF(env, "Micheal");
      jint age = 20;
      (*env)->CallVoidMethod(env, jobj, method, name, age);
    }
    
    JNIEXPORT jobject JNICALL Java_jni_JniExample_getPoint(JNIEnv *env, jclass jclz){
      jclass point = (*env)->FindClass(env,"jni/Point");
      jmethodID method = (*env)->GetMethodID(env,point,"<init>","(II)V");
      jobject ret = (*env)->NewObject(env,point,method,1,2);
      return ret;
    }
    

    这里实现了访问java类中的属性和方法。从代码中可以看出来,C/C++和java的类型转化主要通过env这个指针来完成的,env里面定义了许多函数,用来实现相互转化。在C/C++中看到的java的属性只有基本类型和Object类型,这里类型安全基本上是靠程序员来保证。因为java支持函数重载,所以如果要调用函数,不光要指定函数名,还需要指定类型签名,也就是要加上参数和返回值类型,不然无法区分重载函数。要获得签名,可以在bin目录下,使用下面的命令获得:

    javap -classpath . -p -s jni.JniExample
    

    要把写好的C/C++代码编译成动态库,可以使用下面的命令:

    gcc -shared -fPIC jni_JniExample.c  -o jni.dll
    

    然后,运行java程序,就发现可以调用本地方法啦。

    相关文章

      网友评论

          本文标题:JNI开发的一般流程

          本文链接:https://www.haomeiwen.com/subject/zkptxqtx.html