JNI

来源:互联网 发布:淘宝店铺上传不了图片 编辑:程序博客网 时间:2024/06/07 15:26
JNI


1.android使用JNI访问C库。在java程序中定义HardControl.java来声明native方法,在C库编写hardcontrol,c实现对应的c函数。
2.java如何调用C库的函数:加载C库,找到函数,调用函数这三步。其中找到函数这一步是通过java函数和C库中的函数建立映射来实现。
3.新建一个JNIDemo.java类和native.c文件。


首先编写JNIDemo.java:


public class JNIDemo{
static{
/*1.load*/
System.loadLibrary("native"); //libnative.so
}


public native static void hello();


public static void main(String[] args){

/*2.map java hello<-->c c_hello*/


/*3.call*/
hello();
}
}


其中静态代码块中加载了C库。
public native static void hello();这个声明的函数实现来自于本地函数,即C库中实现。


下面来看实现的C库:
#include <jni.h>  /* /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ */
#include <stdio.h>
#include <stdlib.h>
 
#if 0
typedef struct {
    char *name;          /* Java里调用的函数名 */
    char *signature;    /* JNI字段描述符, 用来表示Java里调用的函数的参数和返回值类型 */
    void *fnPtr;          /* C语言实现的本地函数 */
} JNINativeMethod;
#endif


void c_hello(){
printf("Hello World!\n");
}


static const JNINativeMethod methods[] = {
{"hello", "()V", (void *)c_hello},
};


/* System.loadLibrary */
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");
if (cls == NULL) {
return JNI_ERR;
}


/* 2. map java hello <-->c c_hello */
if ((*env)->RegisterNatives(env, cls, methods, 1) < 0)
return JNI_ERR;


return JNI_VERSION_1_4;
}


上述代码中,其中JNI_OnLoad(JavaVM *jvm, void *reserved)这个函数在java代码加载C库的时候被调用,不需要理解,直接拷贝过来即可。
static const JNINativeMethod methods[] = {
{"hello", "([I)[I", (void *)c_hello},
}; 这个结构体描述了C库和java代码中的函数映射。


将这两个文件放入虚拟机中进行编译:
javac JNIDemo.java 得到对应的.class文件。
gcc -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -fPIC -shared -o libnative.so native.c 来编译得到C库, -I选项对应的系统的JNI库, -fPIC 是编译提示要求加的,-o 表示要编译成的C库名。
最后执行java JNIDemo来运行程序,
运行的时候还可能报错找不到库,这时需要执行 export LD_LIBRARY_PATH=. 表示来搜索当前目录来查询这个C库,这样就能调用成功了。
输出Hello World! 说明调用成功。


注意代码段
static const JNINativeMethod methods[] = {
{"hello", "()V", (void *)c_hello},
};称为JNI的字段描述符,描述C库中这个被调用的函数的参数和返回值类型,上面表示没有参数且返回值为空。
编译完java文件后可以使用javah -jni JNIDemo命令来生成头文件,里面就包含了C库中的字段描述符信息,依次来编写字段描述符。


如果对java代码进行修改,改为:public native int hello(String str,int []a);重新编译java文件,并且使用javah -jni JNIDemo 生成头文件。头文件中这个函数对应的字段描述符为:
/*
 * Class:     JNIDemo
 * Method:    hello
 * Signature: (Ljava/lang/String;[I)I
 */
JNIEXPORT jint JNICALL Java_JNIDemo_hello__Ljava_lang_String_2_3I
  (JNIEnv *, jobject, jstring, jintArray);


另外之前native.c文件中的函数写法有错:
void c_hello(){
printf("Hello World!\n");
}
即使对应的调用的java文件中这个函数没有传参,C文件中也要加上两个变量,写法如下:
void c_hello(JNIEnv *env,jobject cls){
printf("Hello World!\n");
}


2.传入参数为int型这种基本数据类型时:
接下来对hello函数进行修改,有一个入参和一个返回值。
java文件中为:public native static  int hello(int m);
C文件中为:
jint c_hello(JNIEnv *env, jobject cls, jint m)
{
printf("Hello world!val = %d\n",m);
return 100;
}
static const JNINativeMethod methods[] = {
{"hello", "(I)I", (void *)c_hello},
};
这样调用的时候传入一个int型的数据,函数返回值就是100这个int型的数据。


3.传入参数为String这种类时,直接使用字符串变量时会报错,如下面这种写法是错误的:
jstring c_hello(JNIEnv *env, jobject cls, jstring m)
{
printf("Hello world!val = %s",m);
return "return from c";
}
需要通过env这个变量中的方法才行,写法如下:
jstring c_hello(JNIEnv *env, jobject cls, jstring str)
{
const jbyte *cstr;
cstr = (*env)->GetStringUTFChars(env,str,NULL);
if(cstr == NULL){
return NULL;
}
printf("Get string from java:%s\n",cstr);
(*env)->ReleaseStringUTFChars(env,str,cstr);
return (*env)->NewStringUTF(env,"return from C");
}
使用上述的写法就可以获取到字符串和返回字符串到java程序中了。


4.传入参数为数组的示例:
java代码中声明为:public native static int hello(int[] m);
编译,生成头文件:
javac JNIDemo.java 
javah -jni JNIDemo
得到头文件JNIDemo.h 如下:
/*
 * Class:     JNIDemo
 * Method:    hello
 * Signature: ([I)I
 */
JNIEXPORT jint JNICALL Java_JNIDemo_hello
  (JNIEnv *, jclass, jintArray);


依此修改C文件:
jint c_hello(JNIEnv *env, jobject cls, jintArray arr)
{
jint *carr;
jint i,sum=0;
carr=(*env)->GetIntArrayElements(env,arr,NULL);
if(carr == NULL){
return 0;
}
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},
};


编译后,java文件中传入参数[1,2,3]结果为6,表明C文件中确实对数组进行求和操作,符合要求。


5.传入的参数为数组,返回值也是数组,C文件修改如下:
jintArray c_hello(JNIEnv *env, jobject cls, jintArray arr)
{
jint *carr;
jint *oarr;
jintArray rarr;
jint i,n=0;
carr=(*env)->GetIntArrayElements(env,arr,NULL);
if(carr == NULL){
return 0;
}

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);

//create jintArray
rarr=(*env)->NewIntArray(env,n);
if(rarr == NULL){
return 0;
}

//将数组的值拷贝到jintArray结构中并返回
(*env)->SetIntArrayRegion(env,rarr,0,n,oarr);
free(oarr);//释放对应的malloc
return rarr;
}
static const JNINativeMethod methods[] = {
{"hello", "([I)[I", (void *)c_hello},
};


上述代码实现的是数组的倒序功能。



















原创粉丝点击