JNI设计实践之路

JNI设计实践之路

作者:杨小华  

一、 前言

本文为在 32 位 Windows 平台上实现 Java 本地方法提供了实用的示例、步骤和准则。本文中的示例使用 Sun公司的 Java Development Kit (JDK) 版本 1.4.2。 用 C ++语言编写的本地代码是用 Microsoft Visual C++  6.0编译器编译生成。规定在Java程序中function/method称为方法,在C++程序中称为函数。

本文将围绕求圆面积逐步展开,探讨java程序如何调用现有的DLL?如何在C++程序中创建,检查及更新Java对象?如何在C++和Java程序中互抛异常,并进行异常处理?最后将探讨Eclipse及JBuilder工具可执行文件为什么不到100K大小以及所采用的技术方案?

二、 JNI基础知识简介

Java语言及其标准API应付应用程序的编写已绰绰有余。但在某些情况下,还是必须使用非Java代码,例如:打印、图像转换、访问硬件、访问现有的非Java代码等。与非Java代码的沟通要求获得编译器和JVM的专门支持,并需附加的工具将Java代码映射成非Java代码。目前,不同的开发商为我们提供了不同的方案,主要有以下方法:

1. JNI(Java Native Interface)

2. JRI(Java Runtime Interface)

3. J/Direct

4. RNI(Raw Native Interface)

5. Java/COM集成方案

6. CORBA(Common Object Request Broker Architecture)

其中方案1是JDK自带的一部分,方案2由网景公司所提供,方案3 、 4 、 5是微软所提供的方案,方案6是一家非盈利组织开发的一种集成技术,利用它可以在由不同语言实现的对象之间进行“相互操作”的能力。

在开发过程中,我们一般采用第1种方案――JNI技术。因为只用当程序用Microsoft Java编译器编译,而且只有在Microsoft Java虚拟机(JVM)上运行的时候,才采用方案3 、 4 、 5。而方案6一般应用在大型的分布式应用中。

       JNI是一种包容极广的编程接口,允许我们从Java应用程序里调用本地化方法。也就是说,JNI允许运行在虚拟机上的Java程序能够与其它语言(例如C/ C++/汇编语言)编写的程序或者类库进行相互间的调用。同时JNI也提供了一整套的API,允许将Java虚拟机直接嵌入到本地的应用程序中。其中JNI所扮演的角色可用图一描述:

图一 JNI基本结构描述图

目前JNI只能与用C和C++编写的本地化方法打交道。利用JNI,我们本地化方法可以:

1. 创建、检查及更新Java对象

2. 调用Java和非Java程序所编写的方法(函数),以及win32 API等.

3. 捕获和抛出“异常”

4. 装载类并获取类信息

5. 进行运行期类型检查

所以,原来在Java程序中能对类及对象所做的几乎所有事情都可以在本地化方法中实现。

下图表示了通过JNI,Java程序和非Java程序相互调用原理。

图二   Java程序和非Java程序通过JNI相互调用原理

通过JNI,编写Java程序调用非Java程序一般步骤:

1.) 编写对本地化方法及自变量进行声明的Java代码

2.) 利用头文件生成器javah生成本地化方法对应的头文件

3.) 利用C和C++实现本地化方法(可调用非Java程序),并编译、链接生成DLL文件

4.) Java程序通过生成的DLL调用非Java程序

同时我们也可以通过JNI,将Java虚拟机直接嵌入到本地的应用程序中,步骤很简单,只需要在C/C++程序中以JNI API函数为媒介调用Java程序。

以上步骤虽简单,但有很多地方值得注意。如果一招不慎,可能造成满盘皆输。

三、 Java程序调用非Java程序

3.1 本地化方法声明及头文件生成

任务:现有一求圆面积的Circle.dll(用MFC编写,参数:圆半径返回值:圆面积)文件,在Java程序中调用该Dll。

在本地化声明中,可分无包和有包两种情况。我们主要对有包的情况展开讨论。

实例1:

package com.testJni;

public class Circle

{

   public native void cAreas(int radius) ;

   static

   {

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

        System.loadLibrary("CCircle");

    }

}

在Java程序中,需要在类中声明所调用的库名称System.loadLibrary( String libname );

