Java Native Interface(JNI)从零开始详细教程
Posted createchance
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java Native Interface(JNI)从零开始详细教程相关的知识,希望对你有一定的参考价值。
====================================================================================
首先声明:这边文章是我翻译的文章(看了很多关于JNI的介绍,只有这篇个人认为最好,因此忍不住想要翻译给国内的各位),请勿随意转载,尊重文章原作者。
文章原始链接:https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html
这是南洋理工大学的在线学习note,有兴趣的同学可以直接看原文。如果你对E文比较头疼的话,可以直接看下面我翻译的,我会尽量使用平实的语言去翻译这篇文章,另外本人水平有限,如有翻译不当之处还请各位赐教。===================================================================================
引言
有的时候我们需要使用本地代码(C/C++)来克服Java中的内存管理和性能问题,Java通过JNI机制来支持内地代码的使用。
想要比较好地理解JNI是比较难的,因为它包含了两种语言和运行时机制。
在继续之前,我应该假设你具备以下知识点和技能:
1. java
2. C/C++和gcc编译器
3. 对于windows而言,熟悉Gygwin或者MinGW
4. 对于IDE而言,熟悉Eclipse C/C++ Development Tool (CDT)
开始
使用C来实现JNI
步骤1,编写一个使用C实现函数的java类,HelloJNI.java:
public class HelloJNI
static
System.loadLibrary("hello"); // Load native library at runtime
// hello.dll (Windows) or libhello.so (Unixes)
// Declare a native method sayHello() that receives nothing and returns void
private native void sayHello();
// Test Driver
public static void main(String[] args)
new HelloJNI().sayHello(); // invoke the native method
上面代码的静态代码块在这个类被类加载器加载的时候调用了System.loadLibrary()方法来加载一个native库“hello”(这个库中实现了sayHello函数)。这个库在windows品台上对应了“hello.dll”,而在类UNIX平台上对应了“libhello.so”。这个库应该包含在Java的库路径(使用java.library.path系统变量表示)上,否则这个上面的程序会抛出UnsatisfiedLinkError错误。你应该使用VM的参数-Djava.library.path=path_to_lib来指定包含native库的路径。
接下来,我们使用native关键字将sayHello()方法声明为本地实例方法,这就很明显地告诉JVM:这个方法实现在另外一个语言中(C/C++),请去那里寻找他的实现。注意,一个native方法不包含方法体,只有声明。上面代码中的main方法实例化了一个HelloJJNI类的实例,然后调用了本地方法sayHello()。
下面,我们编译HelloJNI.java成HelloJNI.class
javac HelloJNI.java
接下来,我们利用上面生成的class文件生成用于编写C/C++代码的头文件,使用jdk中的javah工具完成:
javah HelloJNI
上面的命令执行完之后生成了HelloJNI.h:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */
#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C"
#endif
/*
* Class: HelloJNI
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
#ifdef __cplusplus
#endif
#endif
我们看到,上面的头文件中生成了一个Java_HelloJNI_sayHello的C函数:
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
将java的native方法转换成C函数声明的规则是这样的:Java_package_and_classname_function_name(JNI arguments)。包名中的点换成单下划线。需要说明的是生成函数中的两个参数:
1. JNIEnv *:这是一个指向JNI运行环境的指针,后面我们会看到,我们通过这个指针访问JNI函数
2. jobject:这里指代java中的this对象
下面我们给出的例子中没有使用上面的两个参数,不过后面我们的例子会使用的。到目前为止,你可以先忽略JNIEXPORT和JNICALL这两个玩意。
上面头文件中有一个extern “C”,同时上面还有C++的条件编译语句,这么一来大家就明白了,这里的函数声明是要告诉C++编译器:这个函数是C函数,请使用C函数的签名协议规则去编译!因为我们知道C++的函数签名协议规则和C的是不一样的,因为C++支持重写和重载等面向对象的函数语法。
接下来,我们给出C语言的实现,以实现上面的函数:
C语言实现:
#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"
// Implementation of native method sayHello() of HelloJNI class
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj)
printf("Hello World!\\n");
return;
将上面的代码保存为HelloJNI.c。jni.h头文件在 “\\include” 和 “\\include\\win32”目录下,这里的JAVA_HOME是指你的JDK安装目录。
这段C代码的作用很简单,就是在终端上打印Hello Word!这句话。
下面我们编译这段代码,使用GCC编译器:
对于windows上的MinGW:
> set JAVA_HOME=C:\\Program Files\\Java\\jdk1.7.0_xx
// Define and Set environment variable JAVA_HOME to JDK installed directory
// I recommend that you set JAVA_HOME permanently, via "Control Panel" ⇒ "System" ⇒ "Environment Variables"
> echo %JAVA_HOME%
// In Windows, you can refer a environment variable by adding % prefix and suffix
> gcc -Wl,--add-stdcall-alias -I"%JAVA_HOME%\\include" -I"%JAVA_HOME%\\include\\win32" -shared -o hello.dll HelloJNI.c
// Compile HellJNI.c into shared library hello.dll
也可以分步编译:
// Compile-only with -c flag. Output is HElloJNI.o
> gcc -c -I"%JAVA_HOME%\\include" -I"%JAVA_HOME%\\include\\win32" HelloJNI.c
// Link into shared library "hello.dll"
> gcc -Wl,--add-stdcall-alias -shared -o hello.dll HelloJNI.o
下面,我们使用nm命令来查看生成hello.dll中的函数:
> nm hello.dll | grep say
624011d8 T _Java_HelloJNI_sayHello@8
对于windows上的Cygwin:
首先,你需要讲__int64定义成“long long”类型,通过-D _int64=”long long选项实现。
对于gcc-3,请包含选项-nmo -cygwin来编译dll库,这些库是不依赖于Cygwin dll的。
> gcc-3 -D __int64="long long" -mno-cygwin -Wl,--add-stdcall-alias
-I"<JAVA_HOME>\\include" -I"<JAVA_HOME>\\include\\win32" -shared -o hello.dll HelloJNI.c
对于gcc-4,我目前还没有找到正确的编译选项。
==================================这部分是笔者添加的==================================
原文中只给出了windows平台上编译方法,下面我给出Linux等类UNIX上编译方法:
gcc -fPIC --shared HelloJNI.c -o libhello.so -I /usr/lib/jvm/java-7-openjdk-amd64/include/
上面的命令编译生成一个libhello.so共享库在当前目录下
==================================这部分是笔者添加的==================================
接下来,让我们运行一下上面的代码吧:
> java HelloJNI
or
> java -Djava.library.path=. HelloJNI
有的时候,你可能需要使用-Djava.library.path来指定加载库的位置,因为可能报出java.lang.UnsatisfiedLinkError错误.
==================================这部分是笔者添加的==================================
我们首先使用nm命令(关于nm请自行Google或者man)查看libhello.so中都有那些函数:
可以看到我们的sayHello函数已经在这个里面,这说明我们编译的基本没有问题。
下面,我给出在我电脑上运行的效果(原文作者没有给出):
首先我们执行java HelloJNI,看看能不能运行:
果然,出现了UnsatisfiedLinkError错误,原因是VM去标准路径下查找这个库,发现找不到,然后就挂了。因此我们还是需要使用-Djava.library.path来明确告诉VM我们的库在哪里(当然,你也可以将你编译出来的库放到系统标准路径中,比如/usr/lib目录下):
现在OK了,因为我们明确告诉VM,我们的libhello.so就在当前目录下,不用傻傻地去系统中找啦!!
==================================这部分是笔者添加的==================================
使用C/C++混合实现JNI
第一步:编写一个使用本地代码的java类:HelloJNICpp.java
public class HelloJNICpp
static
System.loadLibrary("hello"); // hello.dll (Windows) or libhello.so (Unixes)
// Native method declaration
private native void sayHello();
// Test Driver
public static void main(String[] args)
new HelloJNICpp().sayHello(); // Invoke native method
同样地,我们使用javac来编译这个代码:
> javac HelloJNICpp.java
步骤2:生成C/C++的头文件
> javah HelloJNICpp
上面命令会生成一个HelloJNICpp.h的文件,并且这个文件中声明了这个本地函数:
JNIEXPORT void JNICALL Java_HelloJNICpp_sayHello(JNIEnv *, jobject);
步骤3:C/C++编码实现,HelloJNICppImpl.h, HelloJNICppImpl.cpp, 和 HelloJNICpp.c
这里,我们使用C++来实现真正的函数(”HelloJNICppImpl.h” 和 “HelloJNICppImpl.cpp”),而使用C来和java进行交互。(译者注:这样就可以把JNI的代码逻辑和我们真正的业务逻辑分离开了!)
C++头文件:”HelloJNICppImpl.h”
#ifndef _HELLO_JNI_CPP_IMPL_H
#define _HELLO_JNI_CPP_IMPL_H
#ifdef __cplusplus
extern "C"
#endif
void sayHello ();
#ifdef __cplusplus
#endif
#endif
C++的代码实现:”HelloJNICppImpl.cpp”
#include "HelloJNICppImpl.h"
#include <iostream>
using namespace std;
void sayHello ()
cout << "Hello World from C++!" << endl;
return;
C代码实现和Java的交互:”HelloJNICpp.c”
#include <jni.h>
#include "HelloJNICpp.h"
#include "HelloJNICppImpl.h"
JNIEXPORT void JNICALL Java_HelloJNICpp_sayHello (JNIEnv *env, jobject thisObj)
sayHello(); // invoke C++ function
return;
讲上面的代码编译成一个共享库(在windows上是hello.dll)。
使用windows上的MinGW GCC:
> set JAVA_HOME=C:\\Program Files\\Java\\jdk1.7.0_xx
> g++ -Wl,--add-stdcall-alias -I"%JAVA_HOME%\\include" -I"%JAVA_HOME%\\include\\win32"
-shared -o hello.dll HelloJNICpp.c HelloJNICppImpl.cpp
步骤4:运行java代码
> java HelloJNICpp
or
> java -Djava.library.path=. HelloJNICpp
java package中的JNI
在真正的产品化中,所有的java类都是有自己的包的,而不是一个默认的没有名字的包。下面我们说明一下java中的package怎么在JNI中使用。
步骤1:使用JNI的程序, myjni\\HelloJNI.java
package myjni; // 多了包名定义
public class HelloJNI
static
System.loadLibrary("hello"); // hello.dll (Windows) or libhello.so (Unixes)
// A native method that receives nothing and returns void
private native void sayHello();
public static void main(String[] args)
new HelloJNI().sayHello(); // invoke the native method
上面的这个类应该放在myjni目录下。然后我们编译这个代码:
// change directory to package base directory
> javac myjni\\HelloJNI.java
步骤2:生成C/C++头文件
如果你的java代码是放在一个包中的,那么你需要使用完全限定名称来生成C/C++头文件的。你可能会需要使用-classpath选项来指定JNI程序的classpath路径,并且可能会使用-d选项来指定生成头文件的目标文件夹。
> javah --help
......
// Change directory to package base directory
> javah -d include myini.HelloJNI
在上面的例子中,我们选择将生层的头文件放在include目录下,因此,我们输出的就是:”include\\myjni_HelloJNI.h”.这个头文件声明了这样的本地函数:
JNIEXPORT void JNICALL Java_myjni_HelloJNI_sayHello(JNIEnv *, jobject);
我们看到,和上面的例子相比,这里的名字规则是这样的:Java__methodName,同时,点号换成单下划线。
步骤3:C代码实现:HelloJNI.c
#include <jni.h>
#include <stdio.h>
#include "include\\myjni_HelloJNI.h"
JNIEXPORT void JNICALL Java_myjni_HelloJNI_sayHello(JNIEnv *env, jobject thisObj)
printf("Hello World!\\n");
return;
编译C代码:
> gcc -Wl,--add-stdcall-alias -I<JAVA_HOME>\\include -I<JAVA_HOME>\\include\\win32 -shared -o hello.dll HelloJNI.c
运行代码:
> java myjni.HelloJNI
在Eclipse中开发JNI
这部分作者写的非常易懂,由于个人时间问题,就不翻译了,大家看看就明白(其实就是一些Eclipse的设置问题,没有什么复杂的),或者百度看国内的也行。(抱歉了!)
原文链接:https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html
JNI基础知识
上面我们简单演示了怎么使用JNI,现在我们来系统梳理一下JNI中涉及的基本知识。
JNI定义了以下数据类型,这些类型和Java中的数据类型是一致的:
1. Java原始类型:jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean这些分别对应这java的int, byte, short, long, float, double, char and boolean。
2. Java引用类型:jobject用来指代java.lang.Object,除此之外,还定义了以下子类型:
a. jclass for java.lang.Class.
b. jstring for java.lang.String.
c. jthrowable for java.lang.Throwable.
d. jarray对java的array。java的array是一个指向8个基本类型array的引用类型。于是,JNI中就有8个基本类型的array:jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray 和 jbooleanArray,还有一个就是指向Object的jobjectarray。
Native函数会接受上面类型的参数,并且也会返回上面类型的返回值。然而,本地函数(C/C++)是需要按照它们自己的方式处理类型的(比如C中的string,就是char *)。因此,需要在JNI类型和本地类型之间进行转换。通常来讲,本地函数需要:
1. 加收JNI类型的参数(从java代码中传来)
2. 对于JNI类型参数,需要讲这些数据转换或者拷贝成本地数据类型,比如讲jstring转成char *, jintArray转成C的int[]。需要注意的是,原始的JNI类型,诸如jint,jdouble之类的不用进行转换,可以直接使用,参与计算。
3. 进行数据操作,以本地的方式
4. 创建一个JNI的返回类型,然后讲结果数据拷贝到这个JNI数据中
5. returnJNI类型数据
这其中最麻烦的事莫过于在JNI类型(如jstring, jobject, jintArray, jobjectArray)和本地类型(如C-string, int[])之间进行转换这件事情了。不过所幸的是,JNI环境已经为我们定义了很多的接口函数来做这种烦人的转换。(译者注:这里就需要使用上面我们提到的JNIEnv*那个参数了!)
在Java和Native代码之间传递参数和返回值
传递基本类型
传递java的基本类型是非常简单而直接的,一个jxxx之类的类型已经定义在本地系统中了,比如:jint, jbyte, jshort, jlong, jfloat, jdouble, jchar 和 jboolean分别对应java的int, byte, short, long, float, double, char 和 boolean基本类型。
Java JNI 程序:TestJNIPrimitive.java
public class TestJNIPrimitive
static
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
// Declare a native method average() that receives two ints and return a double containing the average
private native double average(int n1, int n2);
// Test Driver
public static void main(String args[])
System.out.println("In Java, the average is " + new TestJNIPrimitive().average(3, 2));
这个JNI程序加载了myjni.dll(windows)库或者libmyjni.so(类UNIX)库。并且声明了一个native方法,这个方法接受两个int类型的参数,并且返回一个double类型的返回值,这个值是两个int型数的平均值。mian方法调用了average函数。
下面,我们将上面的java代码编译成TestJNIPrimitive.class,进而生成C/C++头文件TestJNIPrimitive.h:
> javac TestJNIPrimitive.java
> javah TestJNIPrimitive // Output is TestJNIPrimitive.h
C实现:TestJNIPrimitive.c
头文件TestJNIPrimitive.h中包含了一个函数声明:
JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average(JNIEnv *, jobject, jint, jint);
可以看到,这里的jint和jdouble分别表示java中的int和double。
jni.h(windows上是win32/jni_mh.h)头文件包含了这些数据类型的定义,同时多了一个jsize的定义:
// In "win\\jni_mh.h" - machine header which is machine dependent
typedef long jint;
typedef __int64 jlong;
typedef signed char jbyte;
// In "jni.h"
typedef unsigned char jboolean;
typedef unsigned short jchar;
typedef short jshort;
typedef float jfloat;
typedef double jdouble;
typedef jint jsize;
有趣的是,jint对应到C的long类型(至少是32bit的),而不是C的int类型(至少是16bit的)。于是,在C代码中要使用jint而不是int是很重要的。同时,CygWin不支持__int64类型。
TestJNIPrimitive.c的实现如下:
#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitive.h"
JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
(JNIEnv *env, jobject thisObj, jint n1, jint n2)
jdouble result;
printf("In C, the numbers are %d and %d\\n", n1, n2);
result = ((jdouble)n1 + n2) / 2.0;
// jint is mapped to int, jdouble is mapped to double
return result;
然后,我们编译代码成一个共享库:
// MinGW GCC under Windows
> set JAVA_HOME=jdk-installed-directory
> gcc -Wl,--add-stdcall-alias -I"%JAVA_HOME%\\include" -I"%JAVA_HOME%\\include\\win32" -shared -o myjni.dll TestJNIPrimitive.c
最后,我们运行这个java代码:
> java TestJNIPrimitive
C++实现 TestJNIPrimitive.cpp
代码如下:
#include <jni.h>
#include <iostream>
#include "TestJNIPrimitive.h"
using namespace std;
JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
(JNIEnv *env, jobject obj, jint n1, jint n2)
jdouble result;
cout << "In C++, the numbers are " << n1 << " and " << n2 << endl;
result = ((jdouble)n1 + n2) / 2.0;
// jint is mapped to int, jdouble is mapped to double
return result;
使用g++来编译上面的代码:
// MinGW GCC under Windows
> g++ -Wl,--add-stdcall-alias -I"%JAVA_HOME%\\include" -I"%JAVA_HOME%\\include\\win32" -shared -o myjni.dll TestJNIPrimitive.cpp
传递字符串
Java JNI 程序:TestJNIString.java
public class TestJNIString
static
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
// Native method that receives a Java String and return a Java String
private native String sayHello(String msg);
public static void main(String args[])
String result = new TestJNIString().sayHello("Hello from Java");
System.out.println("In Java, the returned string is: " + result);
上面的代码声明了一个native函数sayHello,这个函数接受一个java的String,然后返回一个Java string,main方法调用了sayHello函数。
然后,我们编译上面的代码,并且生成C/C++的头文件:
> javac TestJNIString.java
> javah TestJNIString
C代码实现:TestJNIString.c
上面的头文件TestJNIString.h声明了这样的一个函数:
JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *, jobject, jstring);
JNI定义了jstring类型应对java的String类型。上面声明中的最后一个参数jstring就是来自Java代码中的String参数,同时,返回值也是一个jstring类型。
传递一个字符串比传递基本类型要复杂的多,因为java的String是一个对象,而C的string是一个NULL结尾的char数组。因此,我们需要将Java的String对象转换成C的字符串表示形式:char *。
前面我们提到,JNI环境指针JNIEnv *已经为我们定义了非常丰富的接口函数用来处理数据的转换:
1. 调用const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*)来将JNI的jstring转换成C的char *
2. 调用jstring NewStringUTF(JNIEnv*, char*)来将C的char *转换成JNI的jstring
因此我们的C程序基本过程如下:
1. 使用GetStringUTFChars()函数来将jstring转换成char *
2. 然后进行需要的数据处理
3. 使用NewStringUTF()函数来将char *转换成jstring,并且返回
#include <jni.h>
#include <stdio.h>
#include "TestJNIString.h"
JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr)
// Step 1: Convert the JNI String (jstring) into C-String (char*)
const char *inCStr = (*env)->GetStringUTFChars(env, inJNIStr, NULL);
if (NULL == inCSt) return NULL;
// Step 2: Perform its intended operations
printf("In C, the received string is: %s\\n", inCStr);
(*env)->ReleaseStringUTFChars(env, inJNIStr, inCStr); // release resources
// Prompt user for a C-string
char outCStr[128];
printf("Enter a String: ");
scanf("%s", outCStr); // not more than 127 characters
// Step 3: Convert the C-string (char*) into JNI String (jstring) and return
return (*env)->NewStringUTF(env, outCStr);
将上面的代码编译成共享库:
// MinGW GCC under Windows
> gcc -Wl,--add-stdcall-alias -I"<JAVA_HOME>\\include" -I"<JAVA_HOME>\\include\\win32" -shared -o myjni.dll TestJNIString.c
最后,运行代码:
> java TestJNIString
In C, the received string is: Hello from Java
Enter a String: test
In Java, the returned string is: test
JNI中的string转换函数
上面我们展示了两个函数,现在我们全面梳理下JNI为我们提供的函数。JNI支持Unicode(16bit字符)和UTF-8(使用1~3字节的编码)转化。一般而言,我们应该在C/C++中使用UTF-8的编码方式。
JNI系统提供了如下关于字符串处理的函数(一共两组,UTF8和Unicode):
// UTF-8 String (encoded to 1-3 byte, backward compatible with 7-bit ASCII)
// Can be mapped to null-terminated char-array C-string
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
// Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding.
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
// Informs the VM that the native code no longer needs access to utf.
jstring NewStringUTF(JNIEnv *env, const char *bytes);
// Constructs a new java.lang.String object from an array of characters in modified UTF-8 encoding.
jsize GetStringUTFLength(JNIEnv *env, jstring string);
// Returns the length in bytes of the modified UTF-8 representation of a string.
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);
// Translates len number of Unicode characters beginning at offset start into modified UTF-8 encoding
// and place the result in the given buffer buf.
// Unicode Strings (16-bit character)
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
// Returns a pointer to the array of Unicode characters
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
// Informs the VM that the native code no longer needs access to chars.
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize length);
// Constructs a new java.lang.String object from an array of Unicode characters.
jsize GetStringLength(JNIEnv *env, jstring string);
// Returns the length (the count of Unicode characters) of a Java string.
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize length, jchar *buf);
// Copies len number of Unicode characters beginning at offset start to the given buffer buf
GetStringUTFChars()函数可以将jstring转成char *,这个函数会返回NULL,如果系统的内容分配失败的话。因此,好的做法是检查这个函数的返回是不是NULL。第三个参数是isCopy,这个参数是一个in-out参数,传进去的是一个指针,函数结束的时候指针的内容会被修改。如果内容是JNI_TRUE的话,那么代表返回的数据是jstring数据的一个拷贝,反之,如果是JNI_FALSE的话,就说明返回的字符串就是直接指向那个String对象实例的。在这种情况下,本地代码不应该随意修改string中的内容,因为修改会代码Java中的修改。JNI系统会尽量保证返回的是直接引用,如果不能的话,那就返回一个拷贝。通常,我们很少关心修改这些string ,因此我们这里一般传递NULL给isCopy参数。
必须要注意的是,当你不在需要GetStringUTFChars返回的字符串的时候,一定记得调用ReleaseStringUTFChars()函数来将内存资源释放!否则会内存泄露!并且上层java中的GC也不能进行!
另外,在GetStringUTFChars和ReleaseStringUTFChars不能block!
NewStringUTF()函数可以从char *字符串得到jstring。
关于更详细的描述,请参考Java Native Interface Specification:http://docs.oracle.com/javase/7/docs/technotes/guides/jni/index.html
C++实现:TestJNIString.cpp
#include <jni.h>
#include <iostream>
#include <string>
#include "TestJNIString.h"
using namespace std;
JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr)
// Step 1: Convert the JNI String (jstring) into C-String (char*)
const char *inCStr = env->GetStringUTFChars(inJNIStr, NULL);
if (NULL == inCStr) return NULL;
// Step 2: Perform its intended operations
cout << "In C++, the received string is: " << inCStr << endl;
env->ReleaseStringUTFChars(inJNIStr, inCStr); // release resources
// Prompt user for a C++ string
string outCppStr;
cout << "Enter a String: ";
cin >> outCppStr;
// Step 3: Convert the C++ string to C-string, then to JNI String (jstring) and return
return env->NewStringUTF(outCppStr.c_str());
使用g++编译上面的代码:
// MinGW GCC under Windows
> g++ -Wl,--add-stdcall-alias -I"<JAVA_HOME>\\include" -I"<JAVA_HOME>\\include\\win32" -shared -o myjni.dll TestJNIString.cpp
需要注意的是,在C++中,本地string类的函数调用语法不一样。在C++中,我们使用env->来调用,而不是(env*)->。同时,在C++函数中不需要JNIEnv*这个参数了。
传递基本类型的数组
JNI 代码:TestJNIPrimitiveArray.java
public class TestJNIPrimitiveArray
static
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
// Declare a native method sumAndAverage() that receives an int[] and
// return a double[2] array with [0] as sum and [1] as average
private native double[] sumAndAverage(int[] numbers);
// Test Driver
public static void main(String args[])
int[] numbers = 22, 33, 33;
double[] results = new TestJNIPrimitiveArray().sumAndAverage(numbers);
System.out.println("In Java, the sum is " + results[0]);
System.out.println("In Java, the average is " + results[1]);
C语言实现:TestJNIPrimitiveArray.c
头文件TestJNIPrimitiveArray.h包含以下函数声明:
JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_average (JNIEnv *, jobject, jintArray);
在Java中,array是指一种类型,类似于类。一共有9种java的array,8个基本类型的array和一个object的array。JNI针对java的基本类型都定义了相应的array:jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray,并且也有面向object的jobjectArray。
同样地,你需要在JNI array和Native array之间进行转换,JNI系统已经为我们提供了一系列的接口函数:
1. 使用jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)将jintarray转换成C的jint[]
2. 使用jintArray NewIntArray(JNIEnv *env, jsize len)函数来分配一个len字节大小的空间,然后再使用void SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf)函数讲jint[]中的数据拷贝到jintArray中去。
一共有8对类似上面的函数,分别对应java的8个基本数据类型。
因此,native程序需要:
1. 接受来自java的JNI array,然后转换成本地array
2. 进行需要的数据操作
3. 将需要返回的数据转换成jni的array,然后返回
下面是C代码实现的TestJNIPrimitiveArray.c:
#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitiveArray.h"
JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage
(JNIEnv *env, jobject thisObj, jintArray inJNIArray)
// Step 1: Convert the incoming JNI jintarray to C's jint[]
jint *inCArray = (*env)->GetIntArrayElements(env, inJNIArray, NULL);
if (NULL == inCArray) return NULL;
jsize length = (*env)->GetArrayLength(env, inJNIArray);
// Step 2: Perform its intended operations
jint sum = 0;
int i;
for (i = 0; i < length; i++)
sum += inCArray[i];
jdouble average = (jdouble)sum / length;
(*env)->ReleaseIntArrayElements(env, inJNIArray, inCArray, 0); // release resources
jdouble outCArray[] = sum, average;
// Step 3: Convert the C's Native jdouble[] to JNI jdoublearray, and return
jdoubleArray outJNIArray = (*env)->NewDoubleArray(env, 2); // allocate
if (NULL == outJNIArray) return NULL;
(*env)->SetDoubleArrayRegion(env, outJNIArray, 0 , 2, outCArray); // copy
return outJNIArray;
JNI基本类型的array函数
JNI基本类型的array(jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray 和 jbooleanArray)函数如下:
// ArrayType: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray
// PrimitiveType: int, byte, short, long, float, double, char, boolean
// NativeType: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean
NativeType * Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, NativeType *buffer);
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, const NativeType *buffer);
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);
同样地,在get函数和release函数之间也不能always block。
访问Java对象变量和回调Java方法
访问Java对象实例的变量
JNI程序:TestJNIInstanceVariable.java
public class TestJNIInstanceVariable
static
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
// Instance variables
private int number = 88;
private String message = "Hello from Java";
// Declare a native method that modifies the instance variables
private native void modifyInstanceVariable();
// Test Driver
public static void main(String args[])
TestJNIInstanceVariable test = new TestJNIInstanceVariable();
test.modifyInstanceVariable();
System.out.println("In Java, int is " + test.number);
System.out.println("In Java, String is " + test.message);
这个类包含了两个private实例变量,一个int,一个String对象。然后我们在main中调用本地函数modifyInstanceVariable来修改这两个变量。
C代码实现:TestJNIInstanceVariable.c
#include <jni.h>
#include <stdio.h>
#include "TestJNIInstanceVariable.h"
JNIEXPORT void JNICALL Java_TestJNIInstanceVariable_modifyInstanceVariable
(JNIEnv *env, jobject thisObj)
// Get a reference to this object's class
jclass thisClass = (*env)->GetObjectClass(env, thisObj以上是关于Java Native Interface(JNI)从零开始详细教程的主要内容,如果未能解决你的问题,请参考以下文章
Java固有接口JNI(Java Native Interface)之HelloWorld
Java Native Interface(JNI)从零开始详细教程
Mac下Java JNI (java native interface)调C
Java Native Interface (JNI) 是不是受 C++ ABI 兼容性问题的影响?