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-types) ret-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---