跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

Linux on POWER 的 JNI 编程实例

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

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

发布日期: 2005 年 3 月 14 日
级别: 初级
访问情况 : 1349 次浏览
评论: 


简介

对那些刚接触 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 中 编译这些示例。

通过 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();
      }
}



清单 2. HelloWorld.c
                    
#include <jni.h>
#include "HelloWorld.h"
#include <stdio.h>
JNIEXPORT void JNICALL
Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj)
{
    printf("Hello world!\n");
    return;
}


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

    javac HelloWorld.java


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

     javah –jni HelloWorld
    



    清单 3. HelloWorld.h
                            
    /* 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
    


  3. 编译本地 C 代码:

    xlc_r –q64 –qarch=auto –qmkshrobj HelloWorld.c –o libhello.so
    



运行

java –cp . HelloWorld



输出

HelloWorld!



注意
  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
                
#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;
}



清单 5. share1.c
                
/************
* share1.c: *
*************/
#include <stdio.h>
void func1()
{
   printf("func1 called\n");
}
void func2()
{
   printf("func2 called\n");
}



清单 6. share2.c
                
/************
* 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

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

这个 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();
}



清单 9. Prog.java
                
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++!



注意
  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
                
#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!



注意
  1. 在前一使用编译时链接示例中需要考虑的事项同样适用。
  2. 要使用 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


参考资料

关于作者

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

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Linux
ArticleID=58283
ArticleTitle=Linux on POWER 的 JNI 编程实例
publish-date=03142005
author1-email=yevik@us.ibm.com
author1-email-cc=

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。