进击的NDK05-JNI开发及其数据类型和方法

什么是JNI?

JNI(Java Native Interface) Java调用 c/c++,c/c++调用Java的一套API。

JNI不是Android开发独有的,Java项目工程也会用到。

JNI调用C流程

  1. 定义native方法
  2. javah命令,生成.h头文件
  3. 复制生成的.h头文件到CPP工程中
  4. 复制jni.hjni_md.h文件到CPP工程中
  5. .c文件中,实现第二步声明的函数
  6. 生成dll文件,并复制到项目根目录

1. 定义native方法

package com.loveq;

public class Main {

    //定义native方法
    public native static String sayHelloFromC();

    public static void main(String[] args) {

    }
}

2.javah命令,生存.h头文件

//2.使用javah生成头文件(javah加全路径) 例如: javah com.loveq.Main (这里在src路径下执行)

3.复制生成的.h头文件到CPP工程中

4. 复制jni.hjni_md.h文件到CPP工程中

jni.hjni_md.hJDKinclude文件夹中,然后修改通过javah生成的头文件,将#include 改为 #include "jni.h"<>代表加载系统的)

.c文件中,实现第二步声明的函数

#include "com_loveq_Main.h"

//c中的函数名等于Java_完整类名_函数名
JNIEXPORT jstring JNICALL Java_com_loveq_Main_sayHelloFromC
(JNIEnv * env, jclass jcls){
    //将C的字符串转为java字符串
    return (*env)->NewStringUTF(env, "hello from c");
}

c中的函数名等于Java_完整类名_函数名。

6.生成dll文件,并复制到项目根目录

6.1 配置管理器

进击的NDK05-JNI开发及其数据类型和方法_第1张图片
01配置管理器.png

6.2 新建平台

进击的NDK05-JNI开发及其数据类型和方法_第2张图片
02新建平台.png

6.3 选择x64平台

进击的NDK05-JNI开发及其数据类型和方法_第3张图片
03选择x64平台.png

6.4 保存关闭

进击的NDK05-JNI开发及其数据类型和方法_第4张图片
04关闭.png

6.5 修改生成属性

进击的NDK05-JNI开发及其数据类型和方法_第5张图片
05修改生成属性.png

6.6 选择动态库dl

进击的NDK05-JNI开发及其数据类型和方法_第6张图片
06选择动态库dll.png

6.7 复制dll到项目根路径

进击的NDK05-JNI开发及其数据类型和方法_第7张图片
07复制dll到项目根路径.png

复制到项目根路径后,就可以加载使用了,这样就完成基本的Java调用C

package com.loveq;

public class Main {

    public native static String sayHelloFromC();

    public static void main(String[] args) {
        System.out.println(sayHelloFromC());
    }

    static {
        //加载动态库
        System.loadLibrary("Project5");
    }
}

JNI 详解

Java 调用C的方法

package com.loveq;

public class Main {

    //静态方法
    public native static String sayHelloFromC();

    public native String sayHelloFromC2();

