####引言 在学习了C语言基础以后 ,咱们简单的了解了C语言编程的一些范式 , 了解了指针 , 结构体 , 联合体 , 函数 , 文件IO等等 。咱们最终的目的是要学会NDK开发 , 而NDK开发就离不开咱们的JNI技术 。下面 , 就来开始咱们的JNI之旅吧 。 ####JNI的概念 JNI全称 Java Native Interface , java本地化接口 , 能够经过JNI调用系统提供的API , 咱们知道 , 无论是linux仍是windows亦或是mac os , 这些操做系统 , 都是依靠C/C++写出来的 , 还包括一些汇编语言写的底层硬件驱动 。java和C/C++不一样 , 它不会直接编译成平台机器码,而是编译成虚拟机能够运行的java字节码的.class文件,经过JIT技术即时编译成本地机器码,因此有效率就比不上C/C++代码,JNI技术就解决了这一痛点,下面咱们来看看JNI调用示意图: java
从上图能够得知 ,JNI技术经过JVM调用到各个平台的API , 虽然JNI能够调用C/C++ , 可是JNI调用仍是比C/C++编写的原生应用仍是要慢一点 , 不过对高性能计算来讲 , 这点算不得什么 , 享受它的便利 , 也要承担它的弊端 。linux
####JNI开发流程以下:编程
1.编写native方法 2.javah方法,生成.h头文件 3.复制.h头文件到CPP工程中 4.复制jni.h和jni_md.h文件到CPP工程中 5.实现.h头文件声明的函数 6.生成dll文件 7.java项目引入该dll文件 8.加载动态连接库 9.运行java类windows
####1.编写native方法bash
public class JniTest {
public native static String getStringFromC();
public static void main(String[] args){
String text= getStringFromC();
System.out.printf(text);
}
}
复制代码
####2.调用java 中javah方法,生成.h头文件 打开命令行,经过cd命令转到当前Java工程的src目录下面,而后执行javah命令,参数是完整类名:函数
D:\IdeaProjects\jni1\src>javah com.haocai.jni.JniTest
复制代码
#####当文件中含有中文时可能出现源码分析
错误: 编码GBK的不可映射字符
复制代码
因此能够指定编码格式,解决该问题性能
D:\IdeaProjects\jni1\src>javah -jni -encoding UTF-8 com.haocai.jni.JniTest
复制代码
而后在src目录就会生成.h头文件: 学习
####3.复制.h头文件到CPP工程中 将.h头文件复制到VS的代码文件目录下 , 在解决方案中的头文件目录-> 右键-> 添加 -> 添加现有项 。 将咱们的头文件添加进来。ui
####4.复制jni.h和jni_md.h文件到CPP工程中
# JDK 的jni.h头文件目录
D:\Java\jdk1.8.0_60\include\jni.h
# 在jni.h头文件中,又引入了jni_md.h头文件 , 全部这个咱们也要一并赋值过来
D:\Java\jdk1.8.0_60\include\win32\jni_md.h
复制代码
将jni.h这个头文件按照上述步骤 , 添加到头文件目录中 , 注意将<>改为" " , <>表示引入的是系统头文件," "表示引入的是第三方头文件。
####5.实现.h头文件中声明的函数。
com_haocai_jni_JniTest.h内容
#include <jni.h>
/* Header for class com_haocai_jni_JniTest */
#ifndef _Included_com_haocai_jni_JniTest
#define _Included_com_haocai_jni_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_haocai_jni_JniTest
* Method: getStringFromC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_haocai_jni_JniTest_getStringFromC
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
复制代码
com_haocai_jni_JniTest.h中头文件函数
/*
* Class: com_haocai_jni_JniTest
* Method: getStringFromC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_haocai_jni_JniTest_getStringFromC
(JNIEnv *, jclass);
复制代码
#####新建一个.c的文件 ,引入咱们生成的头文件 ,而后实现咱们生成的C语言函数。
#include "com_haocai_jni_JniTest.h"
//函数实现
JNIEXPORT jstring JNICALL Java_com_haocai_jni_JniTest_getStringFromC
(JNIEnv *env, jclass jcls) {
//JNIEnv 结构体指针
//env二级指针
//表明Java运行环境,调用Java中的代码
//简单实现
//将C的字符串转为一个java字符串
return (*env)->NewStringUTF(env,"String From C");
}
复制代码
####注意:JNI开发中JNIEnv在C和C++中实现的区别
JNIEnv:JNIEnv里面有不少方法,与Java进行交互,表明Java的运行环境。JNI Environment。
#####在C中:
JNIEnv 结构体指针的别名 env 二级指针
JNIEXPORT jstring JNICALL Java_com_test_JniTest_getStringFromC
(JNIEnv * env, jclass jcls){
//env是一个二级指针,函数中须要再次传入
return (*env)->NewStringUTF(env, "String From C");
}
复制代码
#####在C++中:
JNIEnv 是一个结构体的别名 env 一级指针
JNIEXPORT jstring JNICALL Java_com_test_JniTest_getStringFromC
(JNIEnv * env, jclass jcls){
//env是一个一级指针,函数中不须要再次传入
return env->NewStringUTF("String From C");
}
复制代码
为何要用二级指针:
为何须要传入JNIEnv?函数执行过程当中须要JNIEnv
C++为何没有传入?由于C++中有this指针。 C++只是对C的那一套进行的封装,给一个变量赋值为指针,这个变量是二级指针 源码分析
在jni.h头文件中有下面的预编译代码:
#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv;
#endif
复制代码
若是是C环境的话,JNIEnv就是一个JNINativeInterface_结构体的指针别名。 若是是C++环境的话,JNIEnv就是一个结构体JNIEnv_的别名,而JNIEnv_是对JNINativeInterface_的封装。 #####C语言中结构体与指针相关知识回顾 Android NDK开发之旅6--C语言--结构体
struct Man
{
char name[20];
int age;
};
void main() {
struct Man m1 = { "Jack",30 };
//结构体指针
struct Man *p = &m1;
printf(" %s, %d\n", m1.name, m1.age);
printf(" %s, %d\n",(*p).name,(*p).age);
//"->"箭头是"(*P)."的简写
printf(" %s, %d\n", p->name, p->age);
system("pause");
}
结果输出:
Jack, 30
Jack, 30
Jack, 30
复制代码
#####C语言中NewStringUTF函数,大体实现以下:
#include <stdio.h>
//JNIEnv结构体的指针别名
typedef struct JNINativeInterface_* JNIEnv;
//结构体
struct JNINativeInterface_
{
//函数指针
char* (*NewStringUTF)(JNIEnv*, char*);
};
//函数实现
char* NewStringUTF(JNIEnv* env,char* str) {
//NewStringUTF执行过程,仍然须要用到JNIEnvironment
return str;
}
void main() {
//实例化结构体
struct JNINativeInterface_ struct_env;
struct_env.NewStringUTF = NewStringUTF;
//结构体指针
JNIEnv e = &struct_env;
//结构体的二级指针
JNIEnv *env = &e;
//经过二级指针调用函数
char* str = (*env)->NewStringUTF(env, "abc");
printf("%s", str);
getchar();
}
结果输出:
abc
复制代码
也就是说,咱们在C语音调用下面这句的时候:
(*env)->NewStringUTF(env, "String From C");
复制代码
env是结构体的二级指针,它取内容*env是一级指针,经过一级指针就能够经过->符号操做结构体了。 而NewStringUTF函数中须要用到JNIEnvironment,所以须要继续传入这个二级指针env自身。
可是在C++里面,JNIEnv就是一个结构体的别名,经过使用一级指针一样能够访问结构体自己,可是因为C++里面有this关键字表明自身,所以能够省略传入参数(已经封装好)。
struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
jstring NewStringUTF(const char *utf) {
return functions->NewStringUTF(this,utf);
}
//代码省略
}
复制代码
如上所示,C++中的JNIEnv就是JNIEnv_的别名,而JNIEnv_是对JNINativeInterface_的一次封装,在函数调用的时候,最终仍是调用JNINativeInterface_结构体的方法:
functions->NewStringUTF(this, utf);
复制代码
其中,原本JNINativeInterface_的函数NewStringUTF就是须要传入二级指针的,由于C++中有this指针,表明着调用这functions的指针(其实就是二级指针),所以在C++中能够直接使用this指针表明当前调用者的指针(二级指针),而在C语音中就须要有二级指针去作了。
####6.生成动态连接库dll文件
库名称 | 特性 | 扩展名 |
---|---|---|
动态库 | 1.动态库把对一些库函数的连接载入推迟到程序运行的时期 2.能够实现进程之间的资源共享。(所以动态库也称为共享库) 3.能够动态注入到程序中 |
win(.dll)linux(.so) |
静态库 | 1.静态库对函数库的连接是放在编译时期完成的。 2.程序在运行时与函数库再无瓜葛,移植方便。 3.浪费空间和资源,由于全部相关的目标文件与牵涉到的函数库被连接合成一个可执行文件。 |
win(.lib)linux(.a) |
了解了静态库和动态库 , 下面咱们就来生成一个动态库,以VS为例:
选中项目 -> 右键 -> 属性 -> 常规 -> 项目默认值 -> 配置类型 , 选择动态库.dll
复制代码
配置完成以后 , 选中项目 -> 生成 。便可生成动态连接库.dll文件。
####7.java项目引入该dll文件
####8.加载动态连接库
//加载动态库
static{
System.loadLibrary("jni1");
}
复制代码
####9.运行java类
public class JniTest {
public native static String getStringFromC();
public static void main(String[] args){
String text= getStringFromC();
System.out.printf(text);
}
//加载动态库
static{
System.loadLibrary("jni1");
}
}
复制代码
####输出:
String From C
复制代码
####结语
#####这篇是JNI系列的开篇 , 其实流程比较简单。可是咱们要了解JNIEnv其后的结构体与指针知识,为后续深刻学习JNI打下基础。