Qt_JNI_DLL_配置手册

独自空忆成欢 提交于 2020-03-09 13:56:40

上篇文章实现了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技术调用动态链接库会有些微的性能损失。范例代码:


这种方法适用于核心代码大部分已经用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中的函数实现某种功能,范例代码:


与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的环境变量。
 

2.2 在MyEclipse下添加JRE环境

 

MyEclipse->Windows->Preferences->Java->Installed 选项卡:
 
点击Add添加Jre运行环境,选择Standard VM
 

然后填写JRE位置:

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


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

public class JNI4Zbar {
    {
        /**
         * 系统加载的Dll的位置,此时JNI4Zbar.dll并不存在,我们将在后面展示如何编译出这个Dll
         */
        System.load("E:\\QTWork\\JNI4Zbar\\JNI4Zbar\\JNI4Zbar.dll");
    }

    /**
     * 就这个native关键字.标记了java中利用JNI4Zbar类调用的功能
* 读取一个image文件(完整路径名),输出扫码结果
     */
    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 选项卡
 
选择 JRE System Library 
 
确保选择的是32 位 JRE 1.6 


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

在命令行下编译


 
最终会得到:


 
这个JNI4Zbar.h 内容如下:

这是我们利用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 靠拷贝到 项目的源码中:


 
更改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

然而编译会出现错误:


 
我找了很多资料,一直没有解决这个'WinMain@16'的问题。幸运的是我查到,可以避免利用qmake,而是直接利用g++,来编译这个文件
 
在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的qmake应该就是通过.pro文件中的配置来编译的,但是不知道为什么通不过,还好这种方法能编译成功。如果你是编译c文件,需要用gcc 而不是 g++。


4 在Java环境下测试


重新回到Java环境中,运行JNI4Zbar
 
运行时会出现:
 
按任意按键,则关闭二维码窗口,并继续运行,结果如下:

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


 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!