前言:
硬件的底层驱动使用linux-c写的,我们可以用C编写简单的应用程序,直接调用open read write等c库函数,我们后边的android使用java写的,那么java怎么调用c的内容呢,就是通过JNI接口
JNI - Java Native Interface
一、Java访问C库的方法
1. 加载c库
System.loadLibrary();
2. 找到函数
建立java 函数到C函数的映射关系,在执行java某函数时,自动调用相应的C函数
2.1. 隐式建立映射关系
- 类a.b.c.d.JNIDemo要调用hello函数
- C语言中要实现Java_a_b_c_d_JNIDemo_hello
- 可以用工具生成头文件
- javac -d . JNIDemo.java
- javah -jni a.b.c.d.JNIDemo
2.2. 显式建立映射关系
只要Java程序中加载了so库,首先要执行JNI_OnLoad() 这个方法
JNIDemo.java
public class JNIDemo{
static { //静态代码块,实例化对象时,只调用一次
/* 1. 加载c库 */
System.loadLibrary("native"); // linux下生成 libnative.so
}
public native static void hello(); // native 表示本地函数,c/c++中实现的
public static void main(String ares[]){
/* 3. 调用 */
hello();
}
}
native.c
#include <stdio.h>
#include <jni.h>
#if 0
typedef struct {
char *name; /* Java里调用的函数名 */
char *signature; /* JNI字段描述符, 用来表示Java里调用的函数的参数和返回值类型 */
void *fnPtr; /* C语言实现的本地函数 */
} JNINativeMethod;
#endif
void c_hello() //本地方法 native_hello
{
printf("hello world\n");
}
static const JNINativeMethod methods[] = { //注册多个本地函数,对应不同的名字
{"hello", "()V", (void *)c_hello},
};
/* 2. map java hello<---------->c c_hello */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
cls = (*env)->FindClass(env, "JNIDemo"); //查找c_hello 对应的 java hello所在的类,表示c_hello映射到这个类的hello
if (cls == NULL) {
return JNI_ERR;
}
if((*env)->RegisterNatives(env,cls,methods,1)) //运行环境env提供这个注册方法,这个方法注册到env环境的cls类中,
return JNI_ERR;
return JNI_VERSION_1_4; //返回jni版本 有1.1 1.2 1.3 1.4
}
- jni.h 的路径是
- 指定so库的路径
- 运行结果
代码分析:
- java中加载so库,就会立即调用JNI_OnLoad()这个函数
- 在JNI_OnLoad() 中,首先获取env环境
- 然后查找c_hello 对应的 java hello所在的类,表示c_hello映射到这个类的hello,生成类cls
- 运行环境env提供这个注册方法RegisterNatives,这个方法注册到env环境的cls类中,在这个方法中实现c_hello 和java hello的映射
- RegisterNatives中建立映射,需要定义RegisterNatives结构体类型的数组
- typedef struct {
char *name; /* Java里调用的函数名 */
char *signature; /* JNI字段描述符, 用来表示Java里调用的函数的参数和返回值类型 */
void *fnPtr; /* C语言实现的本地函数 */
} JNINativeMethod;
- 重点是JNI字段描述符
- JNI字段描述符 ,也可以通过生成头文件获取
- 注意:C函数比Java里的声明多2个参数: (JNIEnv *env, jclass cls)
二、Java和c库传递数据
2.1 传递基本类型数据
直接使用、直接返回,是透明的
举例:
JNIDemo.java
public class JNIDemo{
static { //静态代码块,实例化对象时,只调用一次
/* 1. 加载c库 */
System.loadLibrary("native"); // linux下生成 libnative.so
}
public native static int hello(int a); // native 表示本地函数,c/c++中实现的
public static void main(String ares[]){
/* 3. 调用 */
System.out.println("java hello "+ hello(100));
}
}
native.c
#include <stdio.h>
#include <jni.h>
#if 0
typedef struct {
char *name; /* Java里调用的函数名 */
char *signature; /* JNI字段描述符, 用来表示Java里调用的函数的参数和返回值类型 */
void *fnPtr; /* C语言实现的本地函数 */
} JNINativeMethod;
#endif
jint c_hello(JNIEnv *env, jclass cls, jint a) //本地方法 native_hello
{
printf("hello world a = %d\n",a);
return a+1;
}
static const JNINativeMethod methods[] = { //注册多个本地函数,对应不同的名字
{"hello", "(I)I", (void *)c_hello},
};
/* 2. map java hello<---------->c c_hello */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
cls = (*env)->FindClass(env, "JNIDemo"); //查找c_hello 对应的 java hello所在的类,表示c_hello映射到这个类的hello
if (cls == NULL) {
return JNI_ERR;
}
if((*env)->RegisterNatives(env,cls,methods,1)) //运行环境env提供这个注册方法,这个方法注册到env环境的cls类中,
return JNI_ERR;
return JNI_VERSION_1_4; //返回jni版本 有1.1 1.2 1.3 1.4
}
运行结果:
2.2 传递字符串
举例:实现java传进去hello 返回world
JNIDemo.java
public class JNIDemo{
static { //静态代码块,实例化对象时,只调用一次
/* 1. 加载c库 */
System.loadLibrary("native"); // linux下生成 libnative.so
}
public native static String hello(String str); // native 表示本地函数,c/c++中实现的
public static void main(String ares[]){
/* 3. 调用 */
System.out.println(hello("hello"));
}
}
- 运行java程序,参考生成的jni头文件,修改jni文件源码中JIN描述符和c本地函数声明
- 参考jni.pdf P39 ,字符串操作需要env提供的辅助函数,不能直接用,
- native.c
#include <stdio.h>
#include <jni.h>
#if 0
typedef struct {
char *name; /* Java里调用的函数名 */
char *signature; /* JNI字段描述符, 用来表示Java里调用的函数的参数和返回值类型 */
void *fnPtr; /* C语言实现的本地函数 */
} JNINativeMethod;
#endif
jstring JNICALL c_hello(JNIEnv *env, jobject obj, jstring prompt)
{
const jbyte *str;
str = (*env)->GetStringUTFChars(env, prompt, NULL); //通过这个函数,将传进来的prompt转化为str
if (str == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}
printf("%s\n", str);
(*env)->ReleaseStringUTFChars(env, prompt, str); //把分配的空间释放掉
return (*env)->NewStringUTF(env, "world");;
}
static const JNINativeMethod methods[] = { //注册多个本地函数,对应不同的名字
{"hello", "(Ljava/lang/String;)Ljava/lang/String;", (void *)c_hello},
};
/* 2. map java hello<---------->c c_hello */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
cls = (*env)->FindClass(env, "JNIDemo"); //查找c_hello 对应的 java hello所在的类,表示c_hello映射到这个类的hello
if (cls == NULL) {
return JNI_ERR;
}
if((*env)->RegisterNatives(env,cls,methods,1)) //运行环境env提供这个注册方法,这个方法注册到env环境的cls类中,
return JNI_ERR;
return JNI_VERSION_1_4; //返回jni版本 有1.1 1.2 1.3 1.4
}
- 运行结果
2.3 传递数组
- 举例:实现java传进去int 数组 返回和
- JNIDemo.java
public class JNIDemo{
static { //静态代码块,实例化对象时,只调用一次
/* 1. 加载c库 */
System.loadLibrary("native"); // linux下生成 libnative.so
}
public native static int hello(int[] a); // native 表示本地函数,c/c++中实现的
public static void main(String ares[]){
int[] arr = {1,2,3};
/* 3. 调用 */
System.out.println(hello(arr));
}
}
- 运行java程序,参考生成的jni头文件,修改jni文件源码中JIN描述符和c本地函数声明
- 参考jni.pdf P48 ,数组操作需要env提供的辅助函数,不能直接用,
- native.c
#include <stdio.h>
#include <jni.h>
#if 0
typedef struct {
char *name; /* Java里调用的函数名 */
char *signature; /* JNI字段描述符, 用来表示Java里调用的函数的参数和返回值类型 */
void *fnPtr; /* C语言实现的本地函数 */
} JNINativeMethod;
#endif
JNIEXPORT jint JNICALL c_hello(JNIEnv *env, jobject obj, jintArray arr)
{
jint *carr;
jint i, sum = 0;
carr = (*env)->GetIntArrayElements(env, arr, NULL);
if (carr == NULL) {
return 0; /* exception occurred */
}
for (i=0; i<(*env)->GetArrayLength(env,arr); i++) {
sum += carr[i];
}
(*env)->ReleaseIntArrayElements(env, arr, carr, 0);
return sum;
}
static const JNINativeMethod methods[] = { //注册多个本地函数,对应不同的名字
{"hello", "([I)I", (void *)c_hello},
};
/* 2. map java hello<---------->c c_hello */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
cls = (*env)->FindClass(env, "JNIDemo"); //查找c_hello 对应的 java hello所在的类,表示c_hello映射到这个类的hello
if (cls == NULL) {
return JNI_ERR;
}
if((*env)->RegisterNatives(env,cls,methods,1)) //运行环境env提供这个注册方法,这个方法注册到env环境的cls类中,
return JNI_ERR;
return JNI_VERSION_1_4; //返回jni版本 有1.1 1.2 1.3 1.4
}
- 运行结果
- 举例:实现java传进去int 数组 返回数组,打印每个元素
- JNIDemo.java
public class JNIDemo{
static { //静态代码块,实例化对象时,只调用一次
/* 1. 加载c库 */
System.loadLibrary("native"); // linux下生成 libnative.so
}
public native static int[] hello(int[] a); // native 表示本地函数,c/c++中实现的
public static void main(String ares[]){
int[] arr = {1,2,3};
int[] rev = null;
int i;
/* 3. 调用 */
rev = hello(arr);
for(i=0;i<rev.length;i++)
System.out.println(rev[i]);
}
}
native.c
#include <stdio.h>
#include <jni.h>
#include <stdlib.h>
#if 0
typedef struct {
char *name; /* Java里调用的函数名 */
char *signature; /* JNI字段描述符, 用来表示Java里调用的函数的参数和返回值类型 */
void *fnPtr; /* C语言实现的本地函数 */
} JNINativeMethod;
#endif
JNIEXPORT jintArray JNICALL c_hello(JNIEnv *env, jobject obj, jintArray arr)
{
jint *carr;
jint *oarr;
jint i,n;
jintArray rarr;
carr = (*env)->GetIntArrayElements(env, arr, NULL);
if (carr == NULL) {
return 0; /* exception occurred */
}
n = (*env)->GetArrayLength(env,arr);
oarr = malloc(sizeof(jint)*n);
if(oarr == NULL)
{
(*env)->ReleaseIntArrayElements(env, arr, carr, 0);
return 0;
}
for(i = 0;i<n;i++)
{
oarr[i] = carr[n-1-i];
}
(*env)->ReleaseIntArrayElements(env, arr, carr, 0);
rarr = (*env)->NewIntArray(env,n);
if(rarr == NULL)
return 0;
(*env)->SetIntArrayRegion(env,rarr,0,n,oarr);
return rarr;
}
static const JNINativeMethod methods[] = { //注册多个本地函数,对应不同的名字
{"hello", "([I)[I", (void *)c_hello},
};
/* 2. map java hello<---------->c c_hello */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
cls = (*env)->FindClass(env, "JNIDemo"); //查找c_hello 对应的 java hello所在的类,表示c_hello映射到这个类的hello
if (cls == NULL) {
return JNI_ERR;
}
if((*env)->RegisterNatives(env,cls,methods,1)) //运行环境env提供这个注册方法,这个方法注册到env环境的cls类中,
return JNI_ERR;
return JNI_VERSION_1_4; //返回jni版本 有1.1 1.2 1.3 1.4
}
- 运行结果