Java编程教程之 JNI(Java Native Interface)

目录

 

1、介绍

2、开始

2.1 java 与 c


1、介绍

有时,有必要使用本机代码(C / C ++)来克服Java中的内存管理和性能限制。 Java通过Java Native Interface(JNI)支持本机代码。

JNI很难,因为它涉及两种语言和运行时。

我假设你熟悉:

  1. Java的。
  2. C / C ++和GCC编译器
  3. (对于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示例中没有使用这些参数,但稍后会使用它们。 暂时忽略宏JNIEXPORTJNICALL 。

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

  1. 将环境变量JAVA_HOME为指向JDK安装目录(应包含要在下一步中使用的include子目录):
     $ export JAVA_HOME = / your / java / installed / dir
     $ echo $ JAVA_HOME export JAVA_HOME = / your / java / installed / dir
     $ echo $ JAVA_HOME 
  2. 使用gcc将C程序HelloJNI.c into share module libhello.so编译HelloJNI.c into share module libhello.so ,它包含在所有Unix中:
     $ 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 
  3. 运行Java程序:
     $ java -Djava.library.path =。  HelloJNI java -Djava.library.path =。  HelloJNI 

(Mac OS X)64位JDK

  1. 将环境变量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 
  2. 使用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 
  3. 运行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

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

道格拉斯范朋克

播种花生牛奶自留田

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值