• JNI——Java调用C/C++函数


    从C/C++到Java,再从Java回到C/C++,今天终于有机会了解了连接Java、C/C++的桥梁——JNI。哈哈!分享一下!
     
    一、简介
    JNI是Java native interface的简写,可以译作Java原生接口。Java可以通过JNI调用C/C++的库,这对于那些对性能要求比较高的Java程序或者Java无法处理的任务无疑是一个很好的方式。
     
    二、目的:Java代码中调用C/C++代码
    三、实现:假设我们的Java程序为J2C.java, C程序为J2C.c, Java与C之间的通信函数名为write2proc;
                  那么write2proc的声明位于J2C.java,实现位于J2C.c;
    四、操作

    1. 编写并编译Java程序

        javac J2C.java => J2C.class
    2. 生成C/C++头文件

        javah J2C => J2C.h (安装JDK后,$JAVA_HOME应该已加入$PATH, 否则使用绝对路径,例如/usr/bin/javah)

    3. 编写对应的C/C++程序:J2C.c

    4. 生成C/C++目标文件
        gcc -I/usr/lib/jvm/java-6-openjdk-amd64/include -I/usr/lib/jvm/java-6-openjdk-amd64/include/linux -fPIC -c J2C.c => J2C.o
    5. 生成C/C++共享库

        gcc -shared -Wl,-soname,libj2c.so.1 -o libj2c.so.1.0 J2C.o => libj2c.so.1.0

    6. 重命名cp libj2c.so.1.0 libj2c.so => libj2c.so

    7. 将共享库加入动态链接库的路径(此例为当前目录)
        export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

    8. 执行Java程序,实现跨语言通信
         java J2C

     

    五、具体过程

    1. 编写并编译J2C.java

     1 import java.lang.management.ManagementFactory;
     2 import java.lang.management.RuntimeMXBean;
     3 
     4 public class J2C
     5 { 
     6      static 
     7      { 
     8           try{ 
     9                // 此处即为本地方法所在链接库名
    10                System.loadLibrary("j2c");
    11           } catch(UnsatisfiedLinkError e) 
    12           { 
    13                System.err.println( "Cannot load J2C library:
     " + 
    14                e.toString() ); 
    15           } 
    16      }
    17 
    18      //声明的本地方法
    19      public static native int write2proc(int pid);
    20 
    21      public static void main(String[] args){
    22 
    23           //获取本进程(即主线程)的pid
    24           final RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
    25           final String info = runtime.getName();
    26           final int index = info.indexOf("@");
    27 
    28 
    29           if (index != -1) {
    30                final int pid = Integer.parseInt(info.substring(0, index));
    31                System.out.println(info);
    32                System.out.println(pid);
    33 
    34                write2proc(pid);
    35           }
    36 
    37 
    38           try{
    39                Thread.sleep(8000);
    40           } catch(InterruptedException e){
    41                e.printStackTrace();
    42           }
    43      }
    44 }

    note:Java程序中System.loadLibrary参数名表示要载入的C/C++共享库,第6步生成的共享库名必须与该参数一致

               即System.loadLibrary(Name) 对应共享库名libName.so (共享库名必须以lib开头)

    2. 生成C头文件J2C.h:javah J2C

     1 /* DO NOT EDIT THIS FILE - it is machine generated */
     2 #include <jni.h>
     3 /* Header for class J2C */
     4 
     5 #ifndef _Included_J2C
     6 #define _Included_J2C
     7 #ifdef __cplusplus
     8 extern "C" {
     9 #endif
    10 /*
    11  * Class: J2C
    12  * Method: write2proc
    13  * Signature: (I)I
    14  */
    15 JNIEXPORT jint JNICALL Java_J2C_write2proc
    16      (JNIEnv *, jclass, jint);
    17 
    18 #ifdef __cplusplus
    19 }
    20 #endif
    21 #endif

     

    note:1. 头文件自动生成,不要修改它;

              2. 函数JNIEXPORT jint JNICALL Java_J2C_write2proc(JNIEnv *, jclass, jint);

                  按照注释的说明是在J2C.java文件的类J2C的方法write2proc处定义,故C程序的实现函数必须与该处签名一致;

     

    3. 编写C程序J2C.c

     1 #include <stdio.h>
     2 
     3 #include "J2C.h"
     4 
     5 JNIEXPORT int JNICALL Java_J2C_write2proc(JNIEnv * env, jobject arg, jint pid) 
     6 {
     7 
     8      printf("current pid is %d
    ", pid);
     9 
    10      return 0;
    11 
    12 }

    4. 编译C程序

        因为C程序里#include "J2C.h"而J2C.h又#include <jni.h>, 而gcc里面默认环境并不知道jni.h是什么东西,故编译时需要告诉编译器jni.h的位置( jni.h在jdk 的$JAVA_HOME/include下面),所以才有了上面的编译参数;

      因为使用gcc编译得到动态库,在jni调用的时候,某些情况会有异常, 可尝试改用g++。

     

    后续的5、6、7、8同前文所述。

     

    六、总结

    1. Java中方法的原型声明与C/C++对应的实现文件定义必须一致(可以通过自动生成的C/C++头文件来比较),尤其是类名和方法名;

    2. Java中System.loadLibrary()载入的共享库名必须与后面C/C++生成的共享库名一致;

    3. JNI步骤很清晰,但是很多小细节容易引起各种问题,so细心点。

     

    附上C/C++中Java方法签名及常见的错误:

    1. 在C/C++函数中实现Java中定义的方法

    函数声明, Java_完整类名_方法名, 完整类名包括了包名.

    在注释中我们可以看到这样一个东西 Signature, 这个是方法的签名. 关于Signature, 下面通过一个表格来说明.

    java类型 Signature 备注
    boolean Z  
    byte B  
    char C  
    short S  
    int I  
    long L  
    float F  
    double D  
    void V  
    object L用/分割的完整类名 例如: Ljava/lang/String表示String类型
    Array [签名 例如: [I表示int数组, [Ljava/lang/String表示String数组
    Method (参数签名)返回类型签名 例如: ([I)I表示参数类型为int数组, 返回int类型的方法

    如本例中的函数声明:

    JNIEXPORT jint JNICALL Java_J2C_write2proc(JNIEnv *, jobject, jint);

    注释中的签名是 Signature: (I)I

     

    2. JNI开发中碰到的"error: request for member 'GetStringUTFChars' in something not a structure or union"

    这个错误在C、C++中需要分开处理。

    C代码: (*env)->GetStringUTFChars(env, string, 0);

    C++代码: env->GetStringUTFChars(string, 0);

    原因:C语言中使用的是结构体的函数指针, 而在C++中使用的还是struct, 我们知道struct在C++中和class的功能是几乎一样的, struct也可以用来定义类, 所以env在C++中是个类对象的指针.

    下面以IBM的一篇经典文章为例 http://www.ibm.com/developerworks/cn/java/l-linux-jni/

    C++:

    // 从 instring 字符串取得指向字符串 UTF 编码的指针

    const char *str = env->GetStringUTFChars( instring, JNI_FALSE ); 
    printf("Hello,%s ",str); 
    // 通知虚拟机本地代码不再需要通过 str 访问 Java 字符串。
    env->ReleaseStringUTFChars( instring, str );

    C:

    // 从 instring 字符串取得指向字符串 UTF 编码的指针

    const char *str = (*env)->GetStringUTFChars( env, instring, JNI_FALSE ); 
    printf("Hello,%s ",str); 
    // 通知虚拟机本地代码不再需要通过 str 访问 Java 字符串。
    (*env)->ReleaseStringUTFChars( env, instring, str );

     

    3. 当使用 JNI 从 Java 程序访问本机代码时,您会遇到许多问题。您会遇到的三个最常见的错误是:

    无法找到动态链接:Exception in thread "main" java.lang.UnsatisfiedLinkError

    它所产生的错误消息是:java.lang.UnsatisfiedLinkError。这通常指无法找到共享库,或者无法找到共享库内特定的本机方法。


    无法找到共享库文件:Cannot load library

    当用 System.loadLibrary(String libname) 方法(参数是文件名)装入库文件时,请确保文件名拼写正确以及没有指定扩展名。还有,确保库文件的位置在类路径中,从而确保 JVM 可以访问该库文件。


    无法找到具有指定说明的方法

    确保您的 C/C++ 函数实现拥有与头文件中的函数说明相同的说明。

     

    How to resolve java.lang.UnsatisfiedLinkError 
    User should check whether-

    1. System.loadLibrary is passed an incorrect parameter:

         - Windows: To load Name.dll, Name is passed to the loadLibrary method.

         -AIX, HP-UX, Solaris, Linux: To load libName.so or libName.a, libName is passed to the loadLibrary method
    2. Native library is already loaded-

         If the native library was already loaded by an application and the same application tries to load it again, this can cause this error.
    3. Native Library is not present in java.library.path or LD_LIBRARY_PATH

     

    参考:

    http://www.ibm.com/developerworks/cn/java/l-linux-jni/

    http://www.cnblogs.com/icejoywoo/archive/2012/02/22/2363709.html

    http://www.cnblogs.com/mimi1/archive/2012/09/27.html

    http://stackoverflow.com/questions/11624503/i-am-trying-to-use-the-freeimage-library-in-my-project-causes-error

     

  • 相关阅读:
    用户验证之自定义身份验证
    再谈CLR:查看程序集的依赖关系
    关于私钥加密、公钥加密、签名在生活中的场景
    MOSS 2010服务器对象模型(Object Model)
    用户身份验证之Windows验证
    由object不能比较引发的问题
    再谈CLR: .NET 4.0新功能:Mscoree.dll + Mscoreei.dll=更少的Reboot (上)
    再谈CLR:事件定义
    WPF:如何为程序添加splashScreen?
    通过反射得到类型的所有成员
  • 原文地址:https://www.cnblogs.com/Seiyagoo/p/3496834.html
Copyright © 2020-2023  润新知