套马杆的汉子广场舞:Java 本地调用(java调用window DLL里的函数)

来源:百度文库 编辑:偶看新闻 时间:2024/05/05 14:11:10

Java 本地调用

David WendtWebSphere Development Research Triangle Park, NC

1999 年 5 月 01 日

本文为在 32 位 Windows 平台上实现 Java 本地方法提供了实用的示例、步骤和准则。这些示例包括传递和返回常用的数据类型。

本文中的示例使用 Sun Microsystems 公司创建的 Java DevelopmentKit (JDK) 版本 1.1.6 和 Java本地接口 (JNI) 规范。 用 C 语言编写的本地代码是用 MicrosoftVisual C++ 编译器编译生成的。

简介

本文提供调用本地 C 代码的 Java 代码示例,包括传递和返回某些常用的数据类型。本地方法包含在特定于平台的可执行文件中。就本文中的示例而言,本地方法包含在 Windows 32 位动态链接库 (DLL) 中。

不过我要提醒您,对 Java 外部的调用通常不能移植到其他平台上,在 applet 中还可能引发安全异常。实现本地代码将使您的 Java 应用程序无法通过 100% 纯 Java 测试。但是,如果必须执行本地调用,则要考虑几个准则:

1.       将您的所有本地方法都封装在单个类中,这个类调用单个 DLL。对于每种目标操作系统,都可以用特定于适当平台的版本替换这个 DLL。这样就可以将本地代码的影响减至最小,并有助于将以后所需的移植问题包含在内。

2.       本地方法要简单。尽量将您的 DLL 对任何第三方(包括 Microsoft)运行时 DLL 的依赖减到最小。使您的本地方法尽量独立,以将加载您的 DLL 和应用程序所需的开销减到最小。如果需要运行时 DLL,必须随应用程序一起提供它们。

Java 调用 C

对于调用 C 函数的 Java 方法,必须在 Java 类中声明一个本地方法。在本部分的所有示例中,我们将创建一个名为 MyNative 的类,并逐步在其中加入新的功能。这强调了一种思想,即将本地方法集中在单个类中,以便将以后所需的移植工作减到最少。

示例 1 -- 传递参数

在第一个示例中,我们将三个常用参数类型传递给本地函数: Stringintboolean 。本例说明在本地 C 代码中如何引用这些参数。

 

 

public class MyNative

{

 public void showParms( String s, int i, boolean b )

 {

    showParms0( s, i , b );

 }

 

 private native void showParms0( String s, int i, boolean b );

 

 static

 {

    System.loadLibrary( "MyNative" );

 }

}

 

 

请注意,本地方法被声明为专用的,并创建了一个包装方法用于公用目的。这进一步将本地方法同代码的其余部分隔离开来,从而允许针对所需的平台对它进行优化。 static子句加载包含本地方法实现的 DLL。

下一步是生成 C 代码来实现 showParms0 方法。此方法的 C 函数原型是通过对 .class 文件使用 javah 实用程序来创建的,而 .class 文件是通过编译 MyNative.java 文件生成的。这个实用程序可在 JDK 中找到。下面是 javah 的用法:

 

 javac MyNative.java(将 .java 编译为 .class)

 

 javah -jni

      MyNative(生成 .h 文件)

 

 

这将生成一个 MyNative.h 文件,其中包含一个本地方法原型,如下所示:

 

 

/*

 * Class:     MyNative

 * Method:    showParms0

 * Signature: (Ljava/lang/String;IZ)V

 */

JNIEXPORT void JNICALL Java_MyNative_showParms0

 (JNIEnv *, jobject, jstring, jint, jboolean);

 

 

第一个参数是调用 JNI 方法时使用的 JNI Environment 指针。第二个参数是指向在此 Java 代码中实例化的 Java 对象 MyNative 的一个句柄。其他参数是方法本身的参数。请注意,MyNative.h 包括头文件 jni.h。jni.h 包含 JNI API 和变量类型(包括jobject、jstring、jint、jboolean,等等)的原型和其他声明。

本地方法是在文件 MyNative.c 中用 C 语言实现的:

 

 

#include

#include "MyNative.h"

JNIEXPORT void JNICALL Java_MyNative_showParms0

 (JNIEnv *env, jobject obj, jstring s, jint i, jboolean b)

{

 const char* szStr = (*env)->GetStringUTFChars( env, s, 0 );

 printf( "String = [%s]\n", szStr );

 printf( "int = %d\n", i );

 printf( "boolean = %s\n", (b==JNI_TRUE ? "true" : "false") );

 (*env)->ReleaseStringUTFChars( env, s, szStr );

}

 

 

JNI API,GetStringUTFChars,用来根据 Java 字符串或 jstring 参数创建 C 字符串。这是必需的,因为在本地代码中不能直接读取 Java 字符串,而必须将其转换为 C 字符串或 Unicode。有关转换 Java 字符串的详细信息,请参阅标题为 NLS Strings and JNI 的一篇论文。但是,jboolean 和 jint 值可以直接使用。

MyNative.dll 是通过编译 C 源文件创建的。下面的编译语句使用 Microsoft Visual C++ 编译器:

 

 cl -Ic:\jdk1.1.6\include -Ic:\jdk1.1.6\include\win32 -LD MyNative.c

      -FeMyNative.dll 

 

其中 c:\jdk1.1.6 是 JDK 的安装路径。

MyNative.dll 已创建好,现在就可将其用于 MyNative 类了。
可以这样测试这个本地方法:在 MyNative 类中创建一个 main 方法来调用 showParms 方法,如下所示:

 

 

   public static void main( String[] args )

   {

     MyNative obj = new MyNative();

     obj.showParms( "Hello", 23, true );

     obj.showParms( "World", 34, false );

   }

 

 

当运行这个 Java 应用程序时,请确保 MyNative.dll 位于 Windows 的 PATH 环境变量所指定的路径中或当前目录下。当执行此 Java 程序时,如果未找到这个 DLL,您可能会看到以下的消息:

 

 java MyNative 

 

 Can‘t find class MyNative 

 

这是因为 static 子句无法加载这个 DLL,所以在初始化 MyNative 类时引发异常。Java 解释器处理这个异常,并报告一个一般错误,指出找不到这个类。
如果用 -verbose 命令行选项运行解释器,您将看到它因找不到这个 DLL 而加载 UnsatisfiedLinkError 异常。

如果此 Java 程序完成运行,就会输出以下内容:

 

 java MyNative 

 

 String = [Hello] 

 

 int = 23

 

 

 boolean = true 

 

 String = [World] 

 

 int

      = 34 

 

boolean = false 示例 2 -- 返回一个值

本例将说明如何在本地方法中实现返回代码。
将这个方法添加到 MyNative 类中,这个类现在变为以下形式:

 

 

public class MyNative

{

 public void showParms( String s, int i, boolean b )

 {

    showParms0( s, i , b );

 }

 public int hypotenuse( int a, int b )

 {

    return hyptenuse0( a, b );

 }

 

 private native void showParms0( String s, int i, boolean b );

 private native int hypotenuse0( int a, int b );

 

 static

 {

    System.loadLibrary( "MyNative" );

 }

 

 /* 测试本地方法 */

 public static void main( String[] args )

 {

    MyNative obj = new MyNative();

    System.out.println( obj.hypotenuse(3,4) );

    System.out.println( obj.hypotenuse(9,12) );

 }

}

 

 

公用的 hypotenuse 方法调用本地方法 hypotenuse0 来根据传递的参数计算值,并将结果作为一个整数返回。这个新本地方法的原型是使用 javah 生成的。请注意,每次运行这个实用程序时,它将自动覆盖当前目录中的 MyNative.h。按以下方式执行 javah:

 

 javah -jni MyNative 

 

生成的 MyNative.h 现在包含 hypotenuse0 原型,如下所示:

 

 

/*

 * Class:     MyNative

 * Method:    hypotenuse0

 * Signature: (II)I

 */

JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0

 (JNIEnv *, jobject, jint, jint);

 

 

该方法是在 MyNative.c 源文件中实现的,如下所示:

 

 

#include

#include

#include "MyNative.h"

 

JNIEXPORT void JNICALL Java_MyNative_showParms0

 (JNIEnv *env, jobject obj, jstring s, jint i, jboolean b)

{

 const char* szStr = (*env)->GetStringUTFChars( env, s, 0 );

 printf( "String = [%s]\n", szStr );

 printf( "int = %d\n", i );

 printf( "boolean = %s\n", (b==JNI_TRUE ? "true" : "false") );

 (*env)->ReleaseStringUTFChars( env, s, szStr );

}

 

JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0

 (JNIEnv *env, jobject obj, jint a, jint b)

{

 int rtn = (int)sqrt( (double)( (a*a) + (b*b) ) );

 return (jint)rtn;

}

 

 

再次请注意,jint 和 int 值是可互换的。
使用相同的编译语句重新编译这个 DLL:

 

 cl -Ic:\jdk1.1.6\include -Ic:\jdk1.1.6\include\win32 -LD MyNative.c

      -FeMyNative.dll 

 

现在执行 java MyNative 将输出 5 和 15 作为斜边的值。

示例 3 -- 静态方法

您可能在上面的示例中已经注意到,实例化的 MyNative 对象是没必要的。实用方法通常不需要实际的对象,通常都将它们创建为静态方法。本例说明如何用一个静态方法实现上面的示例。更改 MyNative.java 中的方法签名,以使它们成为静态方法:

 

 

 public static int hypotenuse( int a, int b )

 {

    return hypotenuse0(a,b);

 }

 ...

 private static native int hypotenuse0( int a, int b );

 

 

 

现在运行 javah 为 hypotenuse0创建一个新原型,生成的原型如下所示:

 

 

/*

 * Class:     MyNative

 * Method:    hypotenuse0

 * Signature: (II)I

 */

JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0

 (JNIEnv *, jclass, jint, jint);

 

 

C 源代码中的方法签名变了,但代码还保持原样:

 

 

JNIEXPORT jint JNICALL Java_MyNative_hypotenuse0

 (JNIEnv *env, jclass cls, jint a, jint b)

{

 int rtn = (int)sqrt( (double)( (a*a) + (b*b) ) );

 return (jint)rtn;

}

 

 

本质上,jobject 参数已变为 jclass 参数。此参数是指向 MyNative.class 的一个句柄。main 方法可更改为以下形式:

 

 

 public static void main( String[] args )

 {

    System.out.println( MyNative.hypotenuse( 3, 4 ) );

    System.out.println( MyNative.hypotenuse( 9, 12 ) );

 }

 

 

 

因为方法是静态的,所以调用它不需要实例化 MyNative 对象。本文后面的示例将使用静态方法。

示例 4 -- 传递数组

本例说明如何传递数组型参数。本例使用一个基本类型,boolean,并将更改数组元素。下一个示例将访问 String(非基本类型)数组。将下面的方法添加到 MyNative.java 源代码中:

 

 

 public static void setArray( boolean[] ba )

 {

    for( int i=0; i < ba.length; i++ )

      ba[i] = true;

    setArray0( ba );

 }

 ...

 private static native void setArray0( boolean[] ba );

 

 

在本例中,布尔型数组被初始化为 true,本地方法将把特定的元素设置为 false。同时,在 Java 源代码中,我们可以更改 main 以使其包含测试代码:

 

 

    boolean[] ba = new boolean[5];

    MyNative.setArray( ba );

    for( int i=0; i < ba.length; i++ )

      System.out.println( ba[i] );

 

 

在编译源代码并执行 javah 以后,MyNative.h 头文件包含以下的原型:

 

 

/*

 * Class:     MyNative

 * Method:    setArray0

 * Signature: ([Z)V

 */

