說明
之前討論的DLL的靜態鏈接和動態連接都是基於 MSVC 編譯器,但是 MinGW 似乎有另外一套類似但是不相同的機制。下文均在 windows 下使用 Qt Creator 中使用 MinGW 進行說明。
我們在新建庫項目的時候有三種選項,如圖所示:
三種類型分別是:共享庫、靜態鏈接庫和Qt插件,之間區別以及和 MSVC 的庫區別如下:
- 項目會根據類型不同生成 .dll 和 .a文件,這里的 .a 即類似 .lib,但是又不完全相同;
- 共享庫 類似 msvc 的靜態鏈接,構建最終生成 .a(類似.lib) 和 .dll,客戶端編譯時需要頭文件和 .a,運行時需要 .dll;
- 靜態鏈接庫 則是另外一種新的機制,構建最終只生成 .a 文件,客戶端調用時需要 .a 文件,運行時則不需要任何庫文件,類似於客戶端在編譯時將庫包進了自己的exe中;
- Qt插件不再多說,參考Qt插件系統文章,就是一種動態加載DLL的方法,但是又把加載的細節隱藏了;
綜上所屬,MingW 下的鏈接庫相比於 MSVC,着實簡單了很多,把內部的很多復雜的細節隱藏在Qt的內部系統中,對使用者來說,更加方便。
鏈接庫的使用
下面就來簡單介紹一下這些庫的使用,Qt的插件不在討論之列,請參考Qt插件系統文章。介紹中會忽略一些不重要的細節,我們認為你應該對C++和Qt有一個較深的認識,若沒有,請自行去學習相關知識。同時,我們順帶簡單介紹一些 .pro 文件的配置。
共享庫
(1)創建共享庫
共享庫類似靜態鏈接,生成.a文件和.dll文件。
按照正常流程新建庫項目,類型選擇 動態庫,最終生成的項目列表中如下如所示:
可以看出,除了需要導出的類外,額外還有一個XXX_global.h的文件,文件代碼如下:
#ifndef FIRECONTROL_GLOBAL_H
#define FIRECONTROL_GLOBAL_H
#include <QtCore/qglobal.h>
#if defined(FIRECONTROL_LIBRARY)
# define FIRECONTROLSHARED_EXPORT Q_DECL_EXPORT
#else
# define FIRECONTROLSHARED_EXPORT Q_DECL_IMPORT
#endif
#endif // FIRECONTROL_GLOBAL_H
了解 MSVC 的dll導出的都能了解,這里其實是對 __declspec(dllexport) 和 __declspec(dllimport) 進行一種巧妙的聲明,因為導出需要使用export,導入需要使用import,為了避免在庫和客戶端中重復地進行export和import的聲明,這里使用宏定義進行統一聲明,詳細說明可以參考DLL的導出和調用的文章。
在需要導出的類和方法前面使用 FIRECONTROLSHARED_EXPORT 聲明即可,客戶端調用時也需要將這個文件include進來。
當然,如果你對導出DLL的一個比較清晰的認識,也可以刪除這個文件,自己定義。
(2)pro文件
QT -= gui
TARGET = FireControl
TEMPLATE = lib #表示這是一個庫項目
DEFINES += FIRECONTROL_LIBRARY
SOURCES += FireControlManager.cpp
HEADERS += FireControlManager.h\
firecontrol_global.h
unix {
target.path = /usr/lib
INSTALLS += target
}
#上面為創建時自動生成,以下為新增
DESTDIR = $$PWD/../SystemWindow/lib #最終編譯文件的生成路徑,包括.a和.dll
DLLDESTDIR = $$PWD/../../../Service #dll文件的生成路徑
(3)調用共享庫
在調用端右擊->添加庫,選擇生成 .a 文件,即可自動在 pro文件中添加加載設置。當然,也可以不適用GUI操作,直接修改pro文件以達到添加庫的目的。
我們希望調用端和庫項目分開構建,所以這里選擇外部庫
添加.a的庫文件和頭文件的包含目錄,並配置一些鏈接方法
調用設置完成后,pro文件如下:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = SystemWindow
TEMPLATE = app
SOURCES += main.cpp\
MainBench.cpp
HEADERS += MainBench.h
FORMS += MainBench.ui
#以下即為自動生成的加載庫設置
win32:CONFIG(release, debug|release): LIBS += -L$$PWD/lib/ -lFireControl
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/lib/ -lFireControl
else:unix:!macx: LIBS += -L$$PWD/lib/ -lFireControl
INCLUDEPATH += $$PWD/../FireControl
DEPENDPATH += $$PWD/../FireControl
DESTDIR = $$PWD/../../../Service #目標生成路徑
最后,可以在客戶端使用庫導出的類了,當然不要忘了添加頭文件。
靜態庫
(1)創建靜態庫
靜態最終只會生成 .a 文件。
創建庫項目時,選擇靜態庫,完成之后,項目目錄的內容和目錄中,除了沒有入口的main函數外,其他都和普通項目沒有什么區別,而真正的區別在於pro文件的配置中。
(2)pro文件
QT -= gui
TARGET = untitled
TEMPLATE = lib #表示這是一個庫項目
CONFIG += staticlib #表示這是一個靜態庫
SOURCES += untitled.cpp
HEADERS += untitled.h
unix {
target.path = /usr/lib
INSTALLS += target
}
DESTDIR = $$PWD/../SystemWindow/lib #生成路徑
(3)調用靜態庫
調用方法和調用共享庫類似,這里不做贅述,最終實際運行時不需要dll文件
總結
共享庫和靜態庫各有各的優勢,從最簡單的層面上來看:
使用靜態庫可以將大項目區分開,即分模塊顯示項目,便於維護;
使用共項目也是如此,而且可以在不修改客戶端的情況下,修改庫內容的實現,只要接口不變,最終替換dll即可完成內容的改變。
但是看起來靜態庫有些雞肋,具體還有哪些好處,留待日后發現。