目录
1、介绍
有时,有必要使用本机代码(C / C ++)来克服Java中的内存管理和性能限制。 Java通过Java Native Interface(JNI)支持本机代码。
JNI很难,因为它涉及两种语言和运行时。
我假设你熟悉:
- Java的。
- C / C ++和GCC编译器
- (对于Windows)Cygwin或MinGW
2、开始
2.1 java 与 c
步骤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()
以在类加载期间加载本机库“ hello
”(其中包含本机方法sayHello()
)。 它将映射到Windows中的“ hello.dll
”; 或"libhello.so
/ Mac OS X中的"libhello.so
”。该库应包含在Java的库路径中(保存在Java系统变量java.library.path
); 否则,程序将抛出UnsatisfiedLinkError
。 您可以通过VM参数-Djava.library.path= /path/to/lib
将库包含到Java Library的路径中。
接下来,我们通过关键字native
声明方法sayHello()
作为本机实例方法,表示此方法是用另一种语言实现的。 本机方法不包含正文。 sayHello()
包含在加载的本机库中。
main()
方法分配HelloJNI
的实例并调用本机方法sayHello()
。
第2步:编译Java程序HelloJNI.java并生成C / C ++头文件HelloJNI.h
从JDK 8开始,您应该使用“ javac -h
”编译Java程序并生成C / C ++头文件HelloJNI.h
,如下所示:
> javac -h . HelloJNI.java
-h
选项生成C / C ++标头并将其放在指定的目录中(当前目录为'.'
)。
在JDK 8之前,您需要使用javac
进行编译并使用javah
实用程序生成头,如下所示:
> javac HelloJNI.java
> javah HelloJNI
查看生成的头文件:
/* 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
头文件一个C函数Java_HelloJNI_sayHello
,如下所示:
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
C函数的命名约定是Java_{package_and_classname}_{function_name}(JNI_arguments)
。 包名称中的点应替换为下划线。
论点是:
JNIEnv*
:对JNI环境的引用,它允许您访问所有JNI函数。jobject
:引用“this
”Java对象。
我们在这个hello-world示例中没有使用这些参数,但稍后会使用它们。 暂时忽略宏JNIEXPORT
和JNICALL
。
extern "C"
仅由C ++编译器识别。 它通知C ++编译器使用C的函数命名协议(而不是C ++命名协议)编译这些函数。 C和C ++具有不同的函数命名协议,因为C ++支持函数重载,并使用名称修改方案来区分重载函数。
第3步:实现C程序HelloJNI.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;
}
将C程序保存为“ HelloJNI.c
”。
标题“ jni.h
”在“ <JAVA_HOME>\include
”和“ <JAVA_HOME>\include\win32
”(对于Windows)或“ <JAVA_HOME>\include\linux
”(对于Ubuntu)下可用[检查Mac OS X]目录,其中<JAVA_HOME>
是您安装的JDK目录(例如,Windows的“ c:\program files\java\jdk9.0.x
”)。
C函数只是打印消息“Hello world!” 到控制台。
第4步:编译C程序HelloJNI.c
为您的平台(Windows,Mac OS X,Ubuntu)找到适合您的JDK(32位,64位)的编译器,并找出正确的编译器选项是让JNI工作最困难的部分!
(Windows)64位JDK
我们打算使用Cygwin。 你需要注意:
- Windows / Intel使用这些指令集:x86是32位指令集; i868是x86(也是32位)的增强版本; x86_64(或amd64)是64位指令集。
- 32位编译器可以在32位或64位(向后兼容)Windows上运行,但64位编译器只能在64位Windows上运行。
- 64位编译器可能会产生32位或64位的目标。
- 如果您使用Cygwin的GCC,目标可能是本机Windows或Cygwin。 如果目标是本机Windows,则可以在Windows下分发和运行代码。 但是,如果目标是Cygwin,要分发,则需要分发Cygwin运行时环境(
cygwin1.dll
)。 这是因为Cygwin是Windows下的Unix模拟器。 - 以上解释了Cygwin下许多版本的GCC。
对于64位JDK,您需要找到一个生成64位本机Windows目标的编译器。 这是由MinGW-W64提供的。 您可以通过选择软件包“ mingw64-x86_64-gcc-core
”(C编译器)和“ mingw64-x86_64-gcc-g++
”(C ++编译器)在Cygwin下安装MinGW-W64。 可执行文件分别是"x86_64-w64-mingw32-gcc
”(C编译器)和"x86_64-w64-mingw32-g++
”(C ++编译器)。
首先,将环境变量JAVA_HOME
为指向JDK安装目录(例如,“ c:\program files\java\jdk9.0.x
”)。
接下来,使用以下命令将HelloJNI.c
编译为hello.dll
。 在Windows中,我们将环境变量引用为%JAVA_HOME%
。
> x86_64-w64-mingw32-gcc -I“%JAVA_HOME%\ include”-I“%JAVA_HOME%\ include \ win32”-shared -o hello.dll HelloJNI.c
x86_64-w64-mingw32-gcc -I“%JAVA_HOME%\ include”-I“%JAVA_HOME%\ include \ win32”-shared -o hello.dll HelloJNI.c
使用的编译器选项是:
-I
:用于指定头文件目录。 在本例中为“jni.h
”(在“%JAVA_HOME%\include
”中)和“jni_md.h
”(在“%JAVA_HOME%\include\win32"
),其中%JAVA_HOME%
是设置为JDK安装的环境变量目录。-shared
:生成共享库。-o
:用于设置输出文件名“hello.dll
”。
您还可以通过两个步骤进行编译和链接:
//仅使用-c标志进行编译。 输出是HElloJNI.o
> x86_64-w64-mingw32-gcc -c -I“%JAVA_HOME%\ include”-I“%JAVA_HOME%\ include \ win32”HelloJNI.c
//链接到共享库“hello.dll”
> x86_64-w64-mingw32-gcc -shared -o hello.dll HelloJNI.o
//仅使用-c标志进行编译。 输出是HElloJNI.o
> x86_64-w64-mingw32-gcc -c -I“%JAVA_HOME%\ include”-I“%JAVA_HOME%\ include \ win32”HelloJNI.c
//链接到共享库“hello.dll”
> x86_64-w64-mingw32-gcc -shared -o hello.dll HelloJNI.o
您需要通过“ file
”实用程序检查生成的文件类型,这表明它是一个64位(x86_64)本机Windows。
> 文件hello.dll
hello.dll:PE32 +可执行文件(DLL)(控制台)x86-64,适用于MS Windows
文件hello.dll
hello.dll:PE32 +可执行文件(DLL)(控制台)x86-64,适用于MS Windows
在生成的共享库上尝试nm
(列出所有符号)以查找sayHello()
函数。 检查函数名称Java_HelloJNI_sayHello
,类型为"T"
(已定义)。
> nm hello.dll | grep说
00000000624014a0 T Java_HelloJNI_sayHello
nm hello.dll | grep说
00000000624014a0 T Java_HelloJNI_sayHello
(Windows)32位JDK [已淘汰?]
对于32位JDK,您需要找到一个32/64位编译器,它可以生成32位本机Windows的目标。 这是由MinGW-W64(和较旧的MinGW)提供的。 您可以通过选择软件包“ mingw64-i686-gcc-core
”(C编译器)和“ mingw64-i686-gcc-g++
”(C ++编译器)在Cygwin下安装MinGW-W64。 可执行文件分别是"i886-w64-mingw32-gcc
”(C编译器)和"i686-w64-mingw32-g++
”(C ++编译器)。
首先,将环境变量JAVA_HOME
为指向JDK安装目录(例如,“ c:\program files\java\jdk9.0.x
”)。 请按照此处的步骤操作。
接下来,使用以下命令将HelloJNI.c
编译为hello.dll
:
> i886-w64-mingw32-gcc -Wl, - add-stdcall-alias -I“%JAVA_HOME%\ include”-I“%JAVA_HOME%\ include \ win32”-shared -o hello.dll HelloJNI.c
i886-w64-mingw32-gcc -Wl, - add-stdcall-alias -I“%JAVA_HOME%\ include”-I“%JAVA_HOME%\ include \ win32”-shared -o hello.dll HelloJNI.c
使用的编译器选项是:
-Wl
:-Wl
传递链接器选项--add-stdcall-alias
以防止UnsatisfiedLinkError
(带有stdcall后缀(@nn
)的符号@nn
导出,并且后缀也被剥离)。 (有人建议使用-Wl,--kill-at
。)-I
:用于指定头文件目录。 在本例中为“jni.h
”(在“%JAVA_HOME%\include
”中)和“jni_md.h
”(在“%JAVA_HOME%\include\win32"
),其中%JAVA_HOME%
是设置为JDK安装的环境变量目录。-shared
:生成共享库。-o
:用于设置输出文件名“hello.dll
”。-D
__int64
="long long"
:定义类型(如果错误“未知类型名称'__int64'”,请在前面添加此选项)
(Ubuntu)64位JDK
- 将环境变量
JAVA_HOME
为指向JDK安装目录(应包含要在下一步中使用的include
子目录):$ export JAVA_HOME = / your / java / installed / dir $ echo $ JAVA_HOME
export JAVA_HOME = / your / java / installed / dir $ echo $ JAVA_HOME - 使用
gcc
将C程序HelloJNI.c into share module
编译libhello.so
HelloJNI.c into share module
,它包含在所有Unix中:libhello.so
$ gcc -fPIC -I“$ JAVA_HOME / include”-I“$ JAVA_HOME / include / linux”-shared -o libhello.so HelloJNI.c
gcc -fPIC -I“$ JAVA_HOME / include”-I“$ JAVA_HOME / include / linux”-shared -o libhello.so HelloJNI.c - 运行Java程序:
$ java -Djava.library.path =。 HelloJNI
java -Djava.library.path =。 HelloJNI
(Mac OS X)64位JDK
- 将环境变量
JAVA_HOME
为指向JDK安装目录(应包含要在下一步中使用的include
子目录):$ export JAVA_HOME = / your / java / installed / dir //为我的机器@ / Library/Java/JavaVirtualMachines/jdk1.8.0_xx.jdk/Contents/Home $ echo $ JAVA_HOME
export JAVA_HOME = / your / java / installed / dir //为我的机器@ / Library/Java/JavaVirtualMachines/jdk1.8.0_xx.jdk/Contents/Home $ echo $ JAVA_HOME - 使用
gcc
将C程序HelloJNI.c
编译到动态共享模块libhello.dylib
中,gcc
包含在所有Unix中:$ gcc -I“$ JAVA_HOME / include”-I“$ JAVA_HOME / include / darwin”-dynamiclib -o libhello.dylib HelloJNI.c
gcc -I“$ JAVA_HOME / include”-I“$ JAVA_HOME / include / darwin”-dynamiclib -o libhello.dylib HelloJNI.c - 运行Java程序:
$ java -Djava.library.path =。 HelloJNI
java -Djava.library.path =。 HelloJNI
第4步:运行Java程序
> java HelloJNI
可能需要通过VM选项-Djava.library.path= /path/to/lib
显式指定“ hello.dll
”的Java库路径,如下所示:
> java -Djava.library.path=. HelloJNI