JNIEXPORT void JNICALL Java_MyNative_setArray0

 (JNIEnv *, jclass, jbooleanArray);

 

 

请注意,布尔型数组是作为单个名为 jbooleanArray 的类型创建的。
基本类型有它们自已的数组类型,如 jintArray 和 jcharArray。
非基本类型的数组使用 jobjectArray 类型。下一个示例中包括一个 jobjectArray。这个布尔数组的数组元素是通过 JNI 方法 GetBooleanArrayElements 来访问的。
针对每种基本类型都有等价的方法。这个本地方法是如下实现的:

 

 

JNIEXPORT void JNICALL Java_MyNative_setArray0

 (JNIEnv *env, jclass cls, jbooleanArray ba)

{

 jboolean* pba = (*env)->GetBooleanArrayElements( env, ba, 0 );

 jsize len = (*env)->GetArrayLength(env, ba);

 int i=0;

 // 更改偶数数组元素

 for( i=0; i < len; i+=2 )

    pba[i] = JNI_FALSE;

 (*env)->ReleaseBooleanArrayElements( env, ba, pba, 0 );

}

 

 

指向布尔型数组的指针可以使用 GetBooleanArrayElements 获得。
数组大小可以用 GetArrayLength 方法获得。使用 ReleaseBooleanArrayElements 方法释放数组。现在就可以读取和修改数组元素的值了。jsize 声明等价于 jint(要查看它的定义,请参阅 JDK 的 include 目录下的 jni.h 头文件)。

示例 5 -- 传递 Java String 数组

本例将通过最常用的非基本类型,Java String,说明如何访问非基本对象的数组。字符串数组被传递给本地方法,而本地方法只是将它们显示到控制台上。
MyNative 类定义中添加了以下几个方法:

 

 

 public static void showStrings( String[] sa )

 {

    showStrings0( sa );

 }

 private static void showStrings0( String[] sa );

 

 

并在 main 方法中添加了两行进行测试:

 

 

 String[] sa = new String[] { "Hello,", "world!", "JNI", "is", "fun." };

 MyNative.showStrings( sa );

 

 

本地方法分别访问每个元素,其实现如下所示。

 

 

JNIEXPORT void JNICALL Java_MyNative_showStrings0

 (JNIEnv *env, jclass cls, jobjectArray sa)

{

 int len = (*env)->GetArrayLength( env, sa );

 int i=0;

 for( i=0; i < len; i++ )

 {

    jobject obj = (*env)->GetObjectArrayElement(env, sa, i);

    jstring str = (jstring)obj;

    const char* szStr = (*env)->GetStringUTFChars( env, str, 0 );

    printf( "%s ", szStr );

    (*env)->ReleaseStringUTFChars( env, str, szStr );

 }

 printf( "\n" );

}

 

 

数组元素可以通过 GetObjectArrayElement 访问。
在本例中,我们知道返回值是 jstring 类型,所以可以安全地将它从 jobject 类型转换为 jstring 类型。字符串是通过前面讨论过的方法打印的。有关在 Windows 中处理 Java 字符串的信息,请参阅标题为 NLS Strings and JNI 的一篇论文。

示例 6 -- 返回 Java String 数组

最后一个示例说明如何在本地代码中创建一个字符串数组并将它返回给 Java 调用者。MyNative.java 中添加了以下几个方法:

 

 

 public static String[] getStrings()

 {

    return getStrings0();

 }

 private static native String[] getStrings0();

 

 

更改 main 以使 showStrings 将 getStrings 的输出显示出来:

 

 

 MyNative.showStrings( MyNative.getStrings() );

 

 

实现的本地方法返回五个字符串。

 

 

JNIEXPORT jobjectArray JNICALL Java_MyNative_getStrings0

 (JNIEnv *env, jclass cls)