该函数是将一个Dll/so库载入内存,并建立同它的链接。定位库的操作依赖于具体的操作系统。在windows下,首先从当前目录查找,然后再搜寻”PATH”环境变量列出的目录。如果找不到该库,则会抛出异常UnsatisfiedLinkError。库的扩展名可以不用写出来,究竟是Dll还是so,由系统自己判断。这里加载的是3.2中生成的DLL,而不是其他应用程序生成的Dll。还需要对将要调用的方法做本地声明,关键字为native。表明此方法在本地方法中实现,而不是在Java程序中,有点类似于关键字abstract。

我们写一个Circle.bat批处理文件编译Circle.java文件,内容如下(可以用其他工具编译):

javac -d . Circle.java

javah com.testJni.Circle

pause

对于有包的情况一定要注意这一点,就是在用javah时有所不同。开始时我的程序始终运行都不成功,问题就出在这里。本类名称的前面均是包名。这样生成的头文件就是:com_testJni_Circle.h。开始时,在包含包的情况下我用javah Circle生成的头文件始终是Circle.h。在网上查资料时,看见别人的头文件名砸那长,我的那短。但不知道为什么,现在大家和我一样知道为什么了吧。:)。

如果是无包的情况,则将批处理文件换成如下内容:

javac Circle.java

javah Circle

pause

3.2 本地化方法实现

刚才生成的com_testJni_Circle.h头文件内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class com_testJni_Circle */

#ifndef _Included_com_testJni_Circle

#define _Included_com_testJni_Circle

#ifdef __cplusplus

extern "C" {

#endif

/*

 * Class:     com_testJni_Circle

 * Method:    cAreas

 * Signature:  (I)V

 */

JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas

 (JNIEnv *, jobject, jint);

#ifdef __cplusplus

}

#endif

#endif

如果在本地化方法声明中,方法cAreas ()声明为static类型,则与之相对应的Java_com_testJni_Circle_cAreas()函数中的第二个参数类型为jclass。也就是

JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *env, jclass newCircle,jint radius)。

这里JNIEXPORTJNICALL都是JNI的关键字,其实是一些宏(具体参看jni_md.h文件)。

从以上头文件中,可以看出函数名生成规则为:Java[ _包名]_类名_方法名[ _函数签名](其中[ ]是可选项),均以字符下划线( _ )分割。如果是无包的情况,则不包含[ _包名]选项。如果本地化方法中有方法重载,则在该函数名最后面追加函数签名,也就是Signature对应的值,函数签名参见表一。

函数签名

Java类型

V

void

Z

boolean

B

byte

C

char

S

short

I

int

J

long

F

float

D

double

L fully-qualified-class ;

fully-qualified-class

[ type

type[]

( arg-types ) ret-type

method type

表一函数签名与Java类型的映射

在具体实现的时候,我们只关心函数原型:

JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *, jobject, jint);

现在就让我们开始激动人心的一步吧 : ) 。启动VC集成开发环境,新建一工程,在project里选择win32 Dynamic-link Library,输入工程名,然后点击ok,接下去步骤均取默认(图三)。如果不取默认,生成的工程将会有DllMain ()函数,反之将无这个函数。我在这里取的是空。

图三 新建DLL工程

然后选择菜单File->new->Files->C++ Source File,生成一个空*.cpp文件,取名为CCircle。与3.1中System.loadLibrary("CCircle");参数保持一致。将JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *, jobject, jint);拷贝到CPP文件中,并包含其头文件。

对应的CCircle.cpp内容如下:

#include<iostream.h>

#include"com_testJni_Circle.h"

#include"windows.h"

JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *env, jobject newCircle,jint radius)

{

     //调用求圆面积的Circle.dll

     typedef void (*PCircle)(int radius);

     HINSTANCE hDLL;

     PCircle Circle;

     hDLL=LoadLibrary("Circle.dll");//加载动态链接库Circle.dll文件

     Circle=(PCircle)GetProcAddress(hDLL,"Circle");

     Circle(8);

     FreeLibrary(hDLL);//卸载Circle.dll文件;

}

在编译前一定要注意下列情况。

