Java JNI介绍及JNI在Domino中的使用

来源:互联网 发布:光翼学园网络班有用吗 编辑:程序博客网 时间:2024/06/09 18:04

    JNI是Java Native Interface的缩写,中文为JAVA本地调用。从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。

    只有在必须的情况下才使用JNI,如下三种情况可作为参考:

    1. 你的应用需要访问系统的各个特性和设备,这些特性和设备通过Java平台是无法访问的。

    2. 你已经有了大量的测试过和调试过的用另一种语言编写的代码,并且知道如何将其导出到所有的目标平台上。

    3. 通过基准测试,你已经发现所编写的Java代码比其他语言编写的等价代码要慢的多。


    使用Java JNI需要如下步骤:

1. 编写Java类,以简单的输出“Hello Native World”为例子

/** * Native例子 *  * @author weijielu */public class HelloNative {        /**     * native表示此方法是个本地方法,提醒编译器该方法将在外部     */    public static native void greeting();}


2. 编写一个相应的C函数(其实不需要手动编写,用javah程序即可自动生成)

    2.1 首先编译HelloNative.java,用命令"javac HelloNative.java"

    2.2 执行命令“jjavah -classpath C:\dde_root\90_ws\javaAPI\bin nativetest.HelloNative形成相应的C头文件(javah可以在jdk/bin目录下找到)

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class nativetest_HelloNative */#ifndef _Included_nativetest_HelloNative#define _Included_nativetest_HelloNative#ifdef __cplusplusextern "C" {#endif/* * Class:     nativetest_HelloNative * Method:    greeting * Signature: ()V */JNIEXPORT void JNICALL Java_nativetest_HelloNative_greeting  (JNIEnv *, jclass);#ifdef __cplusplus}#endif#endif

3. 用C或者C++实现该本地方法

 如果使用C++实现本地方法,必须将实现本地方法的函数声明为 extern "C",以阻止C++编译器生成C++特有的代码

#include "HelloNative.h"#include <stdio.h>JNIEXPORT void JNICALL Java_nativetest_HelloNative_greeting(JNIEnv* env, jclass cl){    printf("Hello Native World!\n");}

4.  编译本地代码并将其置于共享库中

5. 修改Java程序加载该类库

package nativetest;/** * Native例子 *  * @author weijielu */public class HelloNative {        static{        System.loadLibrary("HelloNative");    }        /**     * native表示此方法是个本地方法,提醒编译器该方法将在外部     */    public static native void greeting();}

6. 执行Java程序即可看到本地代码输出的东东了。


总结流程图:

            


    

下面就是我们下图中,红色椭圆形中的使用JNI的介绍了

    

    众所周知我们Domino使用C/C++实现的,其对外提供的C/C++接口已经十分成熟,且执行效率很高。考虑到上述第2和第3条,对外提供Java API必然使用JNI。

    以Java API中类lotus.domino.Database的remove方法为例介绍代码

1. 编写lotus.domino.Database类,类中有remove方法

package lotus.domino.local;public class Database extends NotesBaseimplements lotus.domino.Database{private native void Nremove(); // native表明这是本地方法public void remove()throws NotesException{synchronized(this){this.CheckObject();Nremove();markInvalid();}}}

2. 本地andb.hpp文件(奇怪,在相应的hpp文件里没有相应的Nremove)

3.  本地andb.cpp文件  (贱人起得这个难找难记的文件名字)

JNIEXPORT void JNICALL Java_lotus_notes_Database_Nremove  (JNIEnv *env, jobject jthis){Java_lotus_domino_local_Database_Nremove(env, jthis);}JNIEXPORT void JNICALL Java_lotus_domino_local_Database_Nremove  (JNIEnv *env, jobject jthis){lptr(ANDatabase) db;JAVA_WRITE_BLOCK_BEGIN(jthis)LSMSG_PARAM objptr = ANotes::ANExtractPointer(env, jthis);if (objptr){      JAVA_PROFILE_METHOD(mysession, objptr, CNOTES_DBMETH_REMOVE);db = (lptr(ANDatabase))objptr;db->ANDDeleteDb(NULL);    JAVA_PROFILE_END}JAVA_BLOCK_END;}

4. 类库的加载我们有一套机制,在此不再赘余

5. 在此我们即可在Java中使用lotus.domino.local.Database类中的 remove()方法了。


多说两句:Domino对外提供Java API使用JNI有一个JNI的共通的问题,即C/C++代码有内存泄露的风险,当Java API使用完一个Domino Java类的时候必须主动告诉C/C++进行对象的内存释放,即调用Java中的recycle()方法。由于Java中的对象是不需要主动回收的,Java虚拟机会进行回收,所有Java程序员使用Domino Java API很容易忘记最后调用recycle()方法。我们的REST Service中的调用Java API的一块代码就是因为忘记recycle导致在性能测试的时候使得内存耗尽而使Domino 崩溃。典型的调用recycle()方法如下:

protected boolean formInDB(String extFileName) throws NotesException {boolean bFound = false;Database dbTr = null;DocumentCollection dc = null;try{dbTr = getPrimaryServiceManager().getDatabase(TRDB);if(Verify.notNull("There should be " + TRDB + " on Server: " + getPrimaryServiceManager().fqih, dbTr)){String sQuery = "FORM = \"ExtensionFiles\" & extensionFileName = \"" + extFileName + "\"";dc = dbTr.search(sQuery);bFound = !(dc.getCount() == 0);}else{bFound = false;}}catch(NotesException e){log("ERROR: Problems occur when search document in tr.nsf");e.printStackTrace();}finally{if(dc != null){dc.recycle();}if(dbTr != null){dbTr.recycle();}}return bFound;}


0 0