{

 jstring      str;

 jobjectArray args = 0;

 jsize        len = 5;

 char*        sa[] = { "Hello,", "world!", "JNI", "is", "fun" };

 int          i=0;

 args = (*env)->NewObjectArray(env, len, (*env)->FindClass(env, "java/lang/String"), 0);

 for( i=0; i < len; i++ )

 {

    str = (*env)->NewStringUTF( env, sa[i] );

    (*env)->SetObjectArrayElement(env, args, i, str);

 }

 return args;

}

 

 

字符串数组是通过调用 NewObjectArray 创建的,同时传递了 String 类和数组长度两个参数。Java String 是使用 NewStringUTF 创建的。String 元素是使用 SetObjectArrayElement 存入数组中的。

调试

现在您已经为您的应用程序创建了一个本地 DLL,但在调试时还要牢记以下几点。如果使用 Java 调试器 java_g.exe,则还需要创建 DLL 的一个“调试”版本。这只是表示必须创建同名但带有一个 _g 后缀的 DLL 版本。就 MyNative.dll 而言,使用 java_g.exe 要求在 Windows 的 PATH 环境指定的路径中有一个 MyNative_g.dll 文件。在大多数情况下,这个 DLL 可以通过将原文件重命名或复制为其名称带缀 _g 的文件。

现在,Java 调试器不允许您进入本地代码,但您可以在 Java 环境外使用 C 调试器(如 Microsoft Visual C++)调试本地方法。首先将源文件导入一个项目中。
将编译设置调整为在编译时将 include 目录包括在内:

 

 c:\jdk1.1.6\include;c:\jdk1.1.6\include\win32 

 

将配置设置为以调试模式编译 DLL。在 Project Settings 中的 Debug 下,将可执行文件设置为 java.exe(或者 java_g.exe,但要确保您生成了一个 _g.dll 文件)。程序参数包括包含 main 的类名。如果在 DLL 中设置了断点,则当调用本地方法时,执行将在适当的地方停止。

下面是设置一个 Visual C++ 6.0 项目来调试本地方法的步骤。

1.       在 Visual C++ 中创建一个 Win32 DLL 项目,并将 .c 和 .h 文件添加到这个项目中。




  • 在 Tools 下拉式菜单的 Options 设置下设置 JDK 的 include 目录。下面的对话框显示了这些目录。


  • 选择 Build 下拉式菜单下的 Build MyNative.dll 来建立这个项目。确保将项目的活动配置设置为调试(这通常是缺省值)。
  • 在 Project Settings 下,设置 Debug 选项卡来调用适当的 Java 解释器,如下所示:


当执行这个程序时,忽略“在 java.exe 中找不到任何调试信息”的消息。当调用本地方法时,在 C 代码中设置的任何断点将在适当的地方停止 Java 程序的执行。

其他信息

JNI 方法和 C++

上面这些示例说明了如何在 C 源文件中使用 JNI 方法。如果使用 C++,则请将相应方法的格式从:

 

 (*env)->JNIMethod( env, .... ); 

 

更改为:

 

 env->JNIMethod( ... ); 

 

在 C++ 中,JNI 函数被看作是 JNIEnv 类的成员方法。

字符串和国家语言支持

本文中使用的技术用 UTF 方法来转换字符串。使用这些方法只是为了方便起见,如果应用程序需要国家语言支持 (NLS),则不能使用这些方法。有关在 Windows 和 NLS 环境中处理 Java 字符串正确方法,请参标题为 NLS Strings and JNI 的一篇论文。

小结

本文提供的示例用最常用的数据类据(如 jint 和 jstring)说明了如何实现本地方法,并讨论了 Windows 特定的几个问题,如显示字符串。本文提供的示例并未包括全部 JNI,JNI 还包括其他参数类型,如 jfloat、jdouble、jshort、jbyte 和 jfieldID,以及用来处理这些类型的方法。有关这个主题的详细信息,请参阅 Sun Microsystems 提供的 Java 本地接口规范。

关于作者

 

David Wendt 是 IBM WebSphere Studio 的一名程序员,该工作室位于北卡罗莱纳州的 Research Triangle Park。可以通过 wendt@us.ibm.com 与他联系。

  

                                                        java调用window DLL里的函数的总结

