Java本地接口
Java本地接口 (JNI)是一个编程框架使得运行在Java虚拟机上的Java程序调用或者被调用特定于本机硬件与操作系统的用其它语言(C、C++或汇编语言等)编写的程序。[1]
功用
JNI允许用本地代码来解决纯粹用Java编程不能解决的平台相关的特性。也用于改造已存在的其它语言写的应用程序,供Java程序访问。许多使用了JNI的标准库提供了文件I/O与其它功能。标准库中性能敏感或平台敏感的API实现允许所有Java应用程序安全且平台独立地访问这些功能。
JNI框架使得本地方法可以访问Java对象,就如同Java程序访问这些本地对象。本地方法可以创建Java对象,然后检查、使用这些对象执行任务。本地方法也可以检查并使用由Java程序创建的对象。
Java开发人员称JNI为逃生门("escape hatch"),因为JINI允许增加标准Java API不能提供的功能。也可以用于时间关键的计算或者如解复杂数学方程,因为本地方法的运算比JVM更快。[2] 也可以在安卓上重用已存在的C/C++编写的库。
存在的问题
- 使用JNI的精细的错误会使JVM不稳定,并且难以再现与调试。
- 仅有应用程序与signed applet可以调用JNI。
- 依赖于JNI的应用程序失去了Java的平台移植性(部分解决之道是对每一平台写专门的JNI代码,然后Java检测操作系统并载入当前的JNI代码)。
- JNI框架对本地端执行代码分配的非JVM资源,不提供任何自动的垃圾收集。因此,本地端代码负责释放任何在本地获取的资源。
- Linux与Solaris平台,如果本地代码登记了自身作为信号处理器,会拦截原本发给JVM的信号。应该使用信号链式的本地代码更好地与JVM合作。[3]
- Windows平台上,结构化异常处理(SEH)可用于把本地代码包裹在SEH try/catch块中捕捉机器(CPU/FPU)生成的软中断(如:空指针访问冲突、除0操作等),在这些中断传播到JVM之前就得到处理。
- NewStringUTF, GetStringUTFLength, GetStringUTFChars, ReleaseStringUTFChars, GetStringUTFRegion函数并不是标准的UTF-8,而是修改的UTF-8。[4] null字符(U+0000)以及码位大于等于U+10000的在修改的UTF-8中有不同的编码。许多程序实际上不正确地使用了这些函数,把标准的UTF-8字符串传入或传出这些函数。程序应当使用NewString, GetStringLength, GetStringChars, ReleaseStringChars, GetStringRegion, GetStringCritical与ReleaseStringCritical函数,这些函数在小尾序机器上使用UTF-16LE编码,在大尾序机器上使用UTF-16BE编码,使用UTF-16代替标准的UTF-8版的例程。
JNI如何工作
在JNI框架柱,本地函数一般在单独的.c或.cpp文件中实现。当JVM调用这些函数,就传递一个JNIEnv
指针,一个jobject
的指针,任何在Java方法中声明的Java参数。一个JNI函数看起啦类似这样:
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobject obj)
{
/*Implement Native Method Here*/
}
env
指向一个结构包含了到JVM的界面,包含了所有必须的函数与JVM交互、访问Java对象。例如,把本地数组转换为Java数组的JNI函数,把本地字符串转换为Java字符串的JNI函数,实例化对象,抛出异常等。基本上,Java程序可以做的任何事情都可以用JNIEnv
做到,虽然相当不容易。
例如,下面代码把Java字符串转化为本地字符串:
//C++ code
extern "C"
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobject obj, jstring javaString)
{
//Get the native string from javaString
const char *nativeString = env->GetStringUTFChars(javaString, 0);
//Do something with the nativeString
//DON'T FORGET THIS LINE!!!
env->ReleaseStringUTFChars(javaString, nativeString);
}
/*C code*/
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobject obj, jstring javaString)
{
/*Get the native string from javaString*/
const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);
/*Do something with the nativeString*/
/*DON'T FORGET THIS LINE!!!*/
(*env)->ReleaseStringUTFChars(env, javaString, nativeString);
}
/*Objective-C code*/
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobject obj, jstring javaString)
{
/*DON'T FORGET THIS LINE!!!*/
JNF_COCOA_ENTER(env);
/*Get the native string from javaString*/
NSString* nativeString = JNFJavaToNSString(env, javaString);
/*Do something with the nativeString*/
/*DON'T FORGET THIS LINE!!!*/
JNF_COCOA_EXIT(env);
}
本地数据类型与Java数据类型可以互相映射。对于复合数据类型,如对象,数组,字符串,就必须用JNIEnv
中的方法来显示地转换。
第2个参数obj引用到一个Java对象,在其中声明了本地方法。
类型映射
下表是Java (JNI)与本地代码之间的数据类型映射:
本地类型 | Java语言的类型 | 描述 | 类型签名(signature) |
---|---|---|---|
unsigned char | jboolean | unsigned 8 bits | Z |
signed char | jbyte | signed 8 bits | B |
unsigned short | jchar | unsigned 16 bits | C |
short | jshort | signed 16 bits | S |
long | jint | signed 32 bits | I |
long long |
jlong | signed 64 bits | J |
float | jfloat | 32 bits | F |
double | jdouble | 64 bits | D |
void | V |
签名"L fully-qualified-class ;"
是由该名字指明的类。例如,签名"Ljava/lang/String;"
是类java.lang.String
。带前缀[
的签名表示该类型的数组,如[I
表示整型数组。void
签名使用V
代码。
这些类型是可以互换的,如jint
也可使用 int
,不需任何类型转换。
但是,Java字符串、数组与本地字符串、数组是不同的。如果在使用char *
代替了jstring
,程序可能会导致JVM崩溃。
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobject obj, jstring javaString) {
// printf("%s", javaString); // INCORRECT: Could crash VM!
// Correct way: Create and release native string from Java string
const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);
printf("%s", nativeString);
(*env)->ReleaseStringUTFChars(env, javaString, nativeString);
}
这种情况也适用于Java数组。下例对数组元素求和。
JNIEXPORT jint JNICALL Java_IntArray_sumArray
(JNIEnv *env, jobject obj, jintArray arr) {
jint buf[10];
jint i, sum = 0;
// This line is necessary, since Java arrays are not guaranteed
// to have a continuous memory layout like C arrays.
env->GetIntArrayRegion(arr, 0, 10, buf);
for (i = 0; i < 10; i++) {
sum += buf[i];
}
return sum;
}
JNIEnv*
JNI环境指针(JNIEnv*)作为每个映射为Java方法的本地函数的第一个参数,使得本地函数可以与JNI环境交互。这个JNI界面指针可以存储,但仅在当前线程中有效。其它线程必须首先调用AttachCurrentThread()把自身附加到虚拟机以获得JNI界面指针。一旦附加,本地线程运行就类似执行本地函数的正常Java线程。本地线程直到执行DetachCurrentThread()把自身脱离虚拟机。[5]
把当前线程附加到虚拟机并获取JNI界面指针:
JNIEnv *env; (*g_vm)->AttachCurrentThread (g_vm, (void **) &env, NULL);
当前线程脱离虚拟机:
(*g_vm)->DetachCurrentThread (g_vm);
高级使用
本地AWT绘制
本地代码不仅可以与Java交互,也可以在Java Canvas
绘图,使用Java AWT Native Interface。
访问汇编代码
JNI允许直接访问汇编代码。[6] 也可以从汇编代码访问Java。[7]
Microsoft的RNI
Microsoft实现的Java虚拟机——Visual J++的类似的访问本地Windows代码的机制Raw Native Interface(RNI)。
例子
HelloWorld
make.sh
#!/bin/sh
# openbsd 4.9
# gcc 4.2.1
# openjdk 1.7.0
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
javac HelloWorld.java
javah HelloWorld
gcc -shared libHelloWorld.c -o libHelloWorld.so
java HelloWorld
build.bat
:: Microsoft Visual Studio 2012 Visual C++ compiler
SET VC="C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC"
:: Microsoft Windows SDK for Windows 7 and .NET Framework 4
SET MSDK="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A"
:: Java 1.7.0 update 21
SET JAVA_HOME="C:\Program Files (x86)\Java\jdk1.7.0_21"
call %VC%\vcvarsall.bat
javac HelloWorld.java
javah HelloWorld
%VC%\bin\cl /I%JAVA_HOME%\include /I%JAVA_HOME%\include\win32 /I%VC%\include /I%VC%\lib /I%MSDK%\Lib libHelloWorld.c /FelibHelloWorld.dll /LD
java HelloWorld
HelloWorld.java
class HelloWorld
{
private native void print();
public static void main(String[] args)
{
new HelloWorld().print();
}
static{
System.loadLibrary("HelloWorld");
}
}
HelloWorld.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: print
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_print
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
libHelloWorld.c
#include <stdio.h>
#include "HelloWorld.h"
JNIEXPORT void JNICALL
Java_HelloWorld_print(JNIEnv *env, jobject obj)
{
printf("Hello World!\n");
return;
}
Invocation:
$ chmod +x make.sh
$ ./make.sh
参见
- Java AWT Native Interface
- Gluegen, a Java tool which automatically generates the Java and JNI code necessary to call C libraries from Java code
- P/Invoke, the .NET Framework method of calling native applications
- SWIG, a multilanguage interface-generator for C and C++ libraries that can generate JNI code
- Java Native Access provides Java programs easy access to native shared libraries without writing boilerplate code
参考文献
- ^ Role of the JNI. The Java Native Interface Programmer's Guide and Specification. [2008-02-27].
- ^ http://benchmarksgame.alioth.debian.org/u64/benchmark.php?test=all&lang=java&lang2=gcc&data=u64
- ^ If JNI based application is crashing, check signal handling!
- ^ Modified UTF-8 Strings
- ^ The Invocation API. Sun Microsystems. http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/invocation.html
- ^ Invoking Assembly Language Programs from Java. Java.net. 2006-10-19 [2007-10-06].
- ^ Launch Java Applications from Assembly Language Programs. Java.net. 2006-10-19 [2007-10-04].
相关书籍
- Gordon, Rob. Essential Jni: Java Native Interface 1st. Prentice Hall. March 1998: 498. ISBN 0-13-679895-0.
- Liang, Sheng. Java(TM) Native Interface: Programmer's Guide and Specification 1st. Prentice Hall. June 20, 1999: 320. ISBN 0-201-32577-2.
外部链接
- Oracle's JNI page for Java 6, including the JNI Specification
- Java Native Interface: Programmer's Guide and Specification - Book, copyright 2002.
- Best practices for using the Java Native Interface
- JNI Complete tutorial with examples
- GNU CNI Tutorial
- Multi-platform JNI Tutorial at Think-Techie.com
- A JNI Tutorial at CodeProject.com (Microsoft specific)
- JNI Tutorial at CodeToad.com
- Larger JNI example from Sun
- JNI video tutorial with Eclipse and Visual Studio
- JNI in XCode from Apple
- Exception handling in JNI
- HawtJNI Simplifies creating JNI libraries by code generating the JNI implementations using declarative annotations placed on your Java code.
- J/Link lets you call Java from Mathematica in a completely transparent way, and it also lets you use and control the Mathematica kernel from a Java program (Commercial)
- Jace is a toolkit designed to make it easy to write JNI-based programs
- JNIWrapper provides simplified access to native code from Java applications without using Java Native Interface.
- Java to Native Interface LGPL library to call native functions from Java
- Java Native Access Access to native libraries from Java without JNI
- NLink Another library for access to native libraries without JNI
- NativeCall – call native methods from Java without JNI Library to access native code without JNI
- JNIEasy Transparent Native Programming for C/C++, pure Java alternative to JNI using POJOS and JDO/JPA development style
- jni4net bridge between Java and .NET (intraprocess, fast, object oriented, open-source)
- Object-Oriented JNI Advanced Add-in for VC6 Object-Oriented JNI with a number of helpers that includes the standard JNI SDK for regular C++ (Commercial)
- Object-Oriented JNI for .NET1.1 (low-level) Object-Oriented JNI with a number of helpers that includes the standard JNI SDK for C#, Managed C++, VB#, J# (Commercial)
- Object-Oriented JNI for .NET2.0 (low-level) Object-Oriented JNI with a number of helpers that includes the standard JNI SDK for C#, Managed C++, VB#, J# (Commercial)
- OOJNI Add-in (C#,VB#) for VS2005/2008 Generates object-oriented JNI code in C# or VB# for Java classes selected, implements Java interfaces and Java native methods in VB# and C#. Java Class methods and fields (which are represented as .NET Class properties) to be wrapped can be filtered. (Commercial)
- eXtremeDB JNI uses Java annotations and reflection to enable Java applications to call the EXtremeDB database (written in C) without reliance on an external database definition language
- JavaToDPR, a Delphi Project (.dpr) Stub File Generator that allows one to write an Embarcadero Delphi DLL to handle the native methods declared in a Java .class file