注意:一定要把SDK目录下include文件夹及其下面的win32文件夹中的头文件拷贝到VC目录的include文件夹下。或者在VC的tools/options/directories中设置,如图四所示。

图四 头文件设置

我们知道dll文件有两种指明导出函数的方法,一种是在.def文件中定义,另一种是在定义函数时使用关键字__declspec(dllexport)。而关键字JNIEXPORT实际在jni_md.h中如下定义,#define JNIEXPORT __declspec(dllexport),可见JNI默认的导出函数使用第二种。使用第二种方式产生的导出函数名会根据编译器发生变化,在有的情况下会发生找不到导出函数的问题(我们在java控制台程序中调用很正常,但把它移植到JSP页面时,就发生了该问题,JVM开始崩溃,百思不得其解,后来加入一个.def文件才解决问题)。其实在《windows 核心编程》一书中,第19.3.2节就明确指出创建用于非Visual C++工具的DLL时,建议加入一个def文件,告诉Microsoft编译器输出没有经过改变的函数名。因此最好采用第一种方法,定义一个.def文件来指明导出函数。本例中可以新建一个CCircle.def文件,内容如下:

; CCircle.def : Declares the module parameters for the DLL.

LIBRARY      "CCircle"

DESCRIPTION 'CCircle Windows Dynamic Link Library'

EXPORTS

    ; Explicit exports can go here

    Java_com_testJni_Circle_cAreas

现在开始对所写的程序进行编译。选择build->rebuild all对所写的程序进行编译。点击build->build CCirclee.DLL生成DLL文件。

也可以用命令行cl来编译。语法格式参见JDK文档JNI部分。

再次强调(曾经为这个东西大伤脑筋):DLL放置地方

1) 当前目录。

2) Windows的系统目录及Windows目录

3) 放在path所指的路径中

4) 自己在path环境变量中设置一个路径,要注意所指引的路径应该到.dll文件的上一级,如果指到.dll,则会报错。

下面就开始测试我们的所写的DLL吧(假设DLL已放置正确)。

import com.testJni.Circle;

public class test

{

   public static void main(String argvs[])

   {

      Circle myCircle;

      myCircle = new Circle();

      myCircle.cAreas(2);

   }     

}

编译,运行程序,将会弹出如下界面:

图五 运行结果

以上是我们通过JNI方法调用的一个简单程序。实际情况要比这复杂的多。

现在开始来讨论JNI中参数的情况,我们来看一个程序片断。

实例二:

JNIEXPORT jstring JNICALL Java_MyNative_cToJava

(JNIEnv *env, jclass obj)

{

              jstring jstr;

char str[]="Hello,word!/n";

jstr=env->NewStringUTF(str);

return jstr;

}

在C和Java编程语言之间传送值时,需要理解这些值类型在这两种语言间的对应关系。这些都在头文件jni.h中定义,用typedef语句声明了这些类在目标平台上的代价类。头文件也定义了常量如:JNI_FALSE=0 和JNI_TRUE=1;表二和表三说明了Java类型和C类型之间的映射关系。

Java语言

C/C++语言

bit位数

boolean

jboolean

8 unsigned

byte

jbyte

8

char

jchar

16 unsigned

short

jshort

16

int

jint

32

long

jlong

64

float

jfloat

32

double

jdouble

64

void

void

0

表二   Java基本类型到本地类型的映射

表三 Java中的类到本地类的映射

JNI函数NewStringUTF()是从一个包含UTF格式编码字符的char类型数组中创建一个新的jstring对象。jstring是以JNI为中介使Java的String类型与本地的string沟通的一种类型,我们可以视而不见 (具体对应见表二和表三)。如果你使用的函数是GetStringUTFChars()(将jstring转换为UTF-8字符串),必须同时使用ReleaseStringUTFChars()函数,通过它来通知虚拟机去回收UTF-8串占用的内存,否则将会造成内存泄漏,最终导致系统崩溃。因为JVM在调用本地方法时,是在虚拟机中开辟了一块本地方法栈供本地方法使用,当本地方法使用完UTF-8串后,得释放所占用的内存。其中程序片断jstr=env->NewStringUTF(str);是C++中的写法,不必使用env指针。因为JNIEnv函数的C++版本包含有直接插入成员函数,他们负责查找函数指针。而对于C的写法,应改为:jstr=(*env)->NewStringUTF(env,str);因为所有JNI函数的调用都使用env指针,它是任意一个本地方法的第一个参数。env指针是指向一个函数指针表的指针。因此在每个JNI函数访问前加前缀(*env)->,以确保间接引用函数指针。