单一的一种程序语言的使用已经不能满足我们真正开发过程中遇到的问题,有可能会使用多种程序语言共同完成某一应用,现在我讲解一下java与C++共同完成对window2000的动态连接库(DLL)的使用
首先定义一下java类WinMsgBox如下
public class WinMsgBox {
    static{
        System.loadLibrary("WinMsgDll");
    }

    public native void showMsgBox(String str);

    public static void main(String[] args){

    WinMsgBox ss = new WinMsgBox();

    ss.showMsgBox("Hello World!");

   }
}

然后使用javah命令生成头文件 如下:
C:\tmp>javah -classpath . -jni edu.netcom.jni.WinMsgBox
使他生成一个edu_netcom_jni_WinMsgBox.h头文件,这个头文件的文件名是包名加上类名共同组成的.头文件内容如下

/* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class edu_netcom_jni_WinMsgBox */

#ifndef _Included_edu_netcom_jni_WinMsgBox

#define _Included_edu_netcom_jni_WinMsgBox

#ifdef __cplusplus extern "C" {

#endif

/* * Class: edu_netcom_jni_WinMsgBox

* Method: showMsgBox

* Signature: (Ljava/lang/String;)V

*/

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

#ifdef __cplusplus

}

#endif

#endif

然后写一个cpp文件实现这个头文件,在这个方法里面调用了MessageBox()函数如下

#include "windows.h"

#include "iostream.h"

//#include "source.h"

#include "edu_netcom_jni_WinMsgBox.h"

/* * Class: edu_netcom_jni_WinMsgBox

* Method: showMsgBox

* Signature: (Ljava/lang/String;)V

*/

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

    const char *msg; msg = env->GetStringUTFChars(str,0);

    MessageBox(NULL,msg,"Java invoke",MB_OK);

    env->ReleaseStringUTFChars(str,msg);

}

然后在vc里面生成一个dll文件,有的时候会报一个错,意思是dll文件打不开,可以写一个def文件,然后把所需要的lib文件定义在这个文件里面,内容如下:

LIBRARY      "WinMsgDll"
DESCRIPTION  ‘message Windows Dynamic Link Library‘
EXPORTS
    ; Explicit exports can go here
Java_edu_netcom_jni_WinMsgBox_showMsgBox


文件名为WinMsgDll.dll,再把WinMsgDll.dll文件放到c:\winnt\system32目录下 这样通过调用native方法就执行dll里面定义的相应的操作了 very easy!

 

 

下载JCOM,解压取出jcom.jar和jcom.dll两个文件。
1、   将附件的jcom.jar复制到本地,并设置环境变量引用之(最简单快捷的办法就是将该jar包解压到自己的代码目录中)。

2、   该dll copy到JDK目录的bin目录下。

3、   查找需要调用的COM主件名称。注意,COM组件名称并不是dll文件的名称,可以到注册表中查询。

4、   查找调用的COM的接口方法:

可以在Microsoft Visual Studio .NET 2003 中引用相应的DLL,在“对象浏览器”中就可以看到该组件的所有接口方法:

5、   Java调用代码:

import jp.ne.so_net.ga2.no_ji.jcom.*;

public class ProcessXML {

         public static void main(String[] args) {

                   Process();

         }

         public static String Process(){

                   ReleaseManager rm = new ReleaseManager();

             try {

                 IDispatch vbcom = new IDispatch(rm, "U8Distribute.iDistribute");             

Object[] param = new Object[] { "XXXXX" };

                 String strRet = (String)vbcom.method( "Process", param );

                            System.out.println("return: "  + strRet);

             }

             catch ( Exception e ) {

                 e.printStackTrace();

             }

             finally {

                 rm.release();

             }

             return null;

         }

}

 

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Java不能直接调用由c或者c++写得dllTF_ID.dll),所以只能采用jni得方法,一步一步生成符合规范得dll文件(假设叫FANGJIAN.dll),在FANGJIAN.dll这个文件里来调用TF_ID.dll。注意一点:两个dll文件不能重名,为什么呢? 因为java后来执行时候,必须把两个dll文件均考到javaclass文件同一目录下,或者把TF_ID.dll考到system32下也可以,如果重名得话,首先不能考到一个目录下,再则即使将前一个dll考到system32下,那么后一个dll也会出现调用自身dll得问题,大家可想而知了!!!

