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

时间:2022-10-13 08:44:17

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
publicclass HelloJNI {
   static{
      System.loadLibrary("hello");// hello.dll (Windows) or libhello.so (Unixes)
   }
   // A native method that receives nothing and returns void
   privatenative void sayHello();
 
   publicstatic void main(String[] args) {
      newHelloJNI().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 |grepsay

>windows: "nm -g file.dll"

 

Step 4) 运行JNI程序

1
java -Djava.library.path=. HelloJNI

>"-Djava.library.path=<path_to_lib>" 是可选的, 作为虚拟机的选项来制定动态库的路径.

Linux下可能需要设置路径:

1
exportLD_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://*.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) 

    constchar* 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];  
    }  
    returnsum;  
}

<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
typedeflong            jint;
typedef__int64         jlong;
typedefsigned char    jbyte;
 
// In "jni.h"
typedefunsigned char  jboolean;
typedefunsigned short jchar;
typedefshort           jshort;
typedeffloat           jfloat;
typedefdouble          jdouble;
typedefjint            jsize;

 

 

C++ 调用Java方法

Read: http://*.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
classJNISample
{
    publicnative void launchSample();
    static
    {
        System.loadLibrary("Sample");
    }
 
    publicstatic int add(int a,intb) {
        returna+b;
    }
    publicboolean judge(booleanbool) {
        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
publicstatic 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
classMain
{
    publicstatic JNISample sample =new JNISample();
    publicstatic void main(String[] args)
    {
        Thread t =new Thread(newRunnable() {
 
            publicvoid run() { 
                sample.launchSample(); 
            }      
        });    
        t.start();
    }
}

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

1
publicnative 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
classQML_EXPORT QMLSample : publicQObject
{
    Q_OBJECT
 
public:
    staticQMLSample * GetInstance();
 
private:
    QMLSample ();
 
private:
    QDeclarativeView* mpView;
    JNIEnv* mpEnv;
    staticQMLSample * 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);
 
    intargc = 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
constQMetaObject* metaObj = QMLSample::GetInstance()->metaObject();
intmethodIndex = metaObj->indexOfMethod("FunctionName(int,QString)");
QMetaMethod method = metaObj->method(methodIndex);
boolret = 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