C/C++和Java互传参数需要自己在编程过程中仔细摸索与体味。

四、 C/C++访问Java成员变量和成员方法

我们修改3.1中的Java程序声明,加入如下代码:

   private int circleRadius;

   public Circle()

   {

       circleRadius=0;

   }

   public void setCircleRadius(int radius)

   {

       circleRadius=radius;

   }

   public void javaAreas()

   {

       float PI = 3.14f;

       if(circleRadius<=0)

       {

           System.out.println (“error!”);

       }

       else

       {

          System.out.println (PI*circleRadius*circleRadius);

       }      

    }

在C++程序中访问Circle类中的private私有成员变量circleRadius,并设置它的值,同时调用Java方法javaAreas()。在函数Java_com_testJni_Circle_cAreas()中加入如下代码:

jclass circle;

       jmethodID AreasID;

       jfieldID radiusID;

jint newRadius=5;

       circle = env->GetObjectClass(newCircle);//get current class

       radiusID=env->GetFieldID(circle,"circleRadius","I");//get field ID

       env->SetIntField(newCircle,radiusID,newRadius);//set field value

       AreasID=env->GetMethodID(circle,"javaAreas","()V");//get method ID

       env->CallVoidMethod(newCircle,AreasID,NULL);//invoking method

在C++代码中,创建、检查及更新Java对象,首先要得到该类,然后再根据类得到其成员的ID,最后根据该类的对象,ID号调用成员变量或者成员方法。

得到类,有两个API函数,分别为FindClass()和GetObjectClass();后者顾名思义用于已经明确知道其对象,然后根据对象找类。前者用于得到没有实例对象的类。这里也可以改成circle = env-> FidnClass("com/testJni/Circle");其中包的分隔符用字符" /"代替。如果已知一个类,也可以在C++代码中创建该类对象,其JNI函数为NewObject();示例代码如下:

jclass      circle =env->FindClass("com/testJni/ Circle ");

jmethodID circleID=env->GetMethodID(circle,"<init>","()V");//得到构造函数的ID

jobject newException=env->NewObject(circle, circleID,NULL);

得到成员变量的ID,根据其在Java代码中声明的类型不同而不同。具体分为两大类:非static型和static型,分别对应GetFieldID()和GetStaticFieldID()。同时也可以获得和设置成员变量的值,根据其声明的type而变化,获得其值的API函数为:GettypeField()和GetStatictypeField();与之相对应的设置其值的函数为SettypeField()和SetStatictypeField();在本例中,成员变量circleRadius声明成int型,则对应的函数分别为GetIntField()和SetIntField();

其实JNI API函数名是很有规律的,从上面已窥全貌。获得成员方法的ID也是同样的分类方法。具体为GetMethodID()和GetStaticMethodID()。调用成员方法跟获得成员变量的值相类似,也根据其方法返回值的type不同而不同,分别为CalltypeMethod()和CallStatictypeMethod()。对于返回值为void的类型,其相应JNI函数为CallVoidMethod();

以上获得成员ID函数的形参均一致。第一个参数为jclass,第二个参数为成员变量或方法,第三个参数为该成员的签名(签名可参见表一)。但调用或设置成员变量或方法时,第一个参数为实例对象(即jobject),其余形参与上面相同。

特别要注意的是得到构造方法的ID时,第二个参数不遵循上面的原则,为jmethodID constructorID = env->GetMethodID(jclass, "<init>"," 函数签名");

从上面代码中可以看出,在C++中可以访问java程序private类型的变量,严重破坏了类的封装原则。从而可以看出其不安全性。

五、 异常处理

本地化方法稳定性非常差,调用任何一个JNI函数都会出错,为了程序的健壮性,非常有必要在本地化方法中加入异常处理。我们继续修改上面的类。

我们声明一个异常类,其代码如下:

package com.testJni;

