JNI 开发流程

一、C 语言执行的流程

  1. 编辑:编写代码的过程。
  2. 预编译(预处理):为编译做准备工作,完成代码文本的替换工作。
  3. 编译:形成目标代码(.obj)。
  4. 连接:将目标代码与C 函数库连接合并,形成最终的可执行文件。
  5. 执行:执行可执行文件。

二、头文件

1、头文件的作用

头文件告诉编译器有这样一个函数,连接器负责找到这个函数的实现

2、自定义头文件

*注:开发工具为 Visual Studio 2017

1、创建 .h 文件,对相应方法进行声明。

例如:创建 math.h

#ifndef _MATH_H  //如果没有定义 _MATH_H 标识
#define _MATH_H  //定义 _MATH_H 标识
int add(int, int, int);
#endif

//该头文件只被包含一次,让编译器自己处理好循环包含问题
#pragma once 
int add(int, int, int);

2、在 .h 文件同级目录下创建对应的 .c 文件,对 .h 文件中声明的方法进行实现。

例如:创建 head.c

#define _CRT_SECURE_NO_WARNINGS
#include "math.h"
#include <stdio.h>

int add(int a, int b, int c){
   int result = 0;
   result = a + b + c;
   return result;
}

3、创建一个C文件,进行验证头文件是否编写成功。

例如:创建 test.c

#include<stdio.h>
#include "math.h"


void main(){
	int a = 3, b = 4, c = 5, result = 0;
	result = add(a, b, c);
	printf("The result is %d!\n", result);
	system("pause");
}

三、define 指令

1、define 指令的作用

  1. define 指令用来定义标识;
    如:** #ifdef __cplusplus 标识支持C++语法;防止文件重复引入**
  2. define 指令用来定义常数;如:#define MAX 100
  3. define 指令用来定义“宏函数”。
    如:
void jni_read(){
    printf("read\n");
}

void jni_write(){
    printf("write\n");
}

/**
 * 宏函数
 */
#define jin(NAME) jni_##NAME();

void main(){
    jni(read);
    jni(write);
    getchar();
}

日志输出示例:

/**
 *__VA_ARGS__    可变参数
 */
#define LOG(FOTMAT,...) printf(##FOTMAT,__VA_ARGS__);printf("\n");

void main(){
    LOG("%s: %d","size",99);
    getchar();
}

四、JNI (Java Native Interface)

1、定义

**Java 调用C/C或者C/C调用 Java 的一套 API **

2、Java调用C/C++项目开发步骤(Windows系统下)

  • 编写native方法
package com.example.jni;
public class JNITest {

    public native static String getStringFromC();
    
    public static void main(String[] args){

    }
}
  • javah命令,生成.h文件
javah com.example.jni.JNITest
//生成 com_example_jni_JNITest.h 文件
  • 复制.h头文件到CPP工程中
  • 复制jni.h和jni_md.h文件到CPP工程中
  • 实现.h头文件中声明的函数;C函数名称:Java_完整类名_函数名
//JNITest.c
#include "com_example_jni_JNITest.h"

JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_getStringFromC
(JNIEnv *jEnv, jclass jcls) {
	//简单实现,将C的字符传转成Java的字符串
	return (*jEnv)->NewStringUTF(jEnv, "C String");

}
  • 生成动态库.dll文件(Windows环境下默认dll,Linux环境下默认为so)
  • 配置D:\dll 目录到环境变量,并将刚刚生成的 .dll 文件复制到D:\dll 目录下;或者复制到项目根目录下;
  • 重启Eclipse,使用IDEA的需要在项目运行配置中的 VM options 中增加配置:
// VM options:
-Djava.library.path=D:\dll

五、JNIEnv

1、JNIEnv 是什么

  • 在C语言中JNIEnv是一个结构体指针,代表Java运行环境,主要是调用Java中的代码,在上面JNITest.c中实现函数声明的时候,jEnv 是一个二级指针
//JNITest.c
#include "com_example_jni_JNITest.h"

JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_getStringFromC
(JNIEnv *jEnv, jclass jcls) {
	//简单实现,将C的字符传转成Java的字符串
	return (*jEnv)->NewStringUTF(jEnv, "C String");

}
  • 在C++中JNIEnv是一个结构体的别名,代表Java运行环境,主要是调用Java中的代码,jEnv 是一个结构体的一级指针
//JNITest.cpp
#include "com_example_jni_JNITest.h"

JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_getStringFromC
(JNIEnv *jEnv, jclass jcls) {
	//简单实现,将C的字符传转成Java的字符串
	return jEnv->NewStringUTF("C String");

}

模拟C 的实现

//JNIEnv 是结构体指针的别名
typedef struct JNINativeInferface_* JNIEnv;
//结构体
struct JNINativeInferface_{
    char* (*NewStringUTF)(JNIEnv*,char*);
};

//函数实现
char* NewStringUTF(JNIEnv* env,char* str){
    return str;
}

void main(){
    //实例化结构体
    struct JNINativeInferface_ struct_env;
    struct_env.NewStringUTF = NewStringUTF;
    
    //结构体指针
    JNIEnv e = &struct_env;
    //结构体的二级指针
    JNIEnv *env = &e;
    //通过二级指针调用函数
    char* str = (*env)->NewStringUTF(env,"Hello");
    printf("str = %s\n",str);
    getchar();
}

2、JNIEnv 调用函数时C和C++的区别

  • C 中需要传入 JNIEnv ,因为函数执行过程中需要 JNIEnv
  • C++ 中不需要传入 JNIEnv ,是因为C++中有 this,相当与JNIEnv
  • C++只是针对C的那一套进行分装,给一个变量赋值为指针,这个变量是二级指针

六、jclass

每个native函数(C中的函数),都至少有两个参数(JNIEnv* jclass或者jobject)。

  • 当native方法为静态方法时:jclass代表native 方法所属类的class对象(JNITest.class);
  • 当 native 方法为非静态方法时:jobject 代表 native 方法所属的对象。