JNI加載Native Library 以及 跨線程和Qt通訊

Part1html

Java Native Interface-JNI-JAVA本地調用
java

JNI標準是Java平臺的一部分, 容許Java代碼和其餘語言進行交互;
git

開始實現->github

Step 1) 編寫Java代碼, 編寫一個JNI接口HelloJNI.javawindows

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++的函數; bash

loadLibrary()會在當前路徑(其實是Java Library Path)下尋找並加載名爲hello的動態庫, 全部的dependency都會在當前路徑下加載; 
對於不一樣的平臺loadLibrary()會自動搜索不一樣的後綴名; e.g. Sample.dll(Windows), libSample.so(Linux);
你也能夠指定路徑: "-Djava.library.path=path_to_lib", 路徑錯誤的話會有"UnsatisfiedLinkError";
oracle

相應還有load()函數, 須要指定路徑和dependency的路徑; 
dlltool http://sourceware.org/binutils/docs/binutils/dlltool.htmlapp

Note 動態庫加載必須在static塊內, 保證首先進行;異步

 

Step 2) 編譯和生成C/C++ headerjvm

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!\n");
   return;
}

>jni.h的路徑通常是在<JAVA_HOME>\include" 和 "<JAVA_HOME>\include\win32";

Note 不一樣環境下的編譯選項是不一樣的;

Windows下的gcc必須加上一個參數 "-Wl,--add-stdcall-alias -shared"; 
VC++的cl和Linux下的gcc不須要這個參數;

gcc -Wl,--add-stdcall-alias -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -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)\Java\jdk1.7.0_17\include" -I"C:\Program Files (x86)\Java\jdk1.7.0_17\include\win32" -shared -o HelloWorld.dll HelloWorld.c"
"cl -I%java_home%\include -I%java_home%\include\win32 -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:\Projects\JAVA\JavaJNIDemo\build\classes" javajnidemo.JavaJNIDemo"

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

 

JNI在Package裏的case

package myjni; //...Java codes

>JNI的類會被放入"myjni"的package內, 文件保存爲"myjni\HelloJNI.java"

編譯: 加上package(路徑)名

javac myjni\HelloJNI.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 "win\jni_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\n");
    // 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:\test.Demo

Check JNI version

#ifdef JNI_VERSION_1_4     
printf("Version is 1.4 \n");   
#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)\Java\jdk1.7.0_17\jre\bin\client; C:\Program Files (x86)\Java\jdk1.7.0_17\jre\bin\server; need check); jvm.lib(C:\Program Files (x86)\Java\jdk1.7.0_17\lib)

 

<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---

相關文章
相關標籤/搜索