QT跨目錄編譯及多工程集成

[+]

採用一個非常簡單的Qt程序作爲例子,通過pro文件的合理編寫,使得我們的程序在使用動態庫的時候,幾乎可以忽略掉動態庫的存在。它包括3部分

  • 生成動態庫
  • 使用動態庫
  • 生成與使用的自動化

測試環境:

  • ubuntu 11.04 + Qt 4.7.2
  • windows vista + Qt 4.7.0(MSVC2008)
  • windows vista + Qt 4.6.3(MinGW)

例子的源碼:http://code.google.com/p/h-qt-exercise/downloads/detail?name=QtAppWithDll.zip&can=2&q= (你可以先看代碼,再決定是不是繼續向下看)

引子

一個非常非常簡單的Qt的小程序,是吧?

  • widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QtGui/QWidget>
class Widget:public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget * parent=0);
};
#endif // WIDGET_H
  • widget.cpp (本文件內容不變)
#include "widget.h"

Widget::Widget(QWidget *parent)
    :QWidget(parent)
{
}
  • main.cpp (本文件內容不變)
#include <QtGui/QApplication>
#include "widget.h"
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    Widget w;
    w.show();
    app.exec();
}

這個程序是如此的簡單,我們都能很輕易地寫出需要的pro文件

HEADERS += widget.h
SOURCES += main.cpp widget.cpp

然後qmake,make即可得到結果。

可是,你想過麼:如果不想讓我們的程序鐵板一塊,分成幾個動態庫(共享庫)會怎麼樣呢,pro文件又該如何寫?

如何做?(一)源碼分開放置

既然要準備用動態庫了,庫的源碼和程序的源碼還是分開放置吧?

  • 將源文件放到不同的路徑下
    • src/main.cpp
    • libwidget/widget.h
    • libwidget/widget.cpp

我們知道qmake不如cmake那麼強大,它的每個project只能有一個目標,要麼是庫,要麼是可執行程序。當目標多於一個時,只能用 subdirs 這個TEMPLATE,於是,

  • 我們需要3個xxx.pro文件
    • project.pro
    • src/src.pro
    • libwidget/libwidet.pro

可以確定,project.pro 文件沒有什麼懸念:

  • project.pro (本文件內容不變)
TEMPLATE=subdirs
CONFIG += ordered
SUBDIRS += libwidget src

如何做?(二)生成動態庫

使用動態庫,當務之急是生成動態庫。

  • 如果我們不在windows下使用,一切都會比較簡單,源代碼也不需要改動。
  • 在windows下,動態庫導出的東西需要使用 __declspec(dllexport)

我們需要兼顧不同的平臺,幸好Qt有解決方案,改造後的widget.h文件如下:

  • widget.h (本文件內容後續不再改變)
#ifndef WIDGET_H
#define WIDGET_H

#include <QtGui/QWidget>

#if defined(LIBWIDGET_BUILD)
#  define WIDGET_API Q_DECL_EXPORT
#else
#  define WIDGET_API Q_DECL_IMPORT
#endif

class WIDGET_API Widget:public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget * parent=0);
};

#endif // WIDGET_H

然後寫寫 libwidget.pro 文件:

TEMPLATE = lib
TARGET = widget
DEFINES += LIBWIDGET_BUILD
SOURCES += widget.cpp
HEADERS += widget.h

這樣一來,確實可以生成動態庫了。可是總覺得不太好:

  • 首先,windows下debug和release的動態庫是不兼容的,取同一個名字(TARGET=widget)會不會有潛在的問題?
  • 其次,生成的庫放到那個路徑下呢?程序鏈接和運行時如何找到它?

暫且存疑,我們先看看其他

如何做?(三)使用動態庫

看看可執行程序的生成,它要使用我們前面的庫,那麼:

  • 編譯預處理時需要找到頭文件
  • 連接時需要找到庫文件(庫文件在那個目錄下,叫什麼名字)
  • 運行時能夠找到動態庫

src/src.pro 文件可以就寫成這個樣子了:

TEMPLATE=app
INCLUDEPATH += ../libwidget

LIBS += -LThePathWePutLib -lwidget

SOURCES += main.cpp

