创建及调用基于QT5 QML的 DLL(举例QML中使用QZXing识别二维码)

1.  概述

Qt Quick技术的引入,使得你能够快速构建 UI,具有动画、各种绚丽效果的 UI都不在话下。在许多情况下基于QML开发的漂亮的界面想将他设计成组件提供给其他人使用,又不想让别人看到QML源码,另外如果其他人是基于vc环境又如何使用你的QML界面呢?本文介绍如何将基于QT.6 QML开发的模块编译成可以在VS环境中直接使用的DLL库文件,并举例使用QZXing识别二维码。下面是实现的步骤:

 

2.  下载需要的软件

2.1. 下载Qtwinmigrate

一般直接编写的QT动态库是无法被Windows下的VC等调用的。有一个叫做Qtwinmigrate的开源软件解决了这个问题,Qtwinmigrate的下载地址:https://github.com/qtproject/qt-solutions/tree/master/qtwinmigrate

2.2. 下载QZXing

ZXing库是一个用来识别二维码的库,QZXing是一个基于QtQt wrapper library,在本文我们使用它和qml开发一个小应用来实现二维码的解码。

 

3.  创建DLL

3.1. 创建QT共享库项目

打开qt creator或VS2015,新建C++ 库项目依次下一步,记得在选择需要的模块的时候选择qml、quick、widgets模块,项目的名字为2DDecodeLib,向导完成后删除自动生成的两个头文件,因为这是QT新建DLL自带的,我们要写的是可以供其他语言调用的DLL,我这里的例子是只用了一个函数。

qt creator 3.60截图

Vs2015截图

 

扫描二维码关注公众号,回复: 3791390 查看本文章

选择模块截图

要删除自动生成的两个头文件

3.2.  加入Qtwinmigrate支持

将下载的Qtwinmigrate解压,并将common.prisrc目录拷贝到2DDecodeLib项目的文件夹下,在项目的PRO文件中最后加入一行:

include(src/qtwinmigrate.pri)

3.3.  加入QZXing支持

将下载的QZXing解压,并将解压出来的所有内容拷贝到2DDecodeLib项目的QZXing文件夹下,QZXing最常用是作为一个子项目包含在我们的项目中,所以在我们的项目的pro文件中添加如下的一句:

include(QZXing/QZXing.pri)

由于项目中使用QML来实现界面,我们不希望别人看到QML的源码,因此我们在PRO文件中加入一行:

CONFIG+=qtquickcompiler


3.4. 实现QML二维码解码界面

编辑main.qml,内容如下:

import QtQuick2.5
import QtQuick.Controls1.3
import QtQuick.Window2.2
import QtQuick.Dialogs1.2
import QZXing2.3
 
   
Window{
    id:window
    visible:true
    width:300;
    height:400; 
 
   
    Rectangle{
        id:content
        color:'lightgray'
        anchors.fill:parent
 
   
        Image{
            id:qr_code
            source:"qrc:/image/qrcode.png"
            height:250
            width:250
            fillMode:Image.PreserveAspectFit
            anchors{top:parent.top;topMargin:20;horizontalCenter:parent.horizontalCenter}
 
   
        }
 
   
        Button{
            text:qsTr("点击解码")
            height:30
            width:70
            anchors{top:qr_code.bottom;topMargin:12;horizontalCenter:parent.horizontalCenter}
            onClicked:{
                decoder.decodeImageQML(qr_code)
            }
        }
 
   
        QZXing{
            id:decoder
            enabledDecoders:QZXing.DecoderFormat_QR_CODE
            onDecodingStarted:{
                console.log("QZXingdecodestart!")
            }
 
   
            onDecodingFinished:{
                if(succeeded){
                    console.log("success")
                }else{
                    console.log("fail")
                }
            }
 
   
            onTagFound:{
                messageDialog.show("QR_CODE:"+tag)
            }
 
   
        }
 
   
        MessageDialog{
            id:messageDialog
            title:qsTr("恭喜你,解码成功")
 
   
            functionshow(caption){
                messageDialog.text=caption;
                messageDialog.open();
            }
        }
    }
}

由于QZXing在QT5以上版本有问题,需要修改QZXing文件夹下的imagehandler.cpp文件中的ImageHandler::extractQImage函数,修改如下:

QImage ImageHandler::extractQImage(QObject *imageObj,
                                   const double offsetX, const double offsetY,
                                   const double width, const double height)
{
#if QT_VERSION >= 0x050000
    QQuickItem *item = qobject_cast<QQuickItem*>(imageObj);
#else
    QGraphicsObject *item = qobject_cast<QGraphicsObject*>(imageObj);
#endif
    if (!item) {
        qDebug() << "Item is NULL";
        return QImage();
    }
#if QT_VERSION >= 0x050000
    QQuickWindow *window = item->window();
    QImage img = window->grabWindow();
#else
    QImage img(item->boundingRect().size().toSize(), QImage::Format_RGB32);
    img.fill(QColor(255, 255, 255).rgb());
    QPainter painter(&img);
    QStyleOptionGraphicsItem styleOption;
    item->paint(&painter, &styleOption);
#endif

    if(offsetX == 0 && offsetY == 0 && width == 0 && height == 0)
        return img;
    else
    {
        return img.copy(offsetX, offsetY, width, height);
    }
}</span>

 
   