下面给出了两个例子:

第一个是用javajni方法生成了一个dll文件,这个dll文件,直接在其方法函数体内写具体实现得方法,然后将dll文件考到java执行得同一目录下,就可以执行成功了!      

    第二个也是用javajni方法生成一个dll文件(FANGJIAN.dll),这个dll文件中再来调用IC卡读写器提供得dll文件(TF_ID.dll),将两个dll文件考到javaclass文件同一目录下面,运行java文件就可以了!!!

用jni生成一个C或者C++的dll,然后在那个dll里面调用你说的这个dll

用java直接调用是不行的

因为java调用的dll是必须准找一定的规则的,都是用javah生成本地方法的头文件,然后写c或着c++,然后编译成dll

例一

                                                                        JAVA通过JNI调用本地C语言方法

(加入日期:2002-3-18 点击数:9803)

【对此文发表评论】 【编程爱好者论坛】 【保存文章至硬盘】 【打印文章】

 

JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。

 JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。

 简单介绍及应用如下:  

一、JAVA中所需要做的工作

 在JAVA程序中,首先需要在类中声明所调用的库名称,如下:

 static {

 System.loadLibrary(“goodluck”);

 }

 在这里,库的扩展名字可以不用写出来,究竟是DLL还是SO,由系统自己判断。

 还需要对将要调用的方法做本地声明,关键字为native。并且只需要声明,而不需要具体实现。如下:

 public native static void set(int i);

 public native static int get();  

然后编译该JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就会生成C/C++的头文件。

 

例如程序testdll.java,内容为:

 public class testdll

 {

 static

 {  

System.loadLibrary("goodluck");

 }  

public native static int get();

 public native static void set(int i);  

public static void main(String[] args)

 {

 testdll test = new testdll();

 test.set(10);

 System.out.println(test.get());

 }  

}  

用javac testdll.java编译它,会生成testdll.class。

 再用javah testdll,则会在当前目录下生成testdll.h文件,这个文件需要被C/C++程序调用来生成所需的库文件。

 

二、C/C++中所需要做的工作

 对于已生成的.h头文件,C/C++所需要做的,就是把它的各个方法具体的实现。然后编译连接成库文件即可。再把库文件拷贝到JAVA程序的路径下面,就可以用JAVA调用C/C++所实现的功能了。

 

接上例子。我们先看一下testdll.h文件的内容:

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

 #include

 /* Header for class testdll */

 

#ifndef _Included_testdll

 #define _Included_testdll

 #ifdef __cplusplus

 extern "C" {

 #endif

 /*

 * Class: testdll

 * Method: get

 * Signature: ()I

 */

 JNIEXPORT jint JNICALL Java_testdll_get

 (JNIEnv *, jclass);

   /*

 * Class: testdll

 * Method: set

 * Signature: (I)V

 */

 JNIEXPORT void JNICALL Java_testdll_set

 (JNIEnv *, jclass, jint);

  #ifdef __cplusplus

 }

 #endif

 #endif

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

 JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass);

 和

 JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);

 这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jint是以JNI为中介使JAVA的int类型与本地的int沟通的一种类型,我们可以视而不见,就当做int使用。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。

 

好,下面我们用testdll.cpp文件具体实现这两个函数:

 #include "testdll.h"

 int i = 0;

 JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass)

 {  

return i;

 }  

JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint j)

 {

 i = j;

 }  

编译连接成库文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名称要与JAVA中需要调用的一致,这里就是goodluck.dll

 

把goodluck.dll拷贝到testdll.class的目录下,java testdll运行它,就可以观察到结果了。

 例二

