级别: 初级

Nikolay Yevik, Linux on POWER 技术顾问, IBM

2005 年 3 月 14 日

本文通过一些简化的示例描述了重要的 Java 本地接口(Java Native Interface,JNI)编程概念,并在适当的地方着重指出了特定于 POWER™ 上 Linux™ 的以及通常的编程隐患。

简介

对那些刚接触 Java 本地接口编程,并且想要在用于 POWER 的 Linux 发行版本(比如 Red Hat Enterprise Linux 3 和 SUSE LINUX Enterprise Linux V9)上检验其 JNI 编程能力的开发者来说,本文会有所帮助。





回页首


编译器

在本文中,示例中简化的源代码是在 SLES 9 和 RHEL AS 3 Update 3 上编译的,使用的是 64 位 IBM Java SDK 1.4.2 的最新服务更新(Service Refresh)以及用于 Linux 的 IBM XL C/C++ Advanced Edition V7.0。注意,并不是要强制使用 IBM Java SDK 和 IBM XL C/c++ 编译器,只是这样做效果会非常好。请随意使用其他编译器在用于 POWER 的 Linux 发行版本上编译 Java 和 JNI C/C++ 代码。

IBM XL C/C++ 编译器可以为 POWER™ 处理器生成性能极度优化的二进制代码,在大部分情况下,这可以显著地提高所有 C/C++ 代码的性能。用于 Linux 的 IBM XL C/C++ 编译器与 GNU C/C++ 能够非常好地兼容,并且包含有 gxl* 编译器变量,可以将 GNU C/C++ 编译器标记翻译为相应的用于 IBM XL C/C++ 编译器的命令,然后调用它。当转而使用 IBM XL C/C++ 编译器时,这可以显著地简化 Makefile 文件(为 GNU 所编写)的移植。





回页首


示例

本节所包含的示例基于通常使用的以及可公开获得的示例 JNI 代码,向您展示了如何在 POWER 上 Linux 中编译这些示例。在下面的 developerWorks 文章中可以找到关于 JNI 编程的更多资料: Java environments for Linux on POWER architecture(2004 年 10 月)和 JNI Programming on AIX (2004 年 3 月)。

通过 Java 代码示例调用本地函数



清单 1. HelloWorld.java





清单 2. HelloWorld.c



编译

  1. 编译 Java 代码,生成 HelloWorld.class 文件:

    
    


  2. 使用 javah 工具生成 HelloWorld.h 文件:

    
    



    清单 3. HelloWorld.h
    
    


  3. 编译本地 C 代码:

    
    



运行





输出





注意
  1. 所有本地 JNI 方法都应该在 Java 代码中声明。
  2. 应该在静态代码块中调用 System.loadLibrary()。
  3. 如果对代码的修改改变了函数的符号,那么需要重新运行 javah。
  4. 在本例中,我们创建了一个 64 位共享程序库,通过向线程安全可重入编译变量 xlc_r 传递一个 –q64 标记来将它加载到 64 位 JVM 地址空间中。如果您使用的是 32 位 JVM,那么创建 32 位的共享程序库。尝试将 64 位对象加载到 32 位地址空间,或者反之,都会导致 java.lang.UnsatisfiedLinkError
  5. 选项 –qarch=auto 生成的指令将在程序被编译的硬件平台上运行。IBM XL C/C++ 编译器有很多通用的和特定于硬件的编译器优化选项,让您能够生成极度优化的代码。
  6. 选项 –qmkshrobj 生成共享对象;gcc 中相应的选项为 –shared
  7. 要指向新创建的共享程序库的位置,可能需要设置 LD_LIBRARY_PATH 全局变量。

链接 JNI 共享对象示例

让我们来修改第一个示例,让 displayHelloWorld() 去调用来自多个对象文件的函数。代码的 Java 部分,即 HelloWorld.java 文件,与前一个示例保持相同,但我们修改本地代码。


清单 4. HelloWorld.c





清单 5. share1.c




清单 6. share2.c




编译

下面的 Makefile 说明了编译的过程。输入 make 来开始编译。


清单 7. Makefile





运行





输出



使用静态编译时链接的 JNI Invocation API 用法示例

这个 C++ 示例展示了如何使用 JNI Invocation API —— 也就是说,将 JVM 嵌入到本地 C/C++ 代码之中,然后调用以 Java 编写的应用程序中可执行的部分。


清单 8. invoke.cpp





清单 9. Prog.java




编译

下面的 Makefile 说明了编译的过程。输入 make 来运行它。


清单 10. Makefile





运行





输出





注意
  1. 由于我们使用的是 64 位的 JVM,所以我们也必须将 C++ 调用代码编译为 64 位的。没有办法将 64 位对象加载到 32 位地址空间,反之亦然。
  2. 我们将要使用的是 JNI_VERSION_1_4(0x00010004)。在最新的 JDK 中,调用 JNI_CreateJavaVM() 时传递给它的版本号再也不能是 JNI_VERSION_1_1(0x00010001)。可以传递的版本号是 JNI_VERSION_1_2(0x00010002) 或 JNI_VERSION_1_4(0x00010004)。
  3. 当通过 JNI Invocation API 创建 JVM 时,通过向将要被调用的嵌入的 JVM 传递 JVM 参数,可以控制 JVM 参数,从而不依赖于默认的值。例如,使用:
    -Xms<size> 来设置初始的 Java 堆大小;
    -Xmx<size> 来设置 Java 堆的最大值;
    -Xoss<size> 来设置任意线程的 Java 栈的最大大小;
    -Xss<size> 来设置任意线程的本地栈的最大大小。
    请注意,-X 选项不是标准的,可能会不加声明地被修改。
  4. 重要的是不要忘记同时从 ../jre/bin/classic 和 ../jre/bin 链接到 JVM 程序库。
  5. 对于具有使用 JNI Invocation API 的本地代码的所有者为 root 的文件,要记得设置 SETUID 位。如果非 root 用户启动了这种所有者为 root 而且设置了 SETUID 的可执行文件,出于安全,LD_LIBRARY_PATH 会被清除,从而将找不到所需要的程序库。
  6. 可能需要设置 LD_LIBRARY_PATH 全局变量,令其指向 ../jre/bin/classic 和 ../jre/bin 目录中新创建的共享程序库和 JVM 程序库。

使用动态编译时链接的 JNI Invocation API 用法示例

此 C 代码示例展示了如何通过 dlopen()dlsym() 调用来使用程序库动态加载的 JNI Invocation API。Prog.java 文件的 Java 代码仍然与前一个示例相同。


清单 11. invoke.c





编译

下面的 Makefile 说明了编译的过程。输入 make 来运行它。


清单 12. Makefile





运行




输出




注意
  1. 在前一使用编译时链接示例中需要考虑的事项同样适用。
  2. 要使用 gcc 编译同一示例,则需要执行下面的命令,其中 JAVA_HOME 是您的 JDK 安装主目录:

    
    





回页首


参考资料





回页首


关于作者

Nikolay Yevik 是 IBM eServer Solutions Enablement 团队的一名 Linux 技术顾问,他有 5 年多在 UNIX 平台上进行 C、C++ 和 Java 开发的经验。他拥有石油工程和计算机科学硕士学位。您可以通过 yevik@us.ibm.com 与他联系。


本文转载:CSDN博客