创建工程的资源文件qml.qrc,并将main.qml加入到资源文件中。另外为了演示方便程序中将只对指定的二维码文件解码,因此在工程目录下新建image文件夹,并在文件夹中存放一张二维码图片,并命名为qrcode.png。本例中的二维码图片如下:

3.5. 实现DLL库的入口文件

实现DLL动态库的入口文件,内容如下:

 
   
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <qmfcapp.h>
#include <windows.h>
#include <QtQuick/QQuickView>
#include "QZXing.h"
 
BOOLWINAPIDllMain(HINSTANCEhInstance,DWORDdwReason,LPVOID/*lpvReserved*/)
{
    staticboolownApplication=FALSE;
 
    if(dwReason==DLL_PROCESS_ATTACH)
    ownApplication=QMfcApp::pluginInstance(hInstance);
    if(dwReason==DLL_PROCESS_DETACH&&ownApplication)
    deleteqApp;
 
    returnTRUE;
}
 
//定义C语言类型导出函数
extern"C"__declspec(dllexport)voiddisplayQml()
{
 
    QApplication*app=(QApplication*)QApplication::instance();
 
    QZXing::registerQMLTypes();
    QQmlApplicationEngineengine(app);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
 
    app->exec();
//    deleteapp;
    app->quit();
 
}
 
  

因为QT必须有调用QApplication的exec方法,这样才能产生消息循环,QT的程序才可以运行。所以说如果我们使用了QT编写了dll程序,在普通的 windows程序中是不能调用的。在调用的时候会出现错误。当然QT提供了解决方法:那就是QTWinmigrate大家都知道DllMain函数是windows动态库的入口函数,如果在dll中使用了QTui界面前,全局的QApplication必须首先要创建,并且应用程序必须创建EventLoop。进入到QmfcApp::pluginInstance方法中去:

 
  
 
   
bool QMfcApp::pluginInstance(Qt::HANDLE plugin)
{
    if (qApp)
	return FALSE;

    QT_WA({
	hhook = SetWindowsHookExW(WH_GETMESSAGE, QtFilterProc, 0, GetCurrentThreadId());
    }, {
	hhook = SetWindowsHookExA(WH_GETMESSAGE, QtFilterProc, 0, GetCurrentThreadId());
    });

    int argc = 0;
    (void)new QApplication(argc, 0);

    if (plugin) {
	char filename[256];
	if (GetModuleFileNameA((HINSTANCE)plugin, filename, 255))
	    LoadLibraryA(filename);
    }

    return TRUE;
}




我们可以看到:Qapplication被创建了出来。QmfcApp::pluginInstanc是为了保证进程中存在一个Qapplication对象,并且dll要把这个Qapplication的实例加载到内存中。

下面是dll中的导出函数:

 

//定义C语言类型导出函数
extern "C" __declspec(dllexport) void displayQml( )
{

    QApplication* app = (QApplication *) QApplication::instance();

    QZXing::registerQMLTypes();
    QQmlApplicationEngine engine(app);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    app->exec();
//    delete app;
    app->quit();

}


dll中的导出函数要用extern“C”形式,在函数中首先获取单例的Qapplication实例然后调用实现界面显示的QML在进入QT的事件循环,最后在退出事件循环,返回到调用界面另外QML中使用QZXing,需要在这个函数中注册一下

最后选择release编译,在release目录下生成了DLL文件,这个文件就可以供VC/C#/VB/JAVA等调用了。

4.  编写VC程序测试

为了测试方便,在vs2015中新建一个Win32测试项目qmlLibTest,在想到中选择windows应用程序

在生成的qmlLibTest.cpp文件中找到处理主窗口消息的回调函数LRESULTCALLBACK WndProc(HWNDhWnd,UINTmessage,WPARAMwParam,LPARAMlParam), 在左键点击事件中加入调用DLL动态库中显示QML设计的二维码解码界面,代码如下:

//
//  函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的:    处理主窗口的消息。
//
//  WM_COMMAND  - 处理应用程序菜单
//  WM_PAINT    - 绘制主窗口
//  WM_DESTROY  - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
	case WM_LBUTTONDOWN:
		{
			typedef void(*FUNA)( );//定义指向和DLL中相同的函数原型指针

			HMODULE hDLL = LoadLibrary(L"2DDecodeLib.dll");;
			if (hDLL != NULL)
			{
				FUNA displayQml = (FUNA) GetProcAddress(hDLL, "displayQml");//获取导入到应用程序中的函数指针,根据方法名取得
				if (displayQml != NULL)
				{
					displayQml( );
				}
				else
				{
					cout << "Cannot Find Function : " << "displayQml" << endl;
				}
				FreeLibrary(hDLL);
			}
			else
			{
				cout << "Cannot Find dll : " << "qmllib.dll" << endl;
			}

		}

		break;
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 分析菜单选择: 
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 在此处添加使用 hdc 的任何绘图代码...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}</span>


 
   


然后将刚才生成的DLL文件拷贝到项目的文件夹下,同时将支持QML及QTquick运行的DLL拷贝到生成的运行文件目录,例如项目设置为release模式,则拷贝到qmlLibTest\x64\Release目录下,文件清单如下图:

最后编译运行,效果如下:

本文中用到的例子及程序清单可以从http://download.csdn.net/detail/liuyez123/9429926下载。

猜你喜欢

转载自blog.csdn.net/liuyez123/article/details/50651423
QML
今日推荐