zoukankan      html  css  js  c++  java
  • JNI使用杂记

    今天遇到一个需要在java里面调用C++的情况,网上一查,吓了一跳,原来这个东东还有专门的方法:JNI

    废话不说,先上笔记:

    听到这个名词后,我首先找到了如下一篇超好的文章

    Linux下JNI的使用【转】

     

     Linux下 JNI的使用

           学习Android其中涉及对JNI的使用,对于这种跨语言的调用真没有见过,

    Java也都是最近才学的更别说对JNI的了解了,

    JNI的使用对于Android来说又是十分的重要和关键。那么到底Java到底是如何调用C/C++的,

    通过网络达人的总结中学习,自己也顺便总结一下这个学习的过程。

    什么是JNI

         JNI是Java native interface的简写,可以译作Java原生接口。

    Java可以通过JNI调用C/C++的库,这对于那些对性能要求比较高的Java程序无疑是一个福音。

    JNI是Java与C/C++交互的接口。

          使用JNI也是有代价。大家都知道JAVA程序是运行在JVM之上的,可以做到平台无关。

    但是如果Java程序通过JNI调用了原生的代码(比如c/c++等),则Java程序就丧失了平台无关性。

    最起码需要重新编译原生代码部分。所以应用JNI需要好好权衡,不到万不得已,请不要选择JNI,

    可以选择替代方案,比如TCP/IP进行进程间通讯等等。这也是为什么谷歌的Android平台的底层虽然用JNI实现,

    但是他不建议开发人员用JNI来开发Android上面的应用的原因。将会丧失Android上面的应用程序平台无关性。

    代码实例

          下面以HelloWorld的实现学习Linux下 JNI的使用。

    第一步:

    创建一个 TestJni.java文件

    复制代码
    import java.util.*;
    public class TestJni
    {
          //声明原生函数:参数为String类型
          public native void print(String content);
          //加载本地库代码     
          static
          {
               System.loadLibrary("TestJni");
          }
    }
    复制代码

    编译 TestJni.java文件:javac TestJni.java

    在当前文件夹下生成TestJni.class文件

          注意print方法的声明,关键字native表明该方法是一个原生代码实现的。

    另外注意static代码段的System.loadLibrary调用,这段代码表示在程序加载的时候,自动加载libTestJni.so库。

    第二步:

    生成 TestJni.h文件

    执行命令:javah -jni TestJni

          生成TestJni.h文件

    复制代码
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class TestJni */
    
    #ifndef _Included_TestJni
    
    #define _Included_TestJni
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     TestJni
     * Method:    print
     * Signature: (Ljava/lang/String;)V
     */
    JNIEXPORT void JNICALL Java_TestJni_print
      (JNIEnv *, jobject, jstring);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    复制代码

    该文件中包含了一个函数Java_TestJni_print的声明。这里面自动包含两个参数,非常重要。JNIEnv *和 jobject

    第三步:

          创建TestJni.c文件

    复制代码
    #include <jni.h>
    #include <stdio.h>
    #include <TestJni.h>
    
    JNIEXPORT void JNICALL
          Java_TestJni_print(JNIEnv *env,jobject obj, jstring content)
    {
          // 从 instring 字符串取得指向字符串 UTF 编码的指针
          //注意C语言必须(*env)->         C++ env->
          const jbyte *str =
               (const jbyte *)(*env)->GetStringUTFChars(env,content, JNI_FALSE);
          printf("Hello---->%s
    ",str);
    // 通知虚拟机本地代码不再需要通过 str 访问 Java 字符串。 (*env)->ReleaseStringUTFChars(env, content, (const char *)str ); return; }
    复制代码

    //这里看到  JNIEnv作用了,使得我们可以使用Java的方法

    //jobject 指向在此 Java 代码中实例化的 Java 对象 LocalFunction 的一个句柄,相当于 this 指针

    参数类型 jstring 对应java中的String,这里是有所不同的。每一个Java里的类型这里有对应的与之匹配。

    命令行输入:

          cc -I/usr/lib/jvm/java-6-sun/include/linux/

      -I/usr/lib/jvm/java-6-sun/include/

      -I/home/xmp/AndroidProject/apk/JNI 

      -fPIC -shared -o libTestJni.so TestJni.c

    生成:libTestJni.so库文件

      在当前目录生成libTestJni.so。注意一定需要包含Java的include目录(请根据自己系统环境设定),

    因为libTestJni.c中包含了jni.h。另外一个值得注意的是在libTestJni.java中我们LoadLibrary方法加载的是“TestJni”,

    可我们生成的Library却是libTestJni。这是Linux的链接规定的,

    一个库的必须要是:lib+库名+.so。链接的时候只需要提供库名就可以了

      -I/home/xmp/AndroidProject/apk/JNI 是我自己的练习目录也必须包含,否则.c文件中会找不到TestJni.h头文件。

      现在 liblibTestJni.so就是一个可以使用的库了,其功能就是有一个print函数 与刚才所创建的TestJni.java文件对应,

    TestJni.java类中加载库liblibTestJni.so,声明了其函数print。所以现在TestJni.java中具备使用print函数的功能;

    所以现在我们就可以通过使用TestJni.java来使用调用C库libTestJni.so中的函数

    当然现在任何java类都可已加载liblibTestJni.so库来使用其中的功能。

    到这里我没有遇到任何奇怪的问题,一切的是那么的顺利,在这里感谢原作者!!!

    第四步:

         创建HelloWord.java函数

    复制代码
    import java.util.*;
    public class HelloWorld
    {
         public static void main(String argv[])
         {
             new HelloWorld();
         }
    
         public HelloWorld()
         {
             new TestJni().print("Hello,World !"); //调用TestJni的原生函数print
         }
    }
    复制代码

    输入命令编译: javac HelloWorld.java

    生成HelloWorld.class

      ============这是分割线===============

    第五步:

          运行HelloWorld程序

    命令行输入:java HelloWorld

    然后得到了如下奇怪的错误:

    Java HotSpot(TM) Server VM warning: You have loaded library /home/candy/test/java/JNI/libTestJni.so which might have disabled stack guard. The VM will try to fix the stack guard now.
    It's highly recommended that you fix the library with 'execstack -c <libfile>', or link it with '-z noexecstack'.
    Exception in thread "main" java.lang.UnsatisfiedLinkError: /home/candy/test/java/JNI/libTestJni.so: /home/candy/test/java/JNI/libTestJni.so: 错误 ELF 类: ELFCLASS64 (Possible cause: architecture word width mismatch)
    at java.lang.ClassLoader$NativeLibrary.load(Native Method)
    at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1929)
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1847)
    at java.lang.Runtime.loadLibrary0(Runtime.java:870)
    at java.lang.System.loadLibrary(System.java:1119)
    at TestJni.<clinit>(TestJni.java:10)
    at HelloWorld.<init>(HelloWorld.java:12)
    at HelloWorld.main(HelloWorld.java:7)

    通过在网上查资料显示,可能的错误有:

    1. 可能是需要的库,看看需要什么库,然后拷贝进当前目录

    2.可能是环境变量没设置好

     解决export LD_LIBRARY_PATH=“HelloWorld路径”:$LD_LIBRARY_PATH   设置环境变量

    3.这一条是我自己遇到的,jdk版本不兼容,系统是64位的,而我的jdk却是32位的,这最后被证明是我的问题的根本所在

    好的,解决了如上问题,我的java能够顺利调用C++程序了。

    输出结果:Hello---->Hello,World !

    验证OK!

    另一个坑:

    我之前的代码习惯是用下划线分割函数单词,但是后来发现如果在定义JNI函数的话使用下划线,会出现如下报错:

    Exception in thread "main" java.lang.UnsatisfiedLinkError: TestJni.get_int(Ljava/lang/String;)I
    at TestJni.get_int(Native Method)
    at HelloWorld.<init>(HelloWorld.java:16)
    at HelloWorld.main(HelloWorld.java:7)

    最后我将所有的下划线去掉,问题完美解决,究其根本原因可能是JNI解析机制不支持用户定义函数加下划线

  • 相关阅读:
    javascript数组
    Javascript prototype理解
    Javascript中的类的创建
    Div拖动效果
    javascript, position
    getSelection();
    网页的宽和高
    javascript apply & call
    Hibernate数据丢失更新问题及解决 凡人
    招聘软件/网页UI设计师
  • 原文地址:https://www.cnblogs.com/candycloud/p/3913568.html
Copyright © 2011-2022 走看看