    public static void main(String[] args) {
        System.out.println(sayHelloFromC());
    }

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

注意这里的第一个方法是静态的,第二个是普通的native方法。我们看一下通过javah上传头文件,这两个方法会有什么不同。

/*
 * Class:     com_loveq_Main
 * Method:    sayHelloFromC
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_loveq_Main_sayHelloFromC
  (JNIEnv *, jclass);

/*
 * Class:     com_loveq_Main
 * Method:    sayHelloFromC2
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_loveq_Main_sayHelloFromC2
  (JNIEnv *, jobject);

通过上面可以看到,主要是第二个参数不同,静态方法第二个参数是jclass,这里代表Main.class,而第二个方法jobject代表类对象的实例,这里代表Main main = new Main(),中的main

总结:每个native函数,都至少有两个参数(JNIEnv*,jclass或者jobject)

  1. native方法为静态方法时:
    jclass 代表native方法所属类的class对象
  2. native方法为非静态方法时:
    jobject 代表native方法所属的对象

C 调用 Java

要实现C 调用 Java,必须先了解Java基本数据类型与JNI数据类型的映射及其签名。这在C 调用 Java时会用到。

Java类型 JNI类型 签名
boolean jboolean Z
byte jbyte B
char jchar C
short jshort S
int jint I
long jlong L
float jfloat F
double jdouble D
void void V
String jstring L+(以/分割包的完整类型)+;String的签名是Ljava/lang/String;
object jobject 见上面
object jobjectArray [开头,在加上数组元素类型的签名,如:int[]的签名是[I,在比如int[][]的签名是[[Iobject的签名是[L/java/lang/Object;
byte[] jByteArray 见上面

C访问Java的成员

java代码

public class Main {

    public String name = "rc";

    public static void main(String[] args) {
    
        Main main = new Main();
        System.out.println("c修改之前的name :" + main.name);
        main.getField();
        System.out.println("c修改之后的name :" + main.name);
    }

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

c代码

JNIEXPORT jstring JNICALL Java_com_loveq_Main_getField
(JNIEnv * env, jobject jobj){
    //获取class对象
    jclass cls = (*env)->GetObjectClass(env, jobj);

    //获取属性id,第三个参数是属性名字,第四个参数是该属性的签名,因为在Java中name的类型是String,所以签名是Ljava/lang/String;
    jfieldID fid =(*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");

    //因为String type是Object  所以这里使用GetObjectField方法
    //GetField
    jstring jstr = (*env)->GetObjectField(env, jobj, fid);

    //打印地址
    printf("jstr:%#x \n", &jstr);

    //jstring 转 c字符串
    char *c_str = (*env)->GetStringUTFChars(env, jstr, JNI_FALSE);
    
    char text[20] = "Hello ";
    //拼接字符串
    strcat(text, c_str);

    //c字符串 转 jstring
    jstring new_jstr = (*env)->NewStringUTF(env, text);

    //修改属性
    //SetField
    (*env)->SetObjectField(env, jobj, fid, new_jstr);
    
    //释放资源
    (*env)->ReleaseStringUTFChars(env, jstr, c_str);

    return new_jstr;

}

C访问Java静态成员

java代码

public class Main {

    public String name = "rc";

    public static int password = 123456;

    public native String getField();

    public native  void getStaticField();

    public native static String sayHelloFromC();

    public native String sayHelloFromC2();

    public static void main(String[] args) {
        System.out.println(sayHelloFromC());
        Main main = new Main();
        System.out.println("c修改之前的password :" + Main.password);
        main.getStaticField();
        System.out.println("c修改之后的password :" + Main.password);
    }

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

c代码

JNIEXPORT void JNICALL Java_com_loveq_Main_getStaticField
(JNIEnv * env, jobject object){
    //获取class
    jclass jcls = (*env)->GetObjectClass(env, object);

    //jfieldID
    jfieldID fid = (*env)->GetStaticFieldID(env, jcls, "password", "I");

    jint password = (*env)->GetStaticIntField(env, jcls, fid);
    
    password = 123;

    (*env)->SetStaticIntField(env, jcls, fid, password);
}

C访问Java代码

Java代码

public class Main {

    public int getRandomNumber(int max) {
        System.out.println("getRandomNumber method call");
        return new Random().nextInt(max);
    }

    public native int accessMethod();

    public static void main(String[] args) {
        Main main = new Main();
        main.accessMethod();

    }

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

C代码

JNIEXPORT jint JNICALL Java_com_loveq_Main_accessMethod
(JNIEnv * env, jobject jobj){
    //1.获取class
    jclass jcls = (*env)->GetObjectClass(env, jobj);
    
    //第一个参数 env 第二个参数class 第三个参数方法名,第四个方法的签名
    jmethodID jmid =(*env)->GetMethodID(env, jcls, "getRandomNumber", "(I)I");

    //调用方法,最后一个是方法参数
    jint random = (*env)->CallIntMethod(env, jobj, jmid, 200);

     printf("random num %d", random);

}

查看方法签名的步骤,1.javac Main.java, 2.javap -s Main.class

你可能感兴趣的:(进击的NDK05-JNI开发及其数据类型和方法)