zoukankan      html  css  js  c++  java
  • JNI加载Native Library 以及 跨线程和Qt通信

    Part1

    Java Native Interface-JNI-JAVA本地调用

    JNI标准是Java平台的一部分, 允许Java代码和其他语言进行交互;

    开始实现->

    Step 1) 编写Java代码, 编写一个JNI接口HelloJNI.java

    public class HelloJNI {
       static {
          System.loadLibrary("hello"); // hello.dll (Windows) or libhello.so (Unixes)
       }
       // A native method that receives nothing and returns void
       private native void sayHello();
    
       public static void main(String[] args) {
          new HelloJNI().sayHello();  // invoke the native method
       }
    }
    

    sayHello()是一个native方法, 这个方法会在生成的JNI header文件中声明C/C++的函数; 

    loadLibrary()会在当前路径(实际上是Java Library Path)下寻找并加载名为hello的动态库, 所有的dependency都会在当前路径下加载; 
    对于不同的平台loadLibrary()会自动搜索不同的后缀名; e.g. Sample.dll(Windows), libSample.so(Linux);
    你也可以指定路径: "-Djava.library.path=path_to_lib", 路径错误的话会有"UnsatisfiedLinkError";

    相应还有load()函数, 需要指定路径和dependency的路径; 
    dlltool http://sourceware.org/binutils/docs/binutils/dlltool.html

    Note 动态库加载必须在static块内, 保证首先进行;

    Step 2) 编译和生成C/C++ header

    javac HelloJNI.java
    

    编译Java生成class;

    javah HelloJNI
    

    javah命令会生成相应的header: HelloJNI.h

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class HelloJNI */
    
    #ifndef _Included_HelloJNI
    #define _Included_HelloJNI
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     HelloJNI
     * Method:    sayHello
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_HelloJNI_sayHello
      (JNIEnv *, jobject);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    在相应的cpp文件中实现函数: JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

    名字转换的格式: Java_{package_and_classname}_{function_name}(JNI arguments)

    -JNIEnv*参数: 指向JNI的环境, 给你调用JNI函数的权限;

    -jobject参数: 指向Java的"this"对象;

    -extern "C"会被C++编译器识别, 把函数用C的命名方式来编译.

    Step 3) 编译C/C++库

    HelloJN.cpp的实现:

    #include <stdio.h>
    #include "HelloJNI.h"
    JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
       printf("Hello World!
    ");
       return;
    }
    

    >jni.h的路径一般是在<JAVA_HOME>include" 和 "<JAVA_HOME>includewin32";

    Note 不同环境下的编译选项是不同的;

    Windows下的gcc必须加上一个参数 "-Wl,--add-stdcall-alias -shared"; 
    VC++的cl和Linux下的gcc不需要这个参数;

    gcc -Wl,--add-stdcall-alias -I"<JAVA_HOME>include" -I"<JAVA_HOME>includewin32" -shared -o hello.dll HelloJNI.cpp
    

    >"-Wl"会把选项"--add-stdcall-alias"传输给链接器, 防止"UnsatisifiedLinkError".(一般导出的名字有"@nn"的前缀, 这个选项会把导出的名字加上没有前缀的别名)
    有些时候也会使用 "-Wl,--kill-at".

    >"-I"指定JNI头文件路径, 路径有空格时加上双引号.

    可以使用nm命令列出函数导出的外部符号: 

    nm hello.dll |grep say
    

    >windows: "nm -g file.dll"

    Step 4) 运行JNI程序

    java -Djava.library.path=. HelloJNI
    

    >"-Djava.library.path=<path_to_lib>" 是可选的, 作为虚拟机的选项来制定动态库的路径.

    Linux下可能需要设置路径:

    export LD_LIBRARY_PATH=.
    

    设置library路径为当前目录, 或者将so放入java执行目录下;

     

    Other

    编译链接相关

    alias name that without @

    "gcc -Wl,--add-stdcall-alias -I"C:Program Files (x86)Javajdk1.7.0_17include" -I"C:Program Files (x86)Javajdk1.7.0_17includewin32" -shared -o HelloWorld.dll HelloWorld.c"
    "cl -I%java_home%include -I%java_home%includewin32 -LD HelloWorldImp.c -Fehello.dll"

    Compile time and Link Time: -L, -I, -Wl,rpath=<your_lib_dir>

    Linux: LD_LIBRARY_PATH; ldd; ldconfig; nm; readlf; Id; 

    <refer to> http://blog.csdn.net/unbutun/article/details/6362474 & http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html

    JNI header

    classpath: should point to the root folder where your top level package (JNI) goes to, not to the folder where your class is physically located.

    http://stackoverflow.com/questions/14795050/javah-command-for-native-methods-gives-exception

    1) "javah HelloWorld" (all the config is set)

    2) "javah -o "JNIDemo.h" -jni -classpath "R:ProjectsJAVAJavaJNIDemouildclasses" javajnidemo.JavaJNIDemo"

    javah -o "<HeaderPATH>JNIHeader.h" -jni -classpath "JavaClassPath" JNISample
    

     

    JNI在Package里的case

    package myjni; //...Java codes
    

    >JNI的类会被放入"myjni"的package内, 文件保存为"myjniHelloJNI.java"

    编译: 加上package(路径)名

    javac myjniHelloJNI.java
    

    javah: 使用package名, 把头文件生成到include文件夹下.

    javah -d include myini.HelloJNI
    

    头文件的native函数: Java_<fully-qualified-name>_methodName

    JNIEXPORT void JNICALL Java_myjni_HelloJNI_sayHello(JNIEnv *, jobject);
    

    ---End---

    Part2

    JNI数据转换成C数据

    e.g. jstring - GetStringUTFChars(), NewStringUTF(), ReleaseStringUTFChars()

    JNIEXPORT void JNICALL Java_JNISample_sampleFunction(JNIEnv* env, jobject obj, jstring name)  
    {  
        const char* pname = env->GetStringUTFChars(name, NULL);  
        env->ReleaseStringUTFChars(name, pname);  
    }
    

    e.g. Array

    JNIEXPORT jint JNICALL Java_IntArray_sumArray   
            (JNIEnv *env, jobject obj, jintArray arr) {   
        jint buf[10];   
        jint i, sum = 0;   
        // This line is necessary, since Java arrays are not guaranteed   
        // to have a continuous memory layout like C arrays.   
        env->GetIntArrayRegion(arr, 0, 10, buf);   
        for (i = 0; i < 10; i++) {   
            sum += buf[i];   
        }   
        return sum;   
    }
    

    <Refer to> http://ironurbane.iteye.com/blog/425513

    JNI的数据定义

    // In "winjni_mh.h" - machine header which is machine dependent
    typedef long            jint;
    typedef __int64         jlong;
    typedef signed char     jbyte;
    
    // In "jni.h"
    typedef unsigned char   jboolean;
    typedef unsigned short  jchar;
    typedef short           jshort;
    typedef float           jfloat;
    typedef double          jdouble;
    typedef jint            jsize;
    

     

    C++ 调用Java方法

    Read: http://stackoverflow.com/questions/819536/how-to-call-java-function-from-c

    Windows http://public0821.iteye.com/blog/423941

    Linux http://blog.sina.com.cn/s/blog_48eef8410100fjxr.html

    JNI数据类型

    Java Type Native Type Description
    boolean jboolean 8 bits, unsigned
    byte jbyte 8 bits, signed
    char jchar 16 bits, unsigned
    double jdouble 64 bits
    float jfloat 32 bits
    int jint 32 bits, signed
    long jlong 64 bits, signed
    short jshort 16 bits, signed
    void void N/A

    JNI的类型签名

    Java Type Signature
    boolean Z
    byte B
    char C
    double D
    float F
    int I
    long J
    void V
    object Lfully-qualified-class;
    type[] [type
    method signature arg-typesret-type

    e.g.

    Java side

    class JNISample 
    {
        public native void launchSample();
        static
        {
            System.loadLibrary("Sample"); 
        }
    
        public static int add(int a,int b) {
            return a+b;
        }
        public boolean judge(boolean bool) {
            return !bool;
        }
    }
    

    C++side

    JNIEnv *env = GetJNIEnv(); //Get env from JNI
    jclass cls;
    cls = env->FindClass("JNISample");
    if(cls !=0)
    {
        printf("find java class success
    ");
        // constructor
        mid = env->GetMethodID(cls,"<init>","()V");
        if(mid !=0)
        {
            jobj=env->NewObject(cls,mid);
        }
    
        // static function
        mid = env->GetStaticMethodID( cls, "add", "(II)I");
        if(mid !=0)
        {
            square = env->CallStaticIntMethod( cls, mid, 5,5);
        }
    
        // function returns boolean
        mid = env->GetMethodID( cls, "judge","(Z)Z");
        if(mid !=0){
            jnot = env->CallBooleanMethod(jobj, mid, 1);
        }
    }
    

    查看属性和方法的签名

    Java版本 "java -version"

    反编译工具 javap: 

    javap -s -p -classpath R:	est.Demo
    

    Check JNI version

    #ifdef JNI_VERSION_1_4     
    printf("Version is 1.4 
    ");   
    #endif
    

    使用API

    jint GetVersion(JNIEnv *env);
    

    返回值需要转换, Need convert the result from DEC to HEX;

    JNI实现过程中的Issue

    x86 or x64 "Can't load load IA 32-bit dll on a amd 64 bit platform" 

    确定本机上的默认JVM的版本和动态库的版本一致(x86或x64), Make sure JAVA's default path; check with "java -version" in command line.

    3rdParty can't find dependent libraries 保证所依赖的动态库都能被找到;

    1) copy the dll into executable file's folder 2) System.load() the dlls by dependecy orders

    JNI_CreateJavaVM failed 

    C++创建JVM调用Java方法 

    http://docs.oracle.com/javase/1.4.2/docs/guide/jni/jni-12.html#JNI_CreateJavaVM & http://blog.csdn.net/louka/article/details/7318656

    [我机器上装了多个版本的Java, 测试的时候没有成功]

    jvm.dll(C:Program Files (x86)Javajdk1.7.0_17jreinclient; C:Program Files (x86)Javajdk1.7.0_17jreinserver; need check); jvm.lib(C:Program Files (x86)Javajdk1.7.0_17lib)

    <Refer to> http://home.pacifier.com/~mmead/jni/cs510ajp/ & http://www.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html

    Sample http://chnic.iteye.com/category/20179

    JNI doc http://docs.oracle.com/javase/7/docs/technotes/guides/jni/ 

    >JNA https://github.com/twall/jna/  XstartOnFirstThread

    ---End---

     

    Part 3

    启动Qt程序

    通过Java启动Qt程序可以调用命令行, 这样Qt会在另一个进程开始.

    public static void launchSampleApp() {  
       Runtime rn = Runtime.getRuntime();  
       Process p = null;  
       try {  
           String command = "QtAppSample";  
           p = rn.exec(command);  
       } catch (Exception e) {  
           System.out.println("JAVA Failed to launch Sample.");  
       }  
    }
    

    >用进程启动Qt可能在通信效率和资源共享方面有些影响.

    Qt事件循环是个dead loop, 如果直接在JNI中启动Qt程序会把Java的主线程Block住;  Qt main event loop will block the Java main thread;

    Java 启动Qt需要另起一个线程

    class Main 
    {
        public static JNISample sample = new JNISample();
        public static void main(String[] args) 
        {
            Thread t = new Thread(new Runnable() {
    
                public void run() {  
                    sample.launchSample();  
                }       
            });     
            t.start();
        }
    }
    

    >JNISample的launchSample()函数是一个native方法

    public native void launchSample();
    

    C++方面, 可以使用static instance的方式来引用Qt类;

    Qt class: 类似singleton, 可以在JNI的cpp函数实现中引用静态的Qt的类来启动Qt程序;

    class QML_EXPORT QMLSample : public QObject
    {
        Q_OBJECT
    
    public:
        static QMLSample * GetInstance();
    
    private:
        QMLSample ();
    
    private:
        QDeclarativeView* mpView;
        JNIEnv* mpEnv;
        static QMLSample * mpSInstance;
    };
    

    JNI函数启动Qt程序

    JNIEXPORT void JNICALL Java_JNISample_launchSample
      (JNIEnv *env, jobject obj)
    {
        Q_UNUSED(obj);
    
        int argc = 0; char** argv = NULL;
        QApplication app(argc, argv);
        QMLSample::GetInstance()->Show();
        QMLSample::GetInstance()->SetJNIEnv(env);
        app.exec();
    }
    

     

    跨线程通信

    signal/slot 

    Java在子线程启动了Qt, 如果Java要向Qt发送消息的话, 需要使用signal/slot的方式.

    Note 如果直接使用JNI调用Qt的directly方法, e.g. setWindowTitle(), Qt会报错: "setProperty : Cannot send events to objects owned by a different thread"

    除了 1)signal/slot, 还可以显式使用 2)QMetaObject::invoke(), 利用MetaObject机制调用Qt函数

    Note 信号发送方式需要改为 Qt::QueuedConnection (或者使用默认的AutoConnection)

    e.g,2)

    const QMetaObject* metaObj = QMLSample::GetInstance()->metaObject();
    int methodIndex = metaObj->indexOfMethod("FunctionName(int,QString)");
    QMetaMethod method = metaObj->method(methodIndex);
    bool ret = method.invoke(QMLDLLSample::GetInstance(),
                          Qt::AutoConnection,
                          Q_ARG(int, i),
                          Q_ARG(QString, string));
    

    >这样就能跨线程调用Qt动态库的函数;

    Note invoke的格式必须严格遵守, 多一个空格就错, must stictly follow the format, e.g.:metaObj->indexOfMethod("Function(int,QString)"), no space is allowed between "int," and "QString".

    对于MetaObject无法识别的类型: 使用qRegisterMetaType()来注册: "QMetaMethod::invoke: Unable to handle unregistered datatype 'MyType'"

    使用invoke异步调用函数的时候, 是无法得到return的返回值的: "It is unable to QMetaObject::invokeMethod with return values in queued connections"

    Solution: 1) 把函数的参数改为指针, 来传递想要得到的值; ---由于是在异步的消息机制下, 这个也是不行的;
    所以只能这样: 2) 得到值以后再发个消息....或者调用Java对象的方法传递值;

    ---End---

  • 相关阅读:
    [学习笔记] Symfony2学习笔记之数据库操作 [转]
    [学习笔记] Twig 的 tags学习 [转]
    [学习笔记] 设计模式之状态机模式 [转]
    【转】Lombok介绍、使用方法和总结
    RabbitMQ
    百度云下载不限速方法+软件
    json数据的key的读取和替换
    spring boot配置mybatis和事务管理
    windows强大的快捷键
    rtsp向rtmp推流
  • 原文地址:https://www.cnblogs.com/roymuste/p/3139583.html
Copyright © 2011-2022 走看看