import com.testJni.*;

public class RadiusIllegal extends Exception

{

    protected String MSG="error!";

    public RadiusIllegal(String message)

    {

        MSG=message;

    }

    public void print()

    {

       System.out.println(MSG);

    }

}

同时也修改Circle.java中的方法,加入异常处理。

    public void javaAreas() throws RadiusIllegal    //修改javaAreas(),加入异常处理

   {

       float PI = 3.14f;

       if(circleRadius<=0)

       {

           throw new RadiusIllegal("warning:radius is illegal!");

       }

       else

       {

            System.out.println (PI*circleRadius*circleRadius);

       }      

   }

   public native void cAreas(int radius) throws RadiusIllegal;    //修改cAreas (),加入异常处理

修改C++代码中的函数,加入异常处理,实现Java和C++互抛异常,并进行异常处理。

   JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *env, jobject newCircle,jint radius)

{

       //此处省略部分代码

       radiusIllegal=env->FindClass("com/testJni/RadiusIllegal");//get the exception class

       if((exception=env->ExceptionOccurred())!=NULL)

       {

              cout<<"errors in com_testJni_RadiusIllegal"<<endl;

              env->ExceptionClear();

       }    

       //此处省略部分代码

       env->CallVoidMethod(newCircle,AreasID,NULL);//invoking

       if((exception=env->ExceptionOccurred())!=NULL)

       {

              if(env->IsInstanceOf(exception,radiusIllegal)==JNI_TRUE)

              {                  

                     cout<<"errors in java method"<<endl;

                     env->ExceptionClear();

              }

              else

              {

                     cout<<"errors in invoking javaAreas() method of Circle"<<endl;

                     env->ExceptionClear();

              }

        }   

        if(radius<=0)

        {

              env->ThrowNew(radiusIllegal,"errors in C function!");//throw exception

         return ;

        }

        else

        {

              //此处为调用计算圆面积的DLL

        }

}

在本地化方法(C++)中,可以自己处理异常,也可以重新抛出异常,让Java程序来捕获该异常,进行相关处理。

如果调用JNI函数发生异常,不及时进行处理,再次调用其他JNI函数时,可能会使JVM崩溃(crash),

大多数JNI函数都具有此特性。可以调用函数ExceptionOccurred()来判断是否发生了异常。该函数返回jthrowable的实例对象,如本例if((exception=env->ExceptionOccurred())!=NULL)就用来判断是否发生了异常。当要判断具体是哪个异常发生时,可以用IsInstanceOf()来进行测试,此函数非彼IsInstanceOf(Java语言中的IsInstanceOf)。在上面的代码中,我们在本地化方法中给circleRadius设置了一非法值,然后调用方法javaAreas(),此时java代码会抛出异常,在本地化方法中进行捕获,然后用IsInstanceOf()来进行测试是否发生了RadiusIllegal类型的异常,以便进行相关处理。在调用其他JNI函数之前,应当首先清除异常,其函数为ExceptionClear()。

如果是C++的程序发生异常,则可以用JNI API函数ThrowNew()抛出该异常。但此时本地化方法并不返回退出,直到该程序执行完毕。所以当在本地化方法中发生异常时,应该人为的退出,及时进行处理,避免程序崩溃。函数ThrowNew()中第一个参数为jclass的类,第二个参数为附加信息,用来描述异常信息。

如果要知道异常发生的详细信息,或者对程序进行调试时,可以用函数ExceptionDescribe()来显示异常栈里面的内容。

六、 MFC程序中嵌入Java虚拟机

可能大家天天都在用Eclipse和Jbulider这两款优秀的IDE进行程序开发,可能还不知道他们的可执行文件不到100KB大小,甚则连一副图片都可能比他们大。其实隐藏在他们背后的技术是JNI,可执行文件只是去启动Java程序,所以也只有那么小。

我们只需要在MFC程序中创建一个JVM,然后基于这个JVM调用Java的方法,启动Java程序,就可以模拟出Eclipse和Jbulider的那种效果,使java程序更专业。其实要实现这种效果,用上面的方法足有够有。创建JVM,只需包含相应的类库,设置相关的属性。

