JavaJNI实现对用VC++编写的动态库的调用

在很多的情况下,我们已经有了现成的供调用的动态库,而不需要用JAVA来再次实现一次。JAVA能不能实现对由其他语言编写的程序呢?答案是肯定的。JAVA内部提供了JNI即为JAVA提供了一个本地的代码接口,这样,我们就可以调用由其他语言实现的代码了。

空洞的说理没什么说服力,现在用一个JAVA调用VC++动态库的例子来说明一下整个过程。

举例如下:

Step1

首先用VC++创建一个空的动态库工程,具体的实现如下:

<!--[if !supportLists]-->(1)    <!--[endif]-->jisuandong.h文件代码如下:
//这部分是固定的预定义,不用修改
#ifdef_cplusplus
#define EXPORT extern “C” _declspec(dllexport)
#else
#define EXPORT _declspec(dllexport)
#endif
//.cpp中的调用函数说明
EXPORT double CALLBACK jisuan(int i,int j);
(2).def文件,代码如下:
LIBRARY “jisuandong”
DESCRIPTION ‘mydlltext’
EXPORTS
Jisuan
(3).cpp文件,代码如下:
#inlcude “stdafx.h”
#include “jisuandong.h” 
#include “math.h”
//函数实现
EXPORT double CALLBACK jisuan(int i,int j)
{
 return sqre(i*i+j*j);
}

编译生成.dll文件就行,这个动态库,是比如是你要供JAVA调用的以前用VC编写的已经实现的动态库。

Step2

编写JAVA程序,这里的JAVA程序是用来生成.h文件的,以备生成调用上述DLL的动态库,最后供JAVA应用程序来调用。这里只是一个示例,所以生成.h文件的JAVA程序和调用的JAVA应用程序没有分开,一般情况是分开写的。JAVA程序代码如下:

(1)
class NativeTest
{
 public static void main(String [] args)
 {
  NativeTest aTest=new NativeTest();
  int i0=Integer.valueOf(args[0]).intValue();
  int i1=Integer.valueOf(args[1]).intValue();
  double d=aTest.nativeHypotenuse(i0,i1);
  System.out.println("the value for the Hypotenuse for a triangle of sides "+args[0]+
  " and "+args[1]+ " is "+d);
 } 
 public native double nativeHypotenuse(int i,int j);
// static
// {
//  System.loadLibrary("NativeTest");
// }
 public NativeTest()
 {
  System.loadLibrary("NativeTest");//这里是最后封装的DLL供JAVA应用程序调用
 }
}
<!--[if !supportLists]-->(2)    <!--[endif]-->用javac NativeTest.java编译生成CLASS文件。
<!--[if !supportLists]-->(3)    <!--[endif]-->用javah –jni NativeTest生成.h文件

Step3

用VC再创建一个空动态库工程,用来调用上述动态库,此动态库就是在你原来打算调用的DLL上的另一层封装,才能实现JAVA应用程序对它的调用。

<!--[if !supportLists]-->(1)    <!--[endif]-->NativeTest.h文件也就是上述生成的.h文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class NativeTest */
 
#ifndef _Included_NativeTest
#define _Included_NativeTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     NativeTest
 * Method:    nativeHypotenuse
 * Signature: (II)D
 */
JNIEXPORT jdouble JNICALL Java_NativeTest_nativeHypotenuse
  (JNIEnv *, jobject, jint, jint);
 
#ifdef __cplusplus
}
#endif
#endif
(2).def文件
LIBRARY “NativeTest”
DESCRIPTION ‘mydll test’
EXPORTS
JNIEXPORT jdouble JNICALL Java_NativeTest_nativeHypotenuse

 

(3).cpp文件

#include “stdafx.h”
#include <math.h>
#include “NativeTest.h”
 
#include “jisuandong.h”//因为要调用jisuandong.dll
#pragma comment(lib,”jisuandong.lib”);//因为这里是静态调用,所以只需把上述lib文件拷贝到本目录下
//函数实现
JNIEXPORT jdouble JNICALL Java_NativeTest_nativeHypotenuse
  (JNIEnv *env, jobject obj, jint i, jint j)
{
 double d;
 return d = jisuan(i,j);
}

(4)最后把jisuandong.dll和NativeTest.dll拷贝到JAVA应用程序的目录下,调用即可。

其他补充如下:

JNI为程序员提供了一种方法,使得他们能够充分利用JVM以外的,完全由平台决定的功能。但是你不应该滥用JNI。大多数情况可能是因为这样而必 须使用JNI,如你已经有做好了的本地.dll文件,其中已经包含了大量呕心沥血的函数,而你不愿意,也不可能完全用Java重写它们,那么此时你应该用 JNI。

在Java中定义的本地方法映射到本地C函数后都会增加两个参数:JNIEnv *和jobject。第一个参数JNIEnv是一个指针,指向在jni.h中定义的一个数据结构,结构中包含了一系列的函数的指针,我们把它们称之为 JNI函数。使用这些JNI函数可以使程序员从本地函数这一侧完成对Java的操作,如访问Java字符串、数组、调用Java的方法、成员变量、甚至处 理Java端的异常等。第二个参数会根据Java类中本地方法的定义不同而不同,如果是定义为static方法,类型会是jclass,表示对特定 Class对象的引用,如果是非static方法,类型是jobject,表示当前对象的引用,相当于 this。

C函数通过JNI接受来自Java的传参也是按基本类型直接传值,对象传引用。对于基本类型可以按照表1来使用,其它类型都必须使用JNI函数进行 转换后才能在C函数中使用。如本例中从Java中传递了一个字符串,映射为JNI类型jstring。使用JNI函数GetStringUTFChars 将jstring转换为UTF-8字符串,然后便可以使用C语言中的任何字符串操作函数进行操作。由于JVM在调用本地方法时,是在虚拟机中开辟了一块本 地方法栈供本地方法使用,当本地方法使用完UTF-8串后,必须使用ReleaseStringUTFChars,通过它来通知虚拟机去回收UTF-8串 占用的内存,否则将会造成内存泄漏,最终导致系统崩溃。同样的,如果需要将C函数返回的返回值能够正确通过JNI传给Java,也要使用JNI函数转换为 前面两个表中的类型。

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

 

<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->

表2 Java中的类到本地类型的映射

 

<!--[if !vml]--><!--[endif]-->
 
<!--[if !vml]--><!--[endif]-->

呵呵,写到这里有点累了,希望对大家有用,本人也是由于工作需要才学的JAVA,希望大家多多交流!!!^_^