转:http://www.ibm.com/developerworks/cn/java/jnimthds/
简介
本文提供调用本地 C 代码的 Java 代码示例,包括传递和返回某些常用的数据类型。本地方法包含在特定于平台的可执行文件中。就本文中的示例而言,本地方法包含在 Windows 32 位动态链接库 (DLL) 中。
不过我要提醒您,对 Java 外部的调用通常不能移植到其他平台上,在 applet 中还可能引发安全异常。实现本地代码将使您的 Java 应用程序无法通过 100% 纯 Java 测试。但是,如果必须执行本地调用,则要考虑几个准则:
- 将您的所有本地方法都封装在单个类中,这个类调用单个 DLL。对于每种目标操作系统,都可以用特定于适当平台的版本替换这个 DLL。这样就可以将本地代码的影响减至最小,并有助于将以后所需的移植问题包含在内。
- 本地方法要简单。尽量将您的 DLL 对任何第三方(包括 Microsoft)运行时 DLL 的依赖减到最小。使您的本地方法尽量独立,以将加载您的 DLL 和应用程序所需的开销减到最小。如果需要运行时 DLL,必须随应用程序一起提供它们。
回页首
Java 调用 C
对于调用 C 函数的 Java 方法,必须在 Java 类中声明一个本地方法。在本部分的所有示例中,我们将创建一个名为 MyNative 的类,并逐步在其中加入新的功能。这强调了一种思想,即将本地方法集中在单个类中,以便将以后所需的移植工作减到最少。
回页首
示例 1 -- 传递参数
在第一个示例中,我们将三个常用参数类型传递给本地函数: String、 int和 boolean 。本例说明在本地 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 <stdio.h> #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 <stdio.h> #include <math.h> #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 项目来调试本地方法的步骤。
- 在 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 本地接口规范
相关推荐
在Windows下实现Java程序对屏幕、鼠标和键盘的监控,虽然可以通过标准的Java事件监听接口实现基本功能,但对于更复杂或系统级的需求,可能需要结合JNI和Windows API来增强监控能力。这要求开发者不仅要熟悉Java编程...
在Java程序中调用用C++语言编写的本地方法,是通过Java本地接口(JNI)实现的,JNI是Java提供的一套标准编程接口,用于Java代码和本地应用程序或库之间的交互。本地库通常是用C或C++编写的动态链接库(在Windows上是...
在Java编程语言中,开发一个本地文件管理器是一项常见的任务,尤其对于学习和理解I/O流、文件操作以及用户界面设计至关重要。这个项目的目标是创建一个类似Windows资源管理器的应用,能够显示本地文件系统的内容,...
在Java编程环境中,实现Linux与Windows之间的文件上传和下载是一项常见的任务,特别是在分布式系统和跨平台应用中。本文将深入探讨如何使用Java技术实现在Linux和Windows之间进行文件的互传,以及创建一个HTML界面来...
要实现Java调用本地的远程桌面连接,通常会使用`Runtime.getRuntime().exec()`方法来执行系统命令。这个方法允许我们在Java程序中执行任何可执行文件或命令行脚本。例如,我们可以构造一个包含用户名、密码和目标...
本案例将详细讲解如何在Java中通过本地方法调用C语言实现的功能,具体以一个调用系统时间的简单例子进行说明。 首先,我们来看`TimeShow.java`这个Java类,它通常包含了一个native方法声明,例如: ```java public...
本文详细介绍了如何使用Java语言结合jcifs库操作Windows共享目录的方法,主要包括了SMB协议的基本概念及其在Windows系统中的应用,以及jcifs库的功能特点和具体使用示例。通过对这些内容的学习,开发者可以更好地...
Windows实现按需调页的虚拟内存机制,使得应用程序可以使用超过物理内存容量的虚拟内存。此外,Windows还使用了页面文件来扩展物理内存的容量。 在Java中,我们可以使用Java Native Interface(JNI)来调用Windows ...
首先,你需要在Java类中声明将要调用的本地方法。这些方法需要使用`native`关键字,并且不需要提供具体实现。例如: ```java public class TestDLL { static { System.loadLibrary("goodluck"); } public ...
1. **定义本地方法**:在Java类中声明一个本地方法,该方法的实现由`native`关键字标识。例如: ```java public class HelloWorld { public native void sayHello(); static { System.loadLibrary("hello"); //...
- **实现本地方法**:在生成的头文件中找到对应的函数声明,然后在C/C++源文件中实现这些方法。 - **编译本地代码**:使用C/C++编译器(如gcc)编译源代码,生成动态链接库。 4. **加载本地库** 在Java代码中,...
9. **在Windows中实现Java本地方法** - **Java调用C**:详细介绍了如何在Windows环境下配置和使用JNI,包括编译和链接本地代码的过程。 10. **在C/C++中调用Java** - **反向调用**:除了Java调用本地代码,JNI...
Java JNI (Java Native Interface) 是Java平台中用于与本地代码交互的一种机制,它允许Java...无论是Windows还是Unix/Linux环境,通过遵循上述步骤和理解JNI的基本原理,开发者都能有效地实现Java与本地代码的交互。
这个过程包括编写JNI代码、生成头文件、实现本地方法、编译本地代码以及在Java程序中加载和调用这些本地方法。虽然这种方法增加了开发复杂性,但它为Java开发者提供了更多优化性能和充分利用硬件资源的途径。
Chap10:在 Windows 中实现 Java 本地方法... 66 1.Java 调用 C. 67 2.调试... 76 3.其他信息... 79 Chap11:如何在C/C++中调用Java. 80 1.环境搭建... 81 2.初始化虚拟机... 83 3.访问类方法... 85 4访问类...
微软最初为Java设计的本地接口项目——Java/COM接口,旨在实现Java与Windows操作系统中的组件对象模型(COM)之间的无缝交互。这个项目的目标是让Java程序能够像使用Java类一样使用COM对象,从而极大地增强了Java应用...
在Java编程环境中,有时我们需要与Windows操作系统进行交互,例如访问共享目录并下载其中的文件。这个过程涉及到几个关键的技术点,包括系统调用、网络通信和文件操作。在这个场景下,我们将通过`SmbUtils.java`和`...