天天看点

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

上篇文章实现了Qt+MinGW+Opencv+Zbar的配置。接下来,由于项目需求,需要用Java调用,因此需要将之前二维码识别的代码编译成Dll,供java调用。

1 Java调用Dll的方法

1.1 利用Java自带的JNI

JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。它允许Java代码和其他语言写的(本地已编译的)代码进行交,这样做通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。

基本流程是首先在java环境下,建立一个java的接口,然后利用Java自带的工具javah,将这个接口转换成C或C++类型的接口,然后,在VC或中Qt的环境下借助编译器,实现这个接口的功能,并编译成Dll。 在Java环境下通过调用这个Dll,就可以访问本地代码或已编译的库的功能,这个方法的效率是最高的,缺点是由于对应于某一平台的 JNI 本地代码调用通常不能移植到其他平台上。

这种方法适用于核心代码大部分已经在本地完成,将代码重新写成Java的工作量复杂或者根本无法完成的情况,我们需要在本地用C++或C把这些代码封装起来供Java 使用,这个封装的Dll可以由我们指定实现某种功能,也就是说可以在保证Java代码不更改的情况下,改变程序的功能。

1.2 利用Java自带的JNA

JNA(Java Native Access )是建立在JNI技术基础之上的一个Java类库,它使您可以方便地使用java直接访问动态链接库中的函数,整个过程均在Java环境下完成。

基本流程是在一个java接口中描述本地库(native library)中的函数与结构,JNA将自动实现Java接口到native function的映射,避免了像JNI一样手工用C或C++写一个动态链接库,也许这也意味着,使用JNA技术比使用JNI技术调用动态链接库会有些微的性能损失。范例代码:

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

这种方法适用于核心代码大部分已经用Java完成,为了直接调用已经存在的Dll的功能,我们只需要查看Dll的函数结构,利用JNA提供的Libaray接口建立一个映射,供Java 使用,但是我们无法改变Dll已经实现的代码,只能调用,因此,如果需要改变程序功能,我们必须更改Java代码。同时,向Java提供数量较少的接口时更适合这种方法。

1.3 利用第三方包JNative

JNative 与JNA有些类似,可以让你方便的访问 Windows 平台下的 DLL 以及 Linux 平台下的 so 动态连接库文件,无需在编写一行 C/C++ 代码。

基本流程是直接使用JNative载入一个Dll,返回一个静态指针,这个指针可以看作是Dll的入口,然后利用这个JNative指针,设置参数并调用Dll中的函数实现某种功能,范例代码:

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

与JNA类似,这种方法适用于核心代码大部分已经在Java完成,为了直接调用已经存在的Dll的功能,我们只需要查看Dll的函数结构,利用JNative载入Dll,供Java 使用,但是我们无法改变Dll已经实现的代码,只能调用,因此,如果需要改变程序功能,我们必须更改Java代码。

2 利用Java JNI声明接口

通过以上学习,我们选择采用第一种情况,原因是为了实现二维码扫描,需要借助OpenCV的库文件读取图片,以及Zbar的库文件进行扫描,我们已经在Qt环境下实现,而且对这段代码已经很熟悉(输入图片地址,输出二维码内容),另外,我们只需要像Java提供一个接口供其使用。如果借助JNA或者JNative,需要重新利用Java调用Opencv和Zbar的库文件重写整个流程,因此,对我们而言,利用JNI为Opencv和Zbar的库文件整体打包一个Dll供Java使用的代码量是最小的,同时一个优点是Java代码不改动的情况下,依然可以更新DLl提供更多的功能

2.1 安装32位JDK

由于我们在QT下调试的OpenCv和Zbar库都是32位的,因此,需要安装32位的JDK建立一个32位的Dll去调用。原本MyEclipse自带的64位JDK可以不用管,直接安装jdk-6u45-windows-i586.exe 这是一个32位的JDK,安装好以后,环境变量中,进行设置,注意此时需要删除之前的64位JDK的环境变量。

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

2.2 在MyEclipse下添加JRE环境

MyEclipse->Windows->Preferences->Java->Installed 选项卡:

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

点击Add添加Jre运行环境,选择Standard VM

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

然后填写JRE位置:

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

2.3 利用JNI建立一个本地接口

MyEclipse新建一个空的Java Application项目,新建一个JNI4Zbar类文件,如下所示:

public class JNI4Zbar {

    {

        System.load("E:\\QTWork\\JNI4Zbar\\JNI4Zbar\\JNI4Zbar.dll");

    }

    public native String scanimage(String name);

public static void main(String[] args) {

         String strContent="";

         //建立一个JNI4Zbar类载入dll,并调用scaniamge函数

         strContent = new JNI4Zbar().scanimage("E:\\myqrcode.jpg ");

//输出扫码结果

         System.out.("java QR_Data : "+strContent);

    }

}

此时,注意配置好项目的Build Path,在项目上点击右键->Build Path->Configure Build Path-> Library 选项卡

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

选择 JRE System Library 

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

确保选择的是32 位 JRE 1.6 

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

同时注意查看,Java Compiler选项卡,保证是1.6版本

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

在命令行下编译

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

最终会得到:

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

这个JNI4Zbar.h 内容如下:

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

这是我们利用Javah生成的.h文件,只需要知道这个头文件通过 JNIEXPORT声明了一个接口,通过JNICALL 声明了一个调用的函数,注意这个代码不需要修改! 

至此,Java的部分就已经完成,我们已经转换到C或(C++)语言平台上了,接下来将利用QT 实现.h文件声明的函数。

3 利用Qt编写接口实现

在Qt Creator中建立一个Qt Console Application项目,名称为 JNI4Zbar,然后把项目中的main.cpp 更名为 JNI4Zbar.cpp, 同时将JNI4Zbar.h文件,Jni.h 和 Jni_md.h 靠拷贝到 项目的源码中:

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

更改JNI4Zbar.cpp内容为

#include "JNI4Zbar.h"

#include <windows.h>

#include <iostream>

#include <jni.h>

#include <zbar.h>

#include <opencv2/core/core.hpp>

#include <opencv2/highgui/highgui.hpp>

#include <opencv2/imgproc/imgproc.hpp>

//#define STR(s) #s

using namespace std;

using namespace zbar;

using namespace cv;

JNIEXPORT jstring JNICALL Java_JNI4Zbar_scanimage(JNIEnv *env, jobject jobj, jstring jstr){

    //获取传进来的文件路径,将jstring转为const char *

    const char *str = env->GetStringUTFChars(jstr, 0);

    //将const char *转为string

    string fullpathfilename = str;

    //printf("%s", str);

    //env->ReleaseStringUTFChars(jstr, str);

    if (str == NULL) {

        return env->NewStringUTF("");

    }

    //建立一个zbar中的扫描器

    ImageScanner scanner;

    scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, 1);

    //读取并显示图像

    Mat image = imread(fullpathfilename);

    namedWindow("Source Image");

    imshow("Source Image", image);

    //读取图像, 参数0, 直接将图片读取为灰度

    Mat imageGray = imread(fullpathfilename,0);

    namedWindow("Gray Image");

    imshow("Gray Image",imageGray);

    waitKey();

    //为Zbar建立一个图像码对象

    int width = imageGray.cols;

    int height = imageGray.rows;

    uchar *raw = (uchar *)imageGray.data;

    Image imageZbar(width, height, "Y800", raw, width * height);

    //识别图像码

    scanner.scan(imageZbar); //扫描条码

    Image::SymbolIterator symbol = imageZbar.symbol_begin();

    //输出识别信息

    if(imageZbar.symbol_begin()==imageZbar.symbol_end())

    {

        cout<<"Bar code query failed, please check the picture!"<<endl;

    }

    for(;symbol != imageZbar.symbol_end();++symbol)

    {

        cout<<"type:"<<endl<<symbol->get_type_name()<<endl<<endl;

        cout<<"bar:"<<endl<<symbol->get_data()<<endl<<endl;

    }

    //获取图像码中的信息

    string information = symbol->get_data();

    //将string 转换为 const char *

    const char * return_information = information.c_str();

    //清空图像码对象

    imageZbar.set_data(NULL,0);

    //返回获取的字符串

    return env->NewStringUTF(return_information);

}

配置.pro 文件

QT -= gui

CONFIG += c++11 console

CONFIG -= app_bundle

CONFIG += shared

# The following define makes your compiler emit warnings if you use

# any feature of Qt which as been marked deprecated (the exact warnings

# depend on your compiler). Please consult the documentation of the

# deprecated API in order to know how to port your code away from it.

DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.

# In order to do so, uncomment the following line.

# You can also select to disable deprecated APIs only up to a certain version of Qt.

#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \

    JNI4Zbar.cpp

HEADERS += \

    JNI4Zbar.h \

    jni_md.h \

    jni.h

#####################  opencv

INCLUDEPATH += C:/opencv/build32/include

DEPENDPATH += C:/opencv/build32/include

# debug  header

INCLUDEPATH += C:/opencv/build32d/include

DEPENDPATH += C:/opencv/build32d/include

# dll.a lib

win32:CONFIG(release, debug|release): LIBS += C:\opencv\build32\x86\mingw\bin\libopencv_*.dll

else:win32:CONFIG(debug, debug|release): LIBS += C:\opencv\build32d\x86\mingw\bin\libopencv_*d.dll

#####################  zbar

win32: LIBS += 'C:\Program Files (x86)\ZBar\bin\libzbar-0.dll'

INCLUDEPATH += 'C:\Program Files (x86)\ZBar\include'

DEPENDPATH += 'C:\Program Files (x86)\ZBar\include'

#####################  jni

INCLUDEPATH += C:\Program Files (x86)\Java\jdk1.6.0_45\include

DEPENDPATH += C:\Program Files (x86)\Java\jdk1.6.0_45\include

#####################  jni_md.h

INCLUDEPATH += C:\Program Files (x86)\Java\jdk1.6.0_45\include\win32

DEPENDPATH += C:\Program Files (x86)\Java\jdk1.6.0_45\include\win32

然而编译会出现错误:

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

我找了很多资料,一直没有解决这个'[email protected]'的问题。幸运的是我查到,可以避免利用qmake,而是直接利用g++,来编译这个文件

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

在JNI4Zbar.cpp的目录下,执行命令:

g++ -Wl,--kill-at -I"C:\Program Files (x86)\Java\jdk1.6.0_45\include" -I"C:\Program Files (x86)\Java\jdk1.6.0_45\include\win32" -I"C:\Program Files (x86)\ZBar\include" -I"C:\opencv\build32\include" -L"C:\Program Files (x86)\ZBar\bin" -l"libzbar-0" -L"C:\opencv\build32\x86\mingw\bin" -l"libopencv_calib3d310" -l"libopencv_core310" -l"libopencv_features2d310" -l"libopencv_flann310" -l"libopencv_highgui310" -l"libopencv_imgcodecs310" -l"libopencv_imgproc310" -l"libopencv_ml310" -l"libopencv_objdetect310" -l"libopencv_photo310" -l"libopencv_shape310" -l"libopencv_stitching310" -l"libopencv_superres310" -l"libopencv_video310" -l"libopencv_videoio310" -l"libopencv_videostab310" -l"opencv_ffmpeg310" -shared -o JNI4Zbar.dll JNI4Zbar.cpp

其中参数 -Wl,--kill-at 是让函数名称不带@符号 -I 是包含的include目录,-L是需要的动态链接库的目录 -l后面紧跟的是需要的动态链接库的名称(注意不包含.dll) -shared 表示创建的是动态链接库 -o 后面是输出的dll的名称,最后的参数 JNI4Zbar.cpp就是编译的cpp文件,编译完后会生成一个JNI4Zbar.dll文件。

需要强调的是,这里的zbar用的是0.1.0版本exe安装包zbar-0.10-setup.exe,自带32位libzbar-0.dll。OpenCV32位的dll库上一篇文章中,qt+MinGW编译的,这里应该需要确认环境变量是否配置正确:

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

我这里用的是笨方法,把每一个用到的头文件和库文件全都在命令行里面,应该有简单的写法吧,实际上qt的qmake应该就是通过.pro文件中的配置来编译的,但是不知道为什么通不过,还好这种方法能编译成功。如果你是编译c文件,需要用gcc 而不是 g++。

4 在Java环境下测试

重新回到Java环境中,运行JNI4Zbar

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

运行时会出现:

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

按任意按键,则关闭二维码窗口,并继续运行,结果如下:

Qt_JNI_DLL_配置手册 1 Java调用Dll的方法2 利用Java JNI声明接口3 利用Qt编写接口实现 4 在Java环境下测试

由此,我们实现了在利用JNI和g++编译dll,这样java程序中可以调用zbar实现二维码识别了

继续阅读