Qt Quick技术的引入,使得你能够快速构建 UI,具有动画、各种绚丽效果的 UI都不在话下。在许多情况下基于QML开发的漂亮的界面想将他设计成组件提供给其他人使用,又不想让别人看到QML源码,另外如果其他人是基于vc环境又如何使用你的QML界面呢?本文介绍如何将基于QT.6 QML开发的模块编译成可以在VS环境中直接使用的DLL库文件,并举例使用QZXing识别二维码。下面是实现的步骤:
一般直接编写的QT动态库是无法被Windows下的VC等调用的。有一个叫做Qtwinmigrate的开源软件解决了这个问题,Qtwinmigrate的下载地址:https://github.com/qtproject/qt-solutions/tree/master/qtwinmigrate
ZXing库是一个用来识别二维码的库,QZXing是一个基于Qt的Qt wrapper library,在本文我们使用它和qml开发一个小应用来实现二维码的解码。
打开qt creator或VS2015,新建C++ 库项目,依次下一步,记得在选择需要的模块的时候选择qml、quick、widgets模块,项目的名字为2DDecodeLib,向导完成后删除自动生成的两个头文件,因为这是QT新建DLL自带的,我们要写的是可以供其他语言调用的DLL,我这里的例子是只用了一个函数。
qt creator 3.60截图
Vs2015截图
选择模块截图
要删除自动生成的两个头文件
将下载的Qtwinmigrate解压,并将common.pri和src目录拷贝到2DDecodeLib项目的文件夹下,在项目的PRO文件中最后加入一行:
include(src/qtwinmigrate.pri)
将下载的QZXing解压,并将解压出来的所有内容拷贝到2DDecodeLib项目的QZXing文件夹下,QZXing最常用是作为一个子项目包含在我们的项目中,所以在我们的项目的pro文件中添加如下的一句:
include(QZXing/QZXing.pri)
由于项目中使用QML来实现界面,我们不希望别人看到QML的源码,因此我们在PRO文件中加入一行:
CONFIG+=qtquickcompiler
编辑main.qml,内容如下:
import QtQuick2.5
import QtQuick.Controls1.3
import QtQuick.Window2.2
import QtQuick.Dialogs1.2
import QZXing2.3
Window{
id:windowvisible:truewidth:300;height:400;Rectangle{id:contentcolor:'lightgray'anchors.fill:parentImage{id:qr_codesource:"qrc:/image/qrcode.png"height:250width:250fillMode:Image.PreserveAspectFitanchors{top:parent.top;topMargin:20;horizontalCenter:parent.horizontalCenter}}
Button{text:qsTr("点击解码")height:30width:70anchors{top:qr_code.bottom;topMargin:12;horizontalCenter:parent.horizontalCenter}onClicked:{decoder.decodeImageQML(qr_code)
}
}
QZXing{id:decoderenabledDecoders:QZXing.DecoderFormat_QR_CODEonDecodingStarted:{console.log("QZXingdecodestart!")}
onDecodingFinished:{if(succeeded){console.log("success")}else{console.log("fail")}
}
onTagFound:{messageDialog.show("QR_CODE:"+tag)}
}
MessageDialog{id:messageDialogtitle: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。本例中的二维码图片如下:
实现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中使用了QT的ui界面前,全局的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等调用了。
为了测试方便,在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下载。