天天看点

用qmake搭建框架之加载静态、共享库一、建立大型工程二、调用生成的库文件三、运行时加载共享库关于插件关于打包

目录

一、建立大型工程

1.1 建立静态库

1.2 建立共享库

二、调用生成的库文件

三、运行时加载共享库

关于插件

关于打包

关于qmake的语法可以参考:http://doc.qt.io/qt-5/qmake-manual.html。这里是qt5的,qt4和qt5的写法还略有区别。

一般来说,我们使用qt creator建立的工程都是以.pro结尾,实际上这里涉及到的就是qmake语法。

除了.pro文件,还有.pri,.prf,.prl文件。

  • pro文件就不多说了。
  • pri中的i是包含(include)的首字母。类似于C、C++中的头文件吧,反正就是我们可以吧 *.pro 文件内的一部分单独放到一个 *.pri 文件内,然后包含进来。
  • prf的f是特性(feature)的首字符,和pri文件类似,该文件也是要被包含进pro文件的,如CONFIG += QT。
  • prl的ls 链接(link)的首字符。主要和生成与使用静态库密切相关(动态库也可以有该文件,去Qt安装目录下的lib目录下看看即可)。

具体可参见博文《浅谈 qmake 之 pro、pri、prf、prl文件》。

因此,对于建立一个大型工程,qmake就显得格外重要。

本文参考博文:《Qt 之创建并使用共享库》、《Qt 之在运行时加载共享库》和《Qt 之创建并使用静态链接库》。

一、建立大型工程

用qmake搭建框架之加载静态、共享库一、建立大型工程二、调用生成的库文件三、运行时加载共享库关于插件关于打包

首先选择新建一个工程,点击其他项目,选择子目录项目,然后选择路径并命名。

这时生成的.pro文件应该如下:

TEMPLATE = subdirs
CONFIG += ordered
           

然后再在该工程下新建不同的子项目,可以是可执行程序,可以是库文件。直接右键新建“新子项目”。

用qmake搭建框架之加载静态、共享库一、建立大型工程二、调用生成的库文件三、运行时加载共享库关于插件关于打包

比如这里新建一个库项目,选择后进入下一个步骤:

用qmake搭建框架之加载静态、共享库一、建立大型工程二、调用生成的库文件三、运行时加载共享库关于插件关于打包

这里可以选择建立的是共享库,静态库还是Qt插件(Qt Plugin)。

1.1 建立静态库

选择建立静态库,此时的子项目.pro文件应包含如下:

TARGET = staticLib
TEMPLATE = lib
CONFIG += staticlib
           

表示会生成一个名为staticLib的静态库。

再在下面加入几行:

CONFIG += debug_and_release
CONFIG(debug, debug|release): TARGET = $$join(TARGET,,,d)
DESTDIR = ../../externlib/
           

表示生成的库文件会放置在externlib下,且如果是debug编译,生成的后缀中会加d。

对于.h 和 .cpp文件,就可以自由的写各种函数了。

1.2 建立共享库

建立共享库和建立的静态库的过程一样,只是选择不同,此时的.pro文件应该如下:

TARGET = sharedLib
TEMPLATE = lib
DEFINES += SHAREDLIB_LIBRARY
           

表示会生成一个名为sharedLib的共享库。

由于共享库需要与可执行程序放置在同一个目录下,因此还需添加以下几行:

CONFIG += debug_and_release
CONFIG(debug, debug|release): TARGET = $$join(TARGET,,,d)
CONFIG(debug, release|debug): DESTDIR = ../../debug/
CONFIG(release, release|debug): DESTDIR = ../../release/
           

同样,这里将debug版本下的编译后缀中加入了d。

对于.h和.cpp文件,便可以自由的写函数接口了。

除了一般C++类的两个文件, Qt Creator还会帮我们创建一个名为{projectName}_global.h的头文件,以确保正确的宏能够被调用。

此外,如果是C语言的写法,还需要在导出的头文件中加入extern "C"。

二、调用生成的库文件

首先新建一个子项目,选择Application用于生成可执行文件。

同样对于生成的最终文件,我们将其指定到特点目录,一般来说,软件会有debug版和release版用于区分,同样加入以下代码。

CONFIG(debug, release|debug): DESTDIR = ../../debug/
CONFIG(release, release|debug): DESTDIR = ../../release/
           

这么书写是一个好习惯,将debug生成的rel生成的有效分隔开。

用qmake搭建框架之加载静态、共享库一、建立大型工程二、调用生成的库文件三、运行时加载共享库关于插件关于打包

然后在子项目中,右键添加库。由于之前建立的静态库共享库工程都在同一个工程下,所以选择内部库即可。

用qmake搭建框架之加载静态、共享库一、建立大型工程二、调用生成的库文件三、运行时加载共享库关于插件关于打包

所要选择的库名都能被自动识别出来,平台可以都勾选上,同时为debug版本添加‘d’作为后缀。

添加后,会在.pro文件中自动增加以下几行:

win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../externlib/ -lstaticLib
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../externlib/ -lstaticLibd
else:unix: LIBS += -L$$OUT_PWD/../../externlib/ -lstaticLib

INCLUDEPATH += $$PWD/../staticLib
DEPENDPATH += $$PWD/../staticLib

win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../externlib/libstaticLib.a
else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../externlib/libstaticLibd.a
else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../externlib/staticLib.lib
else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../externlib/staticLibd.lib
else:unix: PRE_TARGETDEPS += $$OUT_PWD/../../externlib/libstaticLib.a
           

看起来很复杂其实类似于VS工程的添加依赖项,包括头文件所在目录。注意需把静态库文件指定到externlib文件夹下,与前面构建时的对应。

同样,添加共享库的过程也是如此,添加后在.pro文件中会增加如下:

win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../release/ -lsharedLib
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../debug/ -lsharedLibd
else:unix: LIBS += -L$$OUT_PWD/../sharedLib/ -lsharedLib

INCLUDEPATH += $$PWD/../sharedLib
DEPENDPATH += $$PWD/../sharedLib
           

基本与添加静态库一样。

关于静态库和共享库,编译时都需要.lib文件,编译后,共享库的dll或so文件一定要放置于可执行文件可找到的位置,否则运行时会提示缺少该依赖项,而静态库则是编译时需要。具体优缺点如下:

  • 在创建共享库时,需要将其部署到应用程序中,与共享库相关联的应用程序和库都很小。
  • 静态链接会生成一个独立的可执行文件,这样做的好处是只需要部署几个文件,缺点是可执行文件会变得很大。

经过以上部署,我们便可调用静态库或者共享库中的函数接口了。

三、运行时加载共享库

前面一、二都是说的如何在一个完整的工程中部署,同时依赖库文件。而实际上,共享库文件另一个优势就是在程序启动在选择加载还是不加载,也就说我们常说的插件模式。

Qt中提示了QLibrary库或者QPluginLoader两种方式来加载共享库。

如果使用QLibrary类,一般会用到resolve函数,详见《Qt 之在运行时加载共享库》。

如果用QtPlugin,则需添加以下几行:

#define PluginID   "org.qt-project.Qt.Examples.plugin.IPluginUi"
Q_DECLARE_INTERFACE(IPluginUi, PluginID)

Q_PLUGIN_METADATA(IID PluginID FILE "pluginForm.json")
Q_INTERFACES(IPluginUi)
           

其中json文件是为了描述插件的一些属性。

QtPlugin具体调用方法的关键代码如下:

QStringList filters;
filters << "*.dll" << "*.so";      //在插件所在的文件夹下筛选后缀为dll或so结尾的文件
pluginDir.setNameFilters(filters);
foreach (QString filename, pluginDir.entryList(QDir::Files))
{
	QPluginLoader *pluginload = new QPluginLoader(pluginDir.absoluteFilePath(filename));
	pluginload->setLoadHints(QLibrary::ExportExternalSymbolsHint | QLibrary::ResolveAllSymbolsHint);
	if(pluginload->load())
	{
		QObject *obj = pluginload->instance();
		if(obj)
		{
			IPluginUi *plugin = qobject_cast<IPluginUi*>(obj);
			if(plugin)
			{
				QWidget *widget = plugin->PluginUi();
				QString str_sn = plugin->getSerialNumber();

				QString class_name = pluginload->metaData().value("className").toString();
				ui->textEdit->append("export class: " + class_name);
				QJsonObject json = pluginload->metaData().value("MetaData").toObject();
				QString str_author = json.value("author").toString();
				QString str_date = json.value("date").toString();
				QString str_license = json.value("license").toString();
				QString str_version = json.value("version").toString();
				QJsonArray array = json.value("changelog").toArray();
			}
		}
	}
}
           

自此,插件便载入完成了。

给个演示的demo效果如下:

用qmake搭建框架之加载静态、共享库一、建立大型工程二、调用生成的库文件三、运行时加载共享库关于插件关于打包

在Tab2中会显示所载入的插件界面。

工程的完整代码可以在https://github.com/WelinLee/qt_pattern中找到,在windows和linux平台下均可编译通过。

关于插件

通过以上,我们便可以设计一个小型的插件程序了。设计这种模块化的程序,还是要考虑以下几点:

  • 如何注册插件;
  • 如何调用插件;
  • 如何测试插件 ;
  • 插件的生命周期管理;
  • 为插件提供名称、版本、状态等信息,并可以获取插件列表,记录插件的处理日志等;
  • 插件的出错处理。

详见《深入理解插件系统》、《构建自己的 Qt 插件系统》。

关于打包

对于生成的软件,如果是windows平台可以使用windeployqt.exe进行打包。

在linux下可以使用ldd指令(deplist=$(ldd $exe | awk '{if (match($3,"/")){printf("%s ",$3)}}'))找到程序所需的依赖项,然后将其打包。

继续阅读