首先进行环境设置,在VC环境的tools-->options-->Directories下的Library files选项中包含其创建JVM的库文件jvm.lib,该库文件位于JDK / lib目录下,如图6所示:

图六库文件路径设置

然后,在环境变量path中设置jvm.dll的路径。该Dll      位于jdk/jre/bin/server目录或jdk/jre/bin/client目录下。注意:一定不要将jvm.dll和jvm.lib拷贝到你应用程序路径下,这样会引起JVM初始化失败。因为Java虚拟机是以相对路径来寻找和调用用到的库文件和其他相关文件。

接下来,我们在MFC程序(该程序请到《程序员》杂志频道下载)中进行创建JVM初始化工作。示例代码如下:

       JNIEnv *env;

       JavaVM *jvm;

       jint res;

       jclass cls;

       jmethodID mid;      

       JavaVMInitArgs vm_args;

       JavaVMOption options[3];

       memset(&vm_args, 0, sizeof(vm_args));    

     //进行初始化工作

       options[0].optionString = "-Djava.compiler=NONE";

       options[1].optionString = "-Djava.class.path=.";

       options[2].optionString = "-verbose:jni";      

       vm_args.version=JNI_VERSION_1_4;       //版本号设置

       vm_args.nOptions = 3;

       vm_args.options = options;

       vm_args.ignoreUnrecognized = JNI_TRUE;

       res = JNI_CreateJavaVM(&jvm,(void**)&env,&vm_args); //创建JVM

       if (res < 0)

       {

              MessageBox( "Can't create Java VM","Error",MB_OK|MB_ICONERROR);

              exit(1);

       }

       cls = env->FindClass("prog");

       if(env->ExceptionOccurred()!=NULL)

       {

              MessageBox( "Can't find Prog class!","Error",MB_OK|MB_ICONERROR);

              exit(1);

       }

       mid = env->GetStaticMethodID(cls, "main", "([Ljava/lang/String;)V");

       if(env->ExceptionOccurred()!=NULL)

       {

              MessageBox("Can't find Prog.main!","Error",MB_OK|MB_ICONERROR);

              exit(1);

       }

       env->CallStaticVoidMethod( cls, mid, NULL); //调用Java程序main()方法,启动Java程序

       if(env->ExceptionOccurred()!=NULL)

       {

              MessageBox( "Fatal Error!","Error",MB_OK|MB_ICONERROR);

              exit(1);

       }

       jvm->DestroyJavaVM();//释放JVM资源

程序首先进行JVM初始化设置。我们观察jni.h 文件关于JavaVMOption和JavaVMInitArgs的定义

typedef struct JavaVMOption {

    char *optionString;

    void *extraInfo;

} JavaVMOption;

typedef struct JavaVMInitArgs {

    jint version;

    jint nOptions;

    JavaVMOption *options;

    jboolean ignoreUnrecognized;

} JavaVMInitArgs;

结构体JavaVMInitArgs中有四个参数,我们在程序中都得必须设置。其中版本号一定要设置正确,不同的版本有不同的设置方法,关于版本1.1和1.2的设置方法参看sun公司的文档,这里只给出版本1.4的设置方法。第二个参数表示JavaVMOption结构体变量的维数,这里设置为三维,其中options[0].optionString = "-Djava.compiler=NONE";表示disable JIT;options[1].optionString = "-Djava.class.path=.";表示你所调用Java程序的Class文件的路径,这里设置为该exe应用程序的根路径(最后一个字符"."表示根路径);options[2].optionString = "-verbose:jni";用于跟踪运行时的信息。第三个参数是一个JavaVMOption的指针变量。第四个参数意思我们可以参看帮助文档的解释If ignoreUnrecognized is JNI_FALSE, JNI_CreateJavaVM returns JNI_ERR as soon as it encounters any unrecognized option strings。

初始化完毕后,就可以调用创建JVM的函数jint JNICALL JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);如果返回值小于0表示创建JVM失败。最可能的原因就是jvm.dll和jvm.lib设置错误。

如果在运行的过程中找不到java程序的类,那么就是-Djava.class.path设置错误。只要JVM创建成功,就可以根据上面的方法调用java程序。最后当程序结束后,调用函数DestroyJavaVM()摧毁JVM,释放资源。

