Part1
Java Native Interface-JNI-JAVA本地调用
JNI标准是Java平台的一部分, 允许Java代码和其他语言进行交互;
开始实现->
Step 1) 编写Java代码, 编写一个JNI接口HelloJNI.java
1
2
3
4
5
6
7
8
9
10
11
|
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
1
|
javac HelloJNI.java
|
编译Java生成class;
1
|
javah HelloJNI
|
javah命令会生成相应的header: HelloJNI.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/* 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的实现:
1
2
3
4
5
6
|
#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不需要这个参数;
1
|
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命令列出函数导出的外部符号:
1
|
nm hello.dll | grep say
|
>windows: "nm -g file.dll"
Step 4) 运行JNI程序
1
|
java -Djava.library.path=. HelloJNI
|
>"-Djava.library.path=<path_to_lib>" 是可选的, 作为虚拟机的选项来制定动态库的路径.
Linux下可能需要设置路径:
1
|
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"
1
|
javah -o "<HeaderPATH>\JNIHeader.h" -jni -classpath "JavaClassPath" JNISample
|
JNI在Package里的case
1
|
package myjni; //...Java codes
|
>JNI的类会被放入"myjni"的package内, 文件保存为"myjni\HelloJNI.java"
编译: 加上package(路径)名
1
|
javac myjni\HelloJNI.java
|
javah: 使用package名, 把头文件生成到include文件夹下.
1
|
javah -d include myini.HelloJNI
|
头文件的native函数: Java_<fully-qualified-name>_methodName
1
|
JNIEXPORT void JNICALL Java_myjni_HelloJNI_sayHello(JNIEnv *, jobject);
|
---End---
Part2
JNI数据转换成C数据
e.g. jstring - GetStringUTFChars(), NewStringUTF(), ReleaseStringUTFChars()
1
2
3
4
5
|
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
1
2
3
4
5
6
7
8
9
10
11
12
|
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的数据定义
1
2
3
4
5
6
7
8
9
10
11
12
|
// 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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:
1
|
javap -s -p -classpath R:\test.Demo
|
Check JNI version
1
2
3
|
#ifdef JNI_VERSION_1_4
printf ( "Version is 1.4 \n" );
#endif
|
使用API
1
|
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会在另一个进程开始.
1
2
3
4
5
6
7
8
9
10
|
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需要另起一个线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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方法
1
|
public native void launchSample();
|
C++方面, 可以使用static instance的方式来引用Qt类;
Qt class: 类似singleton, 可以在JNI的cpp函数实现中引用静态的Qt的类来启动Qt程序;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class QML_EXPORT QMLSample : public QObject
{
Q_OBJECT
public :
static QMLSample * GetInstance();
private :
QMLSample ();
private :
QDeclarativeView* mpView;
JNIEnv* mpEnv;
static QMLSample * mpSInstance;
};
|
JNI函数启动Qt程序
1
2
3
4
5
6
7
8
9
10
11
|
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)
1
2
3
4
5
6
7
|
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对象的方法传递值;
转载自:http://www.cnblogs.com/roymuste/p/3139583.html