先不考慮運行時的情況。頭文件和庫文件都和前面的libwidget直接相關,怎麼構建自動化呢?比如:庫文件的名字改動了?庫文件的存放目錄變了?...

如何做?(四)構建自動化

我們構建動態庫的時候,可以控制動態庫的名字,可以控制存放目錄,那麼,我在講動態庫的這部分設置獨立出來不就行了:恩,使用一個 libwidget/libwidget.pri 文件。l由於src/src.pro和libwidget/libwidget.pro共用這個文件,還需要一個開關來進行區分(這就是widget-buildlib):

INCLUDEPATH += $$PWD
TEMPLATE += fakelib
LIBWIDGET_NAME = $$qtLibraryTarget(widget)
TEMPLATE -= fakelib
!widget-buildlib{
    LIBS += -L$$PROJECT_LIBDIR -l$$LIBWIDGET_NAME
}else{
    SOURCES += widget.cpp
    HEADERS += widget.h
}

注意:這兒庫目錄用一個變量PROJECT_LIBDIR表示(你這兒可以直接換成存放庫的目錄),具體稍後解釋。這兒的庫的名字使用qtLibraryTarget進行生成(這樣可以確保windows下debug模式生成的動態庫可以自動加個d),fakelib是用來哄騙qtibraryarget的,不然它只在TEMPLATE爲lib是生效。

 

這樣,可執行程序的生成時,它要使用我們前面的庫,只需要包括進來libwidget.pri,於是:

  • src/src.pro 文件可以就寫成這個樣子了:
TEMPLATE=app
include(../libwidget/libwidget.pri)
SOURCES += main.cpp
  • 相應地,libwidget/libwidget.pro 可以修改如下:
TEMPLATE = lib
CONFIG += widget-buildlib
include(libwidget.pri)
TARGET = $$LIBWIDGET_NAME
CONFIG += debug_and_release build_all
DEFINES += LIBWIDGET_BUILD

如何做?(五)運行自動化

現在似乎一切都比較正常了,可是有一點,我們要將生成的庫文件放到什麼地方呢?才能使得運行時都能被找到(就像沒使用動態庫一樣,點擊IDE中的run或者去目錄下雙擊即可運行)

我們需要:

  • 將庫文件放到 lib目錄下
  • 將可執行文件放到 bin目錄下
  • windows下將 xxx.dll 也放到bin目錄下

恩,這兩個目錄對整個工程比較通用,我們可以考慮建立一個 common.pri 文件:

  • common.pri 內容 (本文件內容後續不再改變)
PROJECT_BINDIR = $$PWD/bin
PROJECT_LIBDIR = $$PWD/lib

然後libwidget/libwidget.pri 包含該common.pri 文件

  • libwidget/libwidget.pri (本文件內容後續不再改變)
INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD
TEMPLATE += fakelib
LIBWIDGET_NAME = $$qtLibraryTarget(widget)
TEMPLATE -= fakelib
include(../common.pri)
!widget-buildlib{
    LIBS += -L$$PROJECT_LIBDIR -l$$LIBWIDGET_NAME
}else{
    SOURCES += widget.cpp
    HEADERS += widget.h
}
  • 完整版的 libwidget/libwidget.pro 文件 (本文件內容後續不再改變)
TEMPLATE = lib
CONFIG += widget-buildlib
include(libwidget.pri)
TARGET = $$LIBWIDGET_NAME
DESTDIR = $$PROJECT_LIBDIR
win32{
    DLLDESTDIR = $$PROJECT_BINDIR
    QMAKE_DISTCLEAN += $$PROJECT_BINDIR/$${LIBWIDGET_NAME}.dll
}
CONFIG += debug_and_release build_all
DEFINES += LIBWIDGET_BUILD

注意:這兒我們指定了庫文件的目錄,並會將dll拷貝到了PROJECT_BINDIR目錄

  • 完整版的 src/src.pro 文件 (本文件內容後續不再改變)
TEMPLATE=app
include(../libwidget/libwidget.pri)
DESTDIR = $$PROJECT_BINDIR
unix:QMAKE_RPATHDIR+=$$PROJECT_LIBDIR
SOURCES += main.cpp

注意:這兒我們對unix下,指定了rpath,使得程序運行時不許設置可以即可找到動態庫

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章