您的位置:首页 > 编程语言 > Qt开发

JNI加载Native Library 以及 跨线程和Qt通信

2013-10-24 11:56 441 查看


JNI加载Native Library 以及 跨线程和Qt通信

Part1
Java Native Interface-JNI-JAVA本地调用

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

开始实现->
Step 1) 编写Java代码, 编写一个JNI接口HelloJNI.java

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

编译Java生成class;

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

在相应的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的实现:

>jni.h的路径一般是在<JAVA_HOME>\include" 和 "<JAVA_HOME>\include\win32";
Note 不同环境下的编译选项是不同的;
Windows下的gcc必须加上一个参数 "-Wl,--add-stdcall-alias -shared";

VC++的cl和Linux下的gcc不需要这个参数;

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

有些时候也会使用 "-Wl,--kill-at".

>"-I"指定JNI头文件路径, 路径有空格时加上双引号.
可以使用nm命令列出函数导出的外部符号:

>windows: "nm -g file.dll"

Step 4) 运行JNI程序

>"-Djava.library.path=<path_to_lib>" 是可选的, 作为虚拟机的选项来制定动态库的路径.
Linux下可能需要设置路径:

设置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"

JNI在Package里的case

>JNI的类会被放入"myjni"的package内, 文件保存为"myjni\HelloJNI.java"
编译: 加上package(路径)名

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

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

---End---

Part2
JNI数据转换成C数据
e.g. jstring - GetStringUTFChars(), NewStringUTF(), ReleaseStringUTFChars()

e.g. Array

<Refer to> /article/4490680.html
JNI的数据定义

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 TypeNative TypeDescription
booleanjboolean8 bits, unsigned
bytejbyte8 bits, signed
charjchar16 bits, unsigned
doublejdouble64 bits
floatjfloat32 bits
intjint32 bits, signed
longjlong64 bits, signed
shortjshort16 bits, signed
voidvoidN/A
JNI的类型签名
Java TypeSignature
booleanZ
byteB
charC
doubleD
floatF
intI
longJ
voidV
objectLfully-qualified-class;
type[][type
method signature( arg-types) ret-type
e.g.
Java side

C++side

查看属性和方法的签名
Java版本 "java -version"
反编译工具 javap:

Check JNI version

使用API

返回值需要转换, 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 & /article/10419329.html
[我机器上装了多个版本的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会在另一个进程开始.

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

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

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

C++方面, 可以使用static instance的方式来引用Qt类;
Qt class: 类似singleton, 可以在JNI的cpp函数实现中引用静态的Qt的类来启动Qt程序;

JNI函数启动Qt程序

跨线程通信
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)

>这样就能跨线程调用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---
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: