Linux on POWER 的 JNI 编程实例
2006-04-19 08:55
253 查看
级别: 初级
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
编译
编译 Java 代码,生成 HelloWorld.class 文件:
使用
清单 3. HelloWorld.h
编译本地 C 代码:
运行
输出
注意
所有本地 JNI 方法都应该在 Java 代码中声明。
应该在静态代码块中调用
如果对代码的修改改变了函数的符号,那么需要重新运行 javah。
在本例中,我们创建了一个 64 位共享程序库,通过向线程安全可重入编译变量
选项
选项
要指向新创建的共享程序库的位置,可能需要设置 LD_LIBRARY_PATH 全局变量。
链接 JNI 共享对象示例
让我们来修改第一个示例,让
清单 4. HelloWorld.c
清单 5. share1.c
清单 6. share2.c
编译
下面的 Makefile 说明了编译的过程。输入
清单 7. Makefile
运行
输出
使用静态编译时链接的 JNI Invocation API 用法示例
这个 C++ 示例展示了如何使用 JNI Invocation API —— 也就是说,将 JVM 嵌入到本地 C/C++ 代码之中,然后调用以 Java 编写的应用程序中可执行的部分。
清单 8. invoke.cpp
清单 9. Prog.java
编译
下面的 Makefile 说明了编译的过程。输入
清单 10. Makefile
运行
输出
注意
由于我们使用的是 64 位的 JVM,所以我们也必须将 C++ 调用代码编译为 64 位的。没有办法将 64 位对象加载到 32 位地址空间,反之亦然。
我们将要使用的是 JNI_VERSION_1_4(0x00010004)。在最新的 JDK 中,调用
当通过 JNI Invocation API 创建 JVM 时,通过向将要被调用的嵌入的 JVM 传递 JVM 参数,可以控制 JVM 参数,从而不依赖于默认的值。例如,使用:
请注意,
重要的是不要忘记同时从 ../jre/bin/classic 和 ../jre/bin 链接到 JVM 程序库。
对于具有使用 JNI Invocation API 的本地代码的所有者为 root 的文件,要记得设置 SETUID 位。如果非 root 用户启动了这种所有者为 root 而且设置了 SETUID 的可执行文件,出于安全,LD_LIBRARY_PATH 会被清除,从而将找不到所需要的程序库。
可能需要设置 LD_LIBRARY_PATH 全局变量,令其指向 ../jre/bin/classic 和 ../jre/bin 目录中新创建的共享程序库和 JVM 程序库。
使用动态编译时链接的 JNI Invocation API 用法示例
此 C 代码示例展示了如何通过
清单 11. invoke.c
编译
下面的 Makefile 说明了编译的过程。输入
清单 12. Makefile
运行
输出
注意
在前一使用编译时链接示例中需要考虑的事项同样适用。
要使用 gcc 编译同一示例,则需要执行下面的命令,其中 JAVA_HOME 是您的 JDK 安装主目录:
参考资料
您可以参阅本文在 developerWorks 全球站点上的 英文原文。
Java environments for Linux on POWER architecture(developerWorks,2004 年 10 月)给出了关于当前可用于 POWER 上 Linux 的 Java Development Kits 和 Java Runtime Environments 的简短概述。
JNI Programming on AIX (developerWorks,2004 年 3 月)给出了关于使用 IBM JDK for AIX 开发 Java 本地接口(JNI)应用程序的全面指导。
希望在 Power 体系结构上开始工作的 Linux 开发者,可在 Linux on POWER Architecture developer’s corner 找到精选的文章和参考资料。
对于有兴趣使自己的应用程序能够在 POWER 上 Linux 中使用并得到促进的独立软件开发商来说, Linux on POWER ISV Resource Center 是一个全面的资源。
Java Native Interface: Programmer's Guide and Specification 给出了关于 JNI 特性及编程技术的教程和详细描述。
Java Native Interface 向您介绍了如何将本地代码集成到用 Java 编写的程序。
关于作者
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
class HelloWorld { /* Native method declaration */ public native void displayHelloWorld(); /* Use static intializer */ static { System.loadLibrary("hello"); } /* Main function calls native method*/ public static void main(String[] args) { new HelloWorld().displayHelloWorld(); } } |
#include <jni.h> #include "HelloWorld.h" #include <stdio.h> JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj) { printf("Hello world!/n"); return; } |
编译 Java 代码,生成 HelloWorld.class 文件:
javac HelloWorld.java |
javah工具生成 HelloWorld.h 文件:
javah –jni HelloWorld |
/* 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: displayHelloWorld * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif |
xlc_r –q64 –qarch=auto –qmkshrobj HelloWorld.c –o libhello.so |
java –cp . HelloWorld |
HelloWorld! |
所有本地 JNI 方法都应该在 Java 代码中声明。
应该在静态代码块中调用
System.loadLibrary()。
如果对代码的修改改变了函数的符号,那么需要重新运行 javah。
在本例中,我们创建了一个 64 位共享程序库,通过向线程安全可重入编译变量
xlc_r传递一个
–q64标记来将它加载到 64 位 JVM 地址空间中。如果您使用的是 32 位 JVM,那么创建 32 位的共享程序库。尝试将 64 位对象加载到 32 位地址空间,或者反之,都会导致
java.lang.UnsatisfiedLinkError。
选项
–qarch=auto生成的指令将在程序被编译的硬件平台上运行。IBM XL C/C++ 编译器有很多通用的和特定于硬件的编译器优化选项,让您能够生成极度优化的代码。
选项
–qmkshrobj生成共享对象;gcc 中相应的选项为
–shared。
要指向新创建的共享程序库的位置,可能需要设置 LD_LIBRARY_PATH 全局变量。
链接 JNI 共享对象示例
让我们来修改第一个示例,让
displayHelloWorld()去调用来自多个对象文件的函数。代码的 Java 部分,即 HelloWorld.java 文件,与前一个示例保持相同,但我们修改本地代码。
清单 4. HelloWorld.c
#include <jni.h> #include "HelloWorld.h" #include stdio.h> extern void func1(), func2(),func3(); JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj) { printf("Hello world!/n"); func1(); func2(); func3(); return; } |
/************ * share1.c: * *************/ #include <stdio.h> void func1() { printf("func1 called/n"); } void func2() { printf("func2 called/n"); } |
/************ * share2.c * *************/ void func3 () { printf("func3 called/n"); } |
下面的 Makefile 说明了编译的过程。输入
make来开始编译。
清单 7. Makefile
# This definition makes JAVA_HOME refer to the JDK specified in your PATH. # You can change this to refer to a specific directory JAVA_HOME=/opt/IBMJava2-ppc64-142 libhello.so: shrsub.o HelloWorld.h xlc_r -M -I. -I$(JAVA_HOME)/include -q64 -qarch=auto - qmkshrobj HelloWorld.c shrsub.o -o libhello.so HelloWorld.h: HelloWorld.class $(JAVA_HOME)/bin/javah -jni HelloWorld HelloWorld.class: $(JAVA_HOME)/bin/javac HelloWorld.java shrsub.o: c.o xlc_r -qmkshrobj -q64 -o shrsub.o share1.o share2.o c.o: xlc_r -q64 -c share1.c xlc_r -q64 -c share2.c |
java –cp . HelloWorld |
Hello world! func1 called func2 called func3 called |
这个 C++ 示例展示了如何使用 JNI Invocation API —— 也就是说,将 JVM 嵌入到本地 C/C++ 代码之中,然后调用以 Java 编写的应用程序中可执行的部分。
清单 8. invoke.cpp
#include <jni.h> #include <stdlib.h> #include <iostream.h> int main() { JNIEnv *env; JavaVM *jvm; JavaVMInitArgs vm_args; JavaVMOption options[5]; jint res; jclass cls; jmethodID mid; jstring jstr; jobjectArray args; options[0].optionString = "-Xms4M"; options[1].optionString = "-Xmx64M"; options[2].optionString = "-Xss512K"; options[3].optionString = "-Xoss400K"; options[4].optionString = "-Djava.class.path=."; vm_args.version = JNI_VERSION_1_4; vm_args.options = options; vm_args.nOptions = 5; vm_args.ignoreUnrecognized = JNI_FALSE; /* Create the Java VM */ res = JNI_CreateJavaVM(&jvm,(void**)&env,&vm_args); if (res < 0) { cerr<< "Can't create Java VM" << endl; goto destroy; } cls = env->FindClass("Prog"); if (cls == 0) { cerr << "Can't find Prog class" << endl; goto destroy; } mid = env->GetStaticMethodID(cls, "main", "([Ljava/lang/String;)V"); if (mid == 0) { cerr<< "Can't find Prog.main" << endl; goto destroy; } jstr = env->NewStringUTF(" from C++!"); if (jstr == 0) { cerr << "Out of memory" << endl; goto destroy; } args = env->NewObjectArray( 1, env->FindClass("java/lang/String"), jstr); if (args == 0) { cerr << "Out of memory" << endl; goto destroy; } env->CallStaticVoidMethod( cls, mid, args); destroy: if (env->ExceptionOccurred()) { env->ExceptionDescribe(); } jvm->DestroyJavaVM(); } |
public class Prog { public static void main(String[] args) { System.out.println("Hello World" + args[0]); } } |
下面的 Makefile 说明了编译的过程。输入
make来运行它。
清单 10. Makefile
# This definition makes JAVA_HOME refer to the JDK specified in your PATH. # You can change this to refer to a specific directory JAVA_HOME=/opt/IBMJava2-ppc64-142 all: invoke Prog.class invoke: xlC -q64 -I$(JAVA_HOME)/include – L$(JAVA_HOME)/jre/bin/classic -L$(JAVA_HOME)/jre/bin -ljvm -o invoke invoke.cpp Prog.class: $(JAVA_HOME)/bin/javac Prog.java clean: rm -f *.class invoke *.o |
./invoke |
Hello World from C++! |
由于我们使用的是 64 位的 JVM,所以我们也必须将 C++ 调用代码编译为 64 位的。没有办法将 64 位对象加载到 32 位地址空间,反之亦然。
我们将要使用的是 JNI_VERSION_1_4(0x00010004)。在最新的 JDK 中,调用
JNI_CreateJavaVM()时传递给它的版本号再也不能是 JNI_VERSION_1_1(0x00010001)。可以传递的版本号是 JNI_VERSION_1_2(0x00010002) 或 JNI_VERSION_1_4(0x00010004)。
当通过 JNI Invocation API 创建 JVM 时,通过向将要被调用的嵌入的 JVM 传递 JVM 参数,可以控制 JVM 参数,从而不依赖于默认的值。例如,使用:
-Xms<size>来设置初始的 Java 堆大小;
-Xmx<size>来设置 Java 堆的最大值;
-Xoss<size>来设置任意线程的 Java 栈的最大大小;
-Xss<size>来设置任意线程的本地栈的最大大小。
请注意,
-X选项不是标准的,可能会不加声明地被修改。
重要的是不要忘记同时从 ../jre/bin/classic 和 ../jre/bin 链接到 JVM 程序库。
对于具有使用 JNI Invocation API 的本地代码的所有者为 root 的文件,要记得设置 SETUID 位。如果非 root 用户启动了这种所有者为 root 而且设置了 SETUID 的可执行文件,出于安全,LD_LIBRARY_PATH 会被清除,从而将找不到所需要的程序库。
可能需要设置 LD_LIBRARY_PATH 全局变量,令其指向 ../jre/bin/classic 和 ../jre/bin 目录中新创建的共享程序库和 JVM 程序库。
使用动态编译时链接的 JNI Invocation API 用法示例
此 C 代码示例展示了如何通过
dlopen()和
dlsym()调用来使用程序库动态加载的 JNI Invocation API。Prog.java 文件的 Java 代码仍然与前一个示例相同。
清单 11. invoke.c
#include <jni.h> #include <stdlib.h> #include <dlfcn.h> main() { JNIEnv *env; JavaVM *jvm; JavaVMInitArgs vm_args; JavaVMOption options[5]; jint res; jclass cls; jmethodID mid; jstring jstr; jobjectArray args; char classpath[1024]; vm_args.version = 0x00010004; vm_args.options = options; vm_args.nOptions = 5; vm_args.ignoreUnrecognized = JNI_FALSE; options[0].optionString = "-Xms4M"; options[1].optionString = "-Xmx64M"; options[2].optionString = "-Xss512K"; options[3].optionString = "-Xoss400K"; options[4].optionString = "-Djava.class.path=."; void* lib_handle = 0; void* (*lib_func)() = 0; lib_handle = dlopen("/opt/IBMJava2-ppc64- 142/jre/bin/classic/libjvm.so", RTLD_NOW); if (!lib_handle) { fprintf(stderr, "dlopen failed/n"); goto destroy; } lib_func = (void*(*)())dlsym(lib_handle, "JNI_GetDefaultJavaVMInitArgs"); lib_func(&vm_args); lib_func = (void*(*)())dlsym(lib_handle, "JNI_CreateJavaVM"); lib_func(&jvm,&env,&vm_args); cls = (*env)->FindClass(env, "Prog"); if (cls == 0) { fprintf(stderr, "Can't find Prog class/n"); goto destroy; } mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V"); if (mid == 0) { fprintf(stderr, "Can't find Prog.main/n"); goto destroy; } jstr = (*env)->NewStringUTF(env, " from C!"); if (jstr == 0) { fprintf(stderr, "Out of memory/n"); goto destroy; } args = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env, "java/lang/String"), jstr); if (args == 0) { fprintf(stderr, "Out of memory/n"); goto destroy; } (*env)->CallStaticVoidMethod(env, cls, mid, args); destroy: if ((*env)->ExceptionOccurred(env)) { (*env)->ExceptionDescribe(env); } (*jvm)->DestroyJavaVM(jvm); } |
下面的 Makefile 说明了编译的过程。输入
make来运行它。
清单 12. Makefile
# This definition makes JAVA_HOME refer to the JDK specified in your PATH. # You can change this to refer to a specific directory JAVA_HOME=/opt/IBMJava2-ppc64-142 all: invoke Prog.class invoke: xlc -I$(JAVA_HOME)/include -L$(JAVA_HOME)/jre/bin/classic -L$(JAVA_HOME)/jre/bin -q64 -qarch=auto -o invoke invoke.c -ljvm Prog.class: $(JAVA_HOME)/bin/javac Prog.java clean: rm -f *.class invoke *.o |
./invoke |
Hello World from C! |
在前一使用编译时链接示例中需要考虑的事项同样适用。
要使用 gcc 编译同一示例,则需要执行下面的命令,其中 JAVA_HOME 是您的 JDK 安装主目录:
gcc -I$(JAVA_HOME)/include -L$(JAVA_HOME)/jre/bin/classic -L$(JAVA_HOME)/jre/bin -m64 -rdynamic -o invoke invoke.c -ldl –ljvm |
![]() ![]() |
![]()
|
您可以参阅本文在 developerWorks 全球站点上的 英文原文。
Java environments for Linux on POWER architecture(developerWorks,2004 年 10 月)给出了关于当前可用于 POWER 上 Linux 的 Java Development Kits 和 Java Runtime Environments 的简短概述。
JNI Programming on AIX (developerWorks,2004 年 3 月)给出了关于使用 IBM JDK for AIX 开发 Java 本地接口(JNI)应用程序的全面指导。
希望在 Power 体系结构上开始工作的 Linux 开发者,可在 Linux on POWER Architecture developer’s corner 找到精选的文章和参考资料。
对于有兴趣使自己的应用程序能够在 POWER 上 Linux 中使用并得到促进的独立软件开发商来说, Linux on POWER ISV Resource Center 是一个全面的资源。
Java Native Interface: Programmer's Guide and Specification 给出了关于 JNI 特性及编程技术的教程和详细描述。
Java Native Interface 向您介绍了如何将本地代码集成到用 Java 编写的程序。
![]() ![]() |
![]()
|
![]() | ||
![]() | Nikolay Yevik 是 IBM eServer Solutions Enablement 团队的一名 Linux 技术顾问,他有 5 年多在 UNIX 平台上进行 C、C++ 和 Java 开发的经验。他拥有石油工程和计算机科学硕士学位。您可以通过 yevik@us.ibm.com 与他联系。 |
相关文章推荐
- Android studio 下JNI编程实例并生成so库
- Java JNI 编程进阶 实例+c++数据类型与jni数据类型转换
- android JNI编程实例
- Android studio 下JNI编程实例并生成so库
- Android studio 下JNI编程实例并生成so库
- android JNI 编程实例
- Android studio 下JNI编程实例并生成so库的实现代码
- Android studio 下JNI编程实例并生成so库
- JAVA socket编程实例 转载
- iPhone编程实例—数据操作之sqlite3
- Android jni/ndk编程三:native访问java
- java网络编程之Http多线程下载应用实例
- python网络编程之TCP通信实例和socketserver框架使用例子
- JNI 调用构造方法和父类实例方法
- Android JNI编程(七)——使用AndroidStudio编写第一个JNI程序
- Java编程实现时间和时间戳相互转换实例
- Java编程实例:计算阶乘的四个例子
- [转]Android编译环境(6) - Android JNI实例
- C# 并发编程 · 经典实例
- MongoDB中MapReduce编程模型使用实例