七、 附录

利用JNI函数,我们可以从本地化方法的内部与JVM打交道。正如在前面的例子中所看到的那样,每个本地化方法中都会接收一个特殊的自变量作为自己的第一个参数:JNIEnv――它是指向类型为JNIEnv_的一个特殊JNI数据结构的指针。JNI数据结构的一个元素是指向由JVM生成的一个指针的数组;该数组的每个元素都是指向一个JNI函数的指针。可以从本地化方法的内部对JNI函数的调用。第二个参数会根据Java类中本地方法的定义不同而不同,如果是定义为static方法,类型会是jclass,表示对特定Class对象的引用,如果是非static方法,类型是jobject,表示当前对象的引用,相当于” this”。可以说这两个变量是本地化方法返回JAVA的大门。

注意:在本地化方法中生成的Dll不具备到处运行的特性,而具有”牵一发而动全身”的特点。只要包名一改变,那么你所有的工作就得重新做一遍。原因就是当用javah生成头文件时,函数名的生成规则为Java[ _包名]_类名_方法名[ _函数签名];当你的包名改变时,生成的函数名也跟着改变了,那么你再次调用以前编写的Dll时,会抛出异常。

八、 参考文献

1. 《Java 编程思想》Bruce Eckel 机械工业出版社

2. 《Java2 核心技术卷2》(第6版)Cay S.Horstmann,Gary Cornell机械工业出版社

3. 《高级Java2 大学教程》(英文版) Harvey M.Deitel ,Paul J.Deitel,Sean E.Santry 电子工业出版社

4. 《windows 核心编程》Jeffrey Richter 机械工业出版社

5. http://www.jguru.com

6. sun公司文档

如对本文有任何疑问和异议,欢迎与作者探讨:normalnotebook@126.com

注:本文最初发表在2004年《开发高手》第12期上。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术专栏

慕课网Flask高级编程实战-10.鱼书业务处理

我们的首页会显示最近的赠送书籍列表。这个列表有三个限制条件: 1.数量不超过30 2.按照时间倒序排列,最新的排在最前面 3.去重,同一本书籍的礼物不重复...

1563
来自专栏程序员互动联盟

【专业技术】程序在内存中如何分配的?

好多初学者可能对程序在内存中如何布局都有疑问,在我们和用户的沟通过程中也发现有好多同学问相关的问题。这里转一个文章,讲得很不错的,大家可以看一下。 栈主要用来存...

2316
来自专栏码洞

如履薄冰 —— Redis懒惰删除的巨大牺牲

之前我们介绍了Redis懒惰删除的特性,它是使用异步线程对已经删除的节点进行延后内存回收。但是还不够深入,所以本节我们要对异步线程逻辑处理的细节进行分析,看看A...

871
来自专栏JackieZheng

FreeMarker模板开发指南知识点梳理

freemarker是什么? 有什么用? 怎么用? (问得好,这些都是我想知道的问题) freemarker是什么?   FreeMarker 是一款 模板引擎...

2599
来自专栏芋道源码1024

Java初中级面试题(2)

5977
来自专栏木木玲

设计模式 ——— 状态模式

1242
来自专栏C/C++基础

Protocol Buffers C++入门教程

protobuf(Protocol Buffers )是Google的开源项目,是Google的中立于语言、平台,可扩展的用于序列化结构化数据的解决方案。官网见...

1.2K1
来自专栏后端技术探索

反射机制、依赖注入、控制反转

反向: dll->类[方法,属性]. 从已经有的dll文件反编译得到其中的一些可用的方法.

982
来自专栏非著名程序员

Android开发工具类之TimeUtils

开发最重要的就是速度和效率,其实我一直都非常支持使用第三方的工具类,因为毕竟是一些大牛封装好的,效率什么的,可能比一些初学者写的确实好一些,但是我建议在使用第三...

2055
来自专栏C语言及其他语言

[每日一题]C语言程序设计教程(第三版)课后习题5.5

题目描述 有一个函数 y={ x x<1 | 2x-1 1<=x<10 \ 3x-11 x>=10 写一段程序,输入x,输出y ...

2823

扫码关注云+社区

领取腾讯云代金券