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

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

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-typesret-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对象的方法传递值;

---End---

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值