JNI使用

一、综述

         JNI Java Native Interface 的缩写,java平台的一部分,它允许Java代码和其他语言写的代码进行交互。它使得在 Java 虚拟机 (VM) 内部运行的 Java 代码能够与用其它编程语言(如 CC++ 和汇编语言)编写的应用程序和库进行交互操作。通俗的来说,就是用来在java代码中访问C/C++代码以及从C/C++访问Java代码的一种技术。这里会以Android上的JNI使用为蓝本通过两个实例,介绍JNI的使用。Java Native Interface (JNI)标准JNI 是本地编程接口,

二、重要数据结构

        在介绍相关知识之前,首先来看看和JNI相关的数据结构。源码通常位于:$JAVA_PAHT/include/jni.h;这里我加入了一些注释,阅读起来更加方便。


2-1 JNINativeMethod数据结构

        这个数据结构通常在JNI函数动态注册的时候会用到。

  • l *name java文件中用native关键字声明的方法名
  • l *signature 方法的签名(稍后会介绍)
  • l *fnPtr 一个void*类型指针,指向本地的方法实现。


2-2 JVM相关数据结构

             这里涉及到两个数据结构,和创建JVM相关,没有做深入研究。

             在图中位列出的还有一个重要的函数 JNI_OnLoad(JavaVM *vm, void *reserved);用于将在java中定义的native函数和在C/C++中实现的函数联系起来。这样,有了函数的声明、定义、实现,我们就可以使用了。


2-3 JNI_OnLoad函数

三、简单例子

        这里会通过两个具体的实例来简单的说明JNI的用法。第一个实例可以算作是一个热身,帮助我们了解编写JNI相关代码的一些步骤、方法。第二个实例则会介绍到从C/C++代码访问到Java代码,以及Java代码中内部类在C/C++中的处理方式。

3.1 Java 访问C/C++

HelloJni.java

public class HelloJni{

    //加载生成的库文件

    static {

        System.loadLibrary("jni");

}

//声明java本地函数,native关键字

    native void printStr(String str);

    public void print(){

        System.out.println("[Java] Hello java!");

    }

    public static void main(String [] args){

        HelloJni my = new HelloJni();

        my.print();

        //调用jni方法

        my.printStr("Hello java");

    }

}


        完成之后,$javac HelloJni.java 生成HelloJni.class文件,在用$javah HelloJni 生成HelloJni.h头文件;在这个.h文件中,仅仅是声明了jni的本地函数,具体实现在C或者CPP文件中。顺带说一下,在编写JNI函数的时候,比较麻烦的是从JavaC/C++的相关函数签名;这里可以使用$javap -s -p HelloJni来查看所有函数的签名。用javah生成.h文件中的函数:

JNIEXPORT void JNICALL Java_HelloJni_printStr (JNIEnv *, jobject, jstring);

        其中JNIEXPORT JNICALL 都是Jni的关键字在jni_md.h中有定义。从java代码到C/C++代码的过程中,JVM扮演者重要的角色,如下图所示


3-1 JVM角色

        当Java代码访问native函数的时候,会由JVM进行映射;依据其明明规则以及函数签名等信息将native void printstr(String);映射为Java_HelloJni_printStr(JNIEnv *, jobject , jstring );因此这才是Java代码可以访问到C/C++代码的真正原因。

//testjni.c文件

#include "HelloJni.h"

JNIEXPORT void JNICALL Java_HelloJni_printStr(JNIEnv *env, jobject obj, jstring str){

const char *mystr = (*env)->GetStringUTFChars(env,str,0);//important ....

printf("[CPP] %s\n",str );

    printf("[CPP] %s\n",mystr );

    return ;

}


        这里需要注意的是const char *mystr = (*env)->GetStringUTFChars(env,str,0);JavaString转成C下的char*JavaString16bitC没有String的概念,并且Cchar*8bit。如果不进行转换,或导致乱码等其他未知问题。

生成动态库

$gcc -shared -Wall -fPIC -o libjni.so testjni.c

注意-shared参数,表明生成动态库,如果不加该参数,回导致程序运行失败。

最后一部就是$java HelloJni运行程序了。这里还需要注意一个问题,java.library.path的值。如果直接运行很有可能出现如下异常,其原因就是在指定的java.library.path中找不到libjni.so

Exception in thread "main" java.lang.UnsatisfiedLinkError: no jni in java.library.path

at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1856)

at java.lang.Runtime.loadLibrary0(Runtime.java:845)

at java.lang.System.loadLibrary(System.java:1084)

at HelloJni.<clinit>(HelloJni.java:3)


通常情况,java.library.path = /usr/java/packages/lib/i386:/usr/lib/jni:/lib:/usr/lib

针对这个异常有如下解决方案,推荐第二种。

1. cp libjni.so java.library.path

2. 用-Djava.library.path=.  $java -Djava.library.path=. HelloJni

运行结果

3-2运行结果


总结一下基本的步骤:

  • ① 编写java代码
  • ② 编译java代码
  • ③ 生成C/C++头文件
  • ④ 编写C/C++代码
  • ⑤ 生成.so共享库
  • ⑥ 运行java程序

3.2 Java代码C/C++代码互相访问

        在上一节我们了解到Java代码要想访问当C/C++代码,就必须借助与JVMJVM在这里扮演这桥梁的角色。那么同理,如果C/C++代码要访问当Java也得借助与JVM。本节将介绍如何在C/C++中借助JVM生成java对象。

3.2.1整体结构


3-3代码结构

        如上图3-3所示,整个程序由JniFuncMain.java$createJniOjbect()开始。调用到Jni的方法来获取java类并创建java对象;完成java类和对象的创建之后,便调用相应的方法进行操作。

3.2.2 Java代码

        Java部分代码分为两个模块,一个部分用于通C/C++进行交互,一部分则用于测试交互的java代码。

//JniFuncMain.java

public class JniFuncMain{

    private static int staticIntField = 300;

    //Load library

    static{

        System.out.println(System.getProperty("java.library.path"));

        System.loadLibrary("jnifunc");

    }

    public static native JniTest createJniObject();

    public static void main(String [] args){

        System.out.println("[Java] createJniObject(),Call Java's native method");

        JniTest jniObj = createJniObject();//usaged the C/C++ Code to create the java Object.

        jniObj.callTest();

        System.out.println("[JAVA]to inner class...");

        JniTest obj = new JniTest();

        obj.callInnerClass();

    }

    public static class JniFuncMainInner{

        public static native void InnerClassNative();

    }

}


        在这里需要注意一点:jniObj对象并不是在java代码中由new关键字创建,而是由对应Jni的函数创建。其实JNI提供了很多的方法来创建java对象,只不过去过程比较麻烦;可以这么来理解:在java中,java封装了对象创建的细节,但在JNI中其创建细节是需要我们去关注的。

3.2.3 C/C++代码

        在介绍相关代码之前,还是来先看看对应的数据结构或者调用接口。如如下图3-4所示:这里仅仅列出了几个接下来代码会用到的接口。

AllocObject(jclass );

NewObject(jclass,jmethodId,...);

NewObjectV(jclass,jmethodID,va_list);

IsInstanceOf(jobject,jclass);



3-4 JNI接口

        其实通过上图以及针对jni.h源代码的阅读,我们便可以了解到其实JNI为我们提供了绝大部分的同java对应的函数。其不同点就是,java用起来比较舒服,直接调用函数就ok;但在JNI中,你得按照相应的构造过程来实例;这个有点儿类似面向对象编程和面向过程编程。

//C++源代码如下所示。

#include "JniFuncMain.h"

#include "JniFuncMain_JniFuncMainInner.h"

JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject(JNIEnv *env, jclass clazz){

    jclass targetClass;

    jmethodID mid;

    jobject newObject;

    //jstring helloStr;

    jfieldID fid;

    jint staticIntField;

    jint result;

    //get JniFuncMain.java's staticIntField

    fid = env->GetStaticFieldID(clazz,"staticIntField","I");

    staticIntField = env->GetStaticIntField(clazz,fid);

    printf("[CPP] get JniFuncMain.java's staticIntField = %d\n",staticIntField);

    targetClass = env->FindClass("JniTest");

    mid = env->GetMethodID(targetClass,"<init>","(I)V");

    printf("[CPP] JniTest object be create..\n");

    newObject = env->NewObject(targetClass,mid,100);

    mid = env->GetMethodID(targetClass,"callByNative","(I)I");

    result = env->CallIntMethod(newObject,mid,200);

    fid = env->GetFieldID(targetClass,"intFields","I");

    printf("[CPP] set JniTest object's intField 200 \n");

    env->SetIntField(newObject,fid,result);

    return newObject;

}

JNIEXPORT void JNICALL 

Java_JniFuncMain_00024JniFuncMainInner_InnerClassNative(JNIEnv *env, jclass clazz){

    printf("[CPP]This method is the Inner Class ...\n");

}

int main(){

    return 0;

}


        代码运行结果如下图3-5所示。


3-5运行结果

        那么我们可以从上面的代码中得出从JNI调用Java函数的基本步骤

         获取java中某个变量的域(field)

    fid = env->GetStaticFieldID(clazz,"staticIntField","I");

    staticIntField = env->GetStaticIntField(clazz,fid);


         获得JavaClass,获取构造函数,生成对象

targetClass = env->FindClass("JniTest");

mid = env->GetMethodID(targetClass,"<init>","(I)V");

newObject = env->NewObject(targetClass,mid,100);


        用生成的对象来调用Java 函数

    mid = env->GetMethodID(targetClass,"callByNative","(I)I");

    result = env->CallIntMethod(newObject,mid,200);


        基本上就是遵循上述步骤。详细的接口可以在jni.h中查找的到!

Android 上的JNI使用

        Android 系统的整个框架如下图 1-1 所示。Android 的应用程序全部是有java代码编写,这些Java代码编译之后会生成Dex类型的(Bytecode)字节码,并借助Dalvik(Android虚拟机)运行,注意是Dalvik不是JVM


图4-1 Android 框架

        由的APPFrameworksLibrarysAndroid Runtime以及Linux Kernel构成了Android的基本架构。APPjava写,Frameworks大部分用javaLibrarysC/C++Linux Kernel毫无疑问用CJNI其实质就是一种Java代码和C/C++代码的桥梁。

        用到JNIjava代码的基本结构如下图4-2 4-3所示(这里以MediaPlayer.java为例)


图4-2 加载库


图4-3 MediaPlayer.java start()函数

        首先需要使用System.loacLibrary(“library name”);来加载该java文件用到的库;然后使用native关键字定义本地方法(C/C++中实现的方法)


图4-4 JNI 函数注册

        在这里,读者需要注意的是,这里C/C++函数的函数名好像很简单,并不像上面介绍的那么复杂。这是因为,上面介绍的使用JNI的静态注册方法,来注册native函数的;而这里要是用显示的注册,很明显不方便。因此这里用到了动态注册的办法。稍微留意一下2-1 JNINativeMethod数据结构

PS:更加详细的资料请查阅

http://developer.android.com/training/articles/perf-jni.html

PS:参考资料

Android框架揭秘》 人民邮电出版社 ,棒子的那本书!

本文的PDF文档       点击这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值