一、JAVA中所需要做的工作

 在JAVA程序中,首先需要在类中声明所调用的库名称,如下:

 public class testdll {

       static

       {

       System.loadLibrary("FANGJIAN");

       }

   

       public native static String ID_Read();

       public static void main(String[] args)

       {

       testdll test = new testdll();

        String a=test.ID_Read();

        System.out.println(a);

       }

 }

 用javac testdll.java编译它,会生成testdll.class。

 再用javah testdll,则会在当前目录下生成testdll.h文件,这个文件需要被C/C++程序调用来生成所需的库文件。

  

二、C/C++中所需要做的工作

 对于已生成的.h头文件,C/C++所需要做的,就是把它的各个方法具体的实现,此处就可以在方法体中调用厂家所提供的dll库文件,来实现调用,并获得返回值。然后编译连接成库文件即可。再把库文件和厂家提供的库文件拷贝到JAVA程序的路径下面,就可以用JAVA调用C/C++所实现的功能了。

 需要注意的是必须将jni.h文件和jni_md.h文件考到c编译器的include文件夹下,这样才能通过编译!!!调用了这两个头文件里的声明……….

 接上例子。我们先看一下testdll.h文件的内容:

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

#include

/* Header for class testdll */

 #ifndef _Included_testdll

#define _Included_testdll

#ifdef __cplusplus

extern "C" {

#endif

/*

 * Class:     testdll

 * Method:    ID_Read

 * Signature: ()Ljava/lang/String;

 */

JNIEXPORT jstring JNICALL Java_testdll_ID_1Read(JNIEnv *, jclass);//方法名

 #ifdef __cplusplus

}

#endif

#endif

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

JNIEXPORT jstring JNICALL Java_testdll_ID_1Read(JNIEnv *, jclass);

如果是java类文件有包的话,函数的原型的名字会相应加上包名字

如:pakcage    com.util;       原型变为:

JNIEXPORT jstring JNICALL Java_com_util_testdll_ID_1Read(JNIEnv *, jclass);

 这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jint(jstring)是以JNI为中介使JAVA的int(string)类型与本地的int(string)沟通的一种类型,我们可以视而不见,就当做int(string)使用。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。

 好,下面我们用testdll.cpp文件具体实现这两个函数

 #include

#include

#include

#include

 typedef HANDLE (_stdcall *COMINT)(unsigned char port);

typedef int (_stdcall *COM_CLOSE)(HANDLE hr);

typedef int (_stdcall *ID_Read)(HANDLE icdev,int tt,unsigned char *_Data);

 JNIEXPORT jstring JNICALL Java_testdll_ID_1Read(JNIEnv* env,jclass)

{      

       HINSTANCE hTest;

       HANDLE hr;

       COMINT pComInit;

       COM_CLOSE pComClose;

       ID_Read pRead;

       int j;

    unsigned char data[16]="put card on it",*A=data;

       jstring jstr;

       hTest=LoadLibrary("TF_ID.dll");

       pComInit=(COMINT)GetProcAddress(hTest,"ComInit");

       pComClose=(COM_CLOSE)GetProcAddress(hTest,"ComClose");

       pRead=(ID_Read)GetProcAddress(hTest,"ID_Read");

       hr=pComInit(1);

    j=pRead(hr,3000,data);

       char b[16]="put card on it",*B=b;

       for(int i=0;i<16;i++)        

    *(B+i)=*(A+i);

    jstr=env->NewStringUTF(b);

   

       if(j==8)

              return jstr;

       else

              return jstr;   

       pComClose(hr);

       FreeLibrary(hTest);

 }

 其中进行了unsigned char【】到char【】的转换,并最后付给jstring中!!!

stdcall、cdecl的区别!!!

 编译连接成库文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名称要与JAVA中需要调用的一致,这里就是FANGJIAN.dll

 把FANGJIAN.dll和TF_ID.dll(或考TF_ID.dll到system32下)拷贝到testdll.class的目录下,java testdll运行它,就可以观察到结果了。