1. 程式人生 > >深度解析 Qt 中動態連結庫

深度解析 Qt 中動態連結庫

本文介紹的是Qt動態連結庫,現在有些軟體有自動升級功能,有些就是下載新的DLL檔案,替換原來的動態連結庫,MFC好象也有類似機制,Qt還有一種方式,就是把一個QWidget子類,編譯成動態連結庫.然後根據動態連結庫建立一個物件,返回QWidget子針,如果你的類不是QWidget的子類,就不能用這種方法了

QLibrary也是用來載入動態連結庫,但它創建出來返回的是某個"方法的指標"(不需要標頭檔案),而QPluginLoader建立返回的是"物件的指標"(物件中有哪些方法可呼叫,就要標頭檔案說了,所以上面的例子中,需要提供一個介面標頭檔案)

昨天一個同事說要把一個類做成DLL的形式,但這個類不是從QWidget繼承。 研究了一下,發現Qt

提供了一個類QPluginLoader可以載入動態連結庫。能把一個普通的類編譯生成DLL,通過QPluginLoader使用它。

寫一個例子說明吧:

工程A中,使用了這樣一個類,專門用來讓算所得稅;

  1. Tax.h  
  2. class  Tax  
  3. {  
  4.   float incomeTax(int income);  
  5. };  
  6. Tax.cpp  
  7. Tax::incomeTax(int income)  
  8. {  
  9. float res=(income-1600)*0.5;  
  10. return res;  
  11. }  
  12. main.cpp  
  13. Int main()  
  14. {  
  15. Tax taxobject;  
  16. taxObject. incomeTax(2200);  
  17. ............  

編譯後部署到機器上.

如果計稅方式變了,則incomeTax()必須改寫了;

  1. Tax::incomeTax(int income)  
  2. {  
  3. float res=(income-2000)*0.3;  
  4. return res;  

標頭檔案沒有變,只是修改了計稅方式,工程A必須重新編譯,然後重新部署; 如果工程A很大或部署的機器很多,代價就大了.

如果讓Tax類,單獨編譯成動態連結庫,再把Tax中的要被使用的介面寫在另一個頭檔案中,供工程A使用;每次計稅方式變了,只需修改Tax類,然後重新編譯生成動態連結庫,然後替換工程A中的相應的動態連結庫.而工程A不用重新編譯即可使用新的計稅方式.

新建一工程B,專門用來把Tax類做成動態連結庫的形式:

  1. //首先定義介面(只要一個頭檔案即可)  
  2. Tax.h  
  3. class Tax  
  4. {  
  5.     virtual float incomeTax(int income);  
  6. };  
  7. QT_BEGIN_NAMESPACE  
  8. Q_DECLARE_INTERFACE(Tax,"TaxDLL/1.0");  //這個巨集用宣告介面  
  9. QT_END_NAMESPACE 

然後從寫一個具體業務類,繼承上面的介面和QObject,實現介面中定義的方法

  1. TaxPlugin.h  
  2. class  TaxPlugin:public QObject,Tax  
  3. {  
  4.     Q_OBJECT  
  5.     Q_INTERFACES(Tax)            
  6. public:  
  7.   float incomeTax(int income);  
  8. };  
  9. TaxPlugin.cpp  
  10. TaxPlugin::incomeTax(int income)  
  11. {  
  12. float res=(income-1600)*0.5;  
  13. return res;  
  14. }  
  15. Q_EXPORT_PLUGIN2(Tax, TaxPlugin);    //這個巨集用來匯出動態連結庫 

編譯工程B,生成Tax.dll.

把工程Tax.dll和標頭檔案Tax.h,拷到工程A中,供工程A使用.

工程A中

  1. int main()  
  2. {  
  3. Tax *taxObject;  
  4. QPluginLoader pluginLoader("Tax.dll");  
  5. QObject *plugin = pluginLoader.instance();  
  6. taxObjectqobject_cast<Tax *>(plugin);  
  7. taxObject->incomeTax(2100);  

如果稅率變了,只要修改工程B中的類,然後重新編譯生成Tax.dll,替換工程A中原來的庫.而工程A不必重新編譯即可使用新的計稅方式.

現在有些軟體有自動升級功能,有些就是下載新的DLL檔案,替換原來的動態連結庫。

MFC好象也有類似機制

Qt還有一種方式,就是把一個QWidget子類,編譯成動態連結庫.然後根據動態連結庫建立一個物件,返回QWidget子針,如果你的類不是QWidget的子類,就不能用這種方法了

QLibrary也是用來載入動態連結庫,但它創建出來返回的是某個"方法的指標"(不需要標頭檔案),而QPluginLoader建立返回的是"物件的指標"(物件中有哪些方法可呼叫,就要標頭檔案說了,所以上面的例子中,需要提供一個介面標頭檔案)動態

記錄一下QT5 動態連結庫的建立和使用

在文章的最後有完成的程式碼供下載

1.建立動態連結庫

先新建一個庫專案

選擇chose進入下一下頁面,型別選擇共享庫,輸入一個名稱:我輸入的是sld

再點選下一步到

如果這裡我們需要QtGui所以也勾選上了

再點選下一步直到完成

我們在.pro檔案里加上

DESTDIR =..\MyDebug

它的意思是我們把生成的內容放到這個資料夾裡,如果沒有它會自動生成

點選專案把Shadow build 去掉勾選

設定完成後我們來實現方法測試

把sld.h修改成這樣

複製程式碼
#ifndef SLD_H
#define SLD_H


#include "sld_global.h"
#include <QString>

class SLDSHARED_EXPORT Sld
{
public :
    Sld();
    QString GetStr();
};

#endif
複製程式碼

它的.cpp

複製程式碼
#include "sld.h"
#include <QDebug>

Sld::Sld()
{
    qDebug()<<"";
}


QString Sld::GetStr()
{
    return "aa";
}
複製程式碼

我們那了一個測試方法返回aa

我們再新新增一個窗體供測試用

我給命名為MyDialog

把.h檔案修改成

複製程式碼
#ifndef MYDIALOG_H
#define MYDIALOG_H

#include <QDialog>
#include "sld_global.h"
namespace Ui {
class MyDialog;
}

class SLDSHARED_EXPORT MyDialog : public QDialog
{
    Q_OBJECT

public:
    explicit MyDialog(QWidget *parent = 0);
    ~MyDialog();

private:
    Ui::MyDialog *ui;
};

#endif // MYDIALOG_H
複製程式碼

,cpp

複製程式碼
#include "mydialog.h"
#include "ui_mydialog.h"

MyDialog::MyDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::MyDialog)
{
    ui->setupUi(this);
}

MyDialog::~MyDialog()
{
    delete ui;
}
複製程式碼

這裡什麼也沒做只是把SLDSHARED_EXPORT加上了

好了就這樣,我們構建一下就會在MyDebug資料夾裡生成我們的dll

2.怎麼使用動態連結庫

新建一個Qt WidgetsApplication專案

在.pro檔案裡修改和上邊一樣加上

DESTDIR =..\MyDebug 再點選專案把shadow build 去掉

再加上

INCLUDEPATH +=../sld
LIBS +=$$DESTDIR/sld.lib

第一句是把sld專案的資料夾包含到這個專案裡來,這樣我們就能直接用它裡面的標頭檔案了

第二句是告訴編譯器lib在哪(我用的是vs的編譯器如果 用mingw就是改成sld.dll)

我樣在窗體上加一個按鈕,並新增槽

.h

複製程式碼
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H
複製程式碼

.cpp

複製程式碼
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "sld.h"
#include <QMessageBox>
#include "mydialog.h"
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    MyDialog * my =new MyDialog(this);
    my->show();
    QMessageBox * msg=new QMessageBox(this);
    Sld s;

    msg->setText(s.GetStr());
    msg->show();
}
複製程式碼

好了,構建,執行看效果吧

作者:李鵬 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。 本文主要講解在QT開發環境中如何使用VC生成的DLL及QT自身生成的DLL。至於其它情況本文不作討論。

連線庫分為2種

(1)動態連線庫,通常有.h .lib .dll三個檔案,功能實現在dll中 (2)靜態連線庫,通常有.h .lib二個檔案,功能實現在lib中 由上可以看出動態庫的lib和靜態庫的lib檔案是不同的。     如果使用生成連線庫的開發環境與使用連線庫的開發環境相同,一般不會出什麼問題,如VC寫的連線庫 (包括動態庫和靜態庫)還在VC中用一般不會有什麼問題的。有時候我們需要DLL跨開發環境,如以前VC下的 DLL想在QT中用。有網友說QT不支援VC生成的靜態庫,所以只測試QT使用動態庫的情況。 【先看VC生成的DLL】 使用VC6建立Win32 dynamic-link library工程,工程中只有兩個檔案,編譯即可生成DLL和LIB [html] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. ////////////////////////////////// MFCDLL.h  /////////////////////////////////////  
  2. #ifndef _MFCDLL_H  
  3. #define _MFCDLL_H  
  4. #ifdef __cplusplus  
  5. extern "C" {  
  6. #endif  
  7. #ifdef DLL  
  8.     // do nothing  
  9. #else  
  10. #define DLL __declspec(dllimport)  
  11. #endif  
  12. DLL void hello();  
  13. DLL int add(int a, int b);  
  14. #ifdef __cplusplus  
  15. }  
  16. #endif  
  17. #endif  
////////////////////////////////// MFCDLL.h  /////////////////////////////////////
#ifndef _MFCDLL_H
#define _MFCDLL_H

#ifdef __cplusplus
extern "C" {
#endif

#ifdef DLL
    // do nothing
#else
#define DLL __declspec(dllimport)
#endif

DLL void hello();
DLL int add(int a, int b);

#ifdef __cplusplus
}
#endif

#endif
[cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. ////////////////////////////////// MFCDLL.cpp  /////////////////////////////////////
  2. #define DLL __declspec(dllexport)
  3. #include "MFCDLL.h"
  4. #include <windows.h>
  5. void hello()  
  6. {  
  7. ::MessageBox(NULL, "hello world!",   
  8. "greeting", MB_OK);  
  9. }  
  10. int add(int a, int b)  
  11. {  
  12. return a + b;  
  13. }  
////////////////////////////////// MFCDLL.cpp  /////////////////////////////////////
#define DLL __declspec(dllexport)
#include "MFCDLL.h"
#include <windows.h>

void hello()
{
::MessageBox(NULL, "hello world!", 
"greeting", MB_OK);
}

int add(int a, int b)
{
return a + b;
}
【使用QT生成DLL】使用QT建立動態庫工程,編譯即可得到DLL(無LIB檔案) [cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. ////////////////////////////////// qtdll_global.h  //////////////////////////////
  2. #ifndef QTDLL_GLOBAL_H
  3. #define QTDLL_GLOBAL_H
  4. #include
  5. #if defined(QTDLL_LIBRARY)
  6. #  define QTDLLSHARED_EXPORT Q_DECL_EXPORT
  7. #else
  8. #  define QTDLLSHARED_EXPORT Q_DECL_IMPORT
  9. #endif
  10. #endif // QTDLL_GLOBAL_H
////////////////////////////////// qtdll_global.h  //////////////////////////////
#ifndef QTDLL_GLOBAL_H
#define QTDLL_GLOBAL_H

#include

#if defined(QTDLL_LIBRARY)
#  define QTDLLSHARED_EXPORT Q_DECL_EXPORT
#else
#  define QTDLLSHARED_EXPORT Q_DECL_IMPORT
#endif

#endif // QTDLL_GLOBAL_H
[cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. ////////////////////////////////// qtdll.h  /////////////////////////////////////
  2. #ifndef QTDLL_H
  3. #define QTDLL_H
  4. #include "qtdll_global.h"
  5. class QTDLLSHARED_EXPORT QTDLL  
  6. {  
  7. public:  
  8.     QTDLL();  
  9. public:  
  10. int add(int a, int b);  
  11. };  
  12. #endif // QTDLL_H
////////////////////////////////// qtdll.h  /////////////////////////////////////
#ifndef QTDLL_H
#define QTDLL_H

#include "qtdll_global.h"

class QTDLLSHARED_EXPORT QTDLL
{
public:
    QTDLL();

public:
    int add(int a, int b);
};

#endif // QTDLL_H
[cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. ////////////////////////////////// qtdll.cpp  ///////////////////////////////////
  2. #include "qtdll.h"
  3. QTDLL::QTDLL()  
  4. {  
  5. }  
  6. int QTDLL::add(int a, int b)  
  7. {  
  8. return a + b;  
  9. }  
////////////////////////////////// qtdll.cpp  ///////////////////////////////////
#include "qtdll.h"


QTDLL::QTDLL()
{
}

int QTDLL::add(int a, int b)
{
    return a + b;
}
【QT顯式載入VC生成的DLL】 [cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. #include <QLibrary>
  4. #include <QDebug>
  5. MainWindow::MainWindow(QWidget *parent) :  
  6.     QMainWindow(parent),  
  7.     ui(new Ui::MainWindow)  
  8. {  
  9.     ui->setupUi(this);  
  10. // DLL顯式載入,只需要DLL檔案即可,不需要.H和.LIB檔案
  11. // 需要將DLL放到可執行目錄中
  12. typedefvoid(*FUN1)();  
  13. typedefint(*FUN2)(intint);  
  14.     QLibrary lib("MFCDLL.dll");  
  15. if (lib.load()) {  
  16.         qDebug() << "load ok!";  
  17.         FUN1 hello = (FUN1)lib.resolve("hello");  
  18.         FUN2 add = (FUN2)lib.resolve("add");  
  19. if (hello) {  
  20.             qDebug() << "load hello ok!";  
  21.             hello();  
  22.         }  
  23. if (add) {  
  24.             qDebug() << "load add ok!";  
  25.             qDebug() << add(3, 5);  
  26.         }  
  27.     } else {  
  28.         qDebug() << "load error!";  
  29.     }  
  30. }  
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QLibrary>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // DLL顯式載入,只需要DLL檔案即可,不需要.H和.LIB檔案
    // 需要將DLL放到可執行目錄中
    typedef void(*FUN1)();
    typedef int(*FUN2)(int, int);

    QLibrary lib("MFCDLL.dll");
    if (lib.load()) {
        qDebug() << "load ok!";

        FUN1 hello = (FUN1)lib.resolve("hello");
        FUN2 add = (FUN2)lib.resolve("add");
        if (hello) {
            qDebug() << "load hello ok!";
            hello();
        }
        if (add) {
            qDebug() << "load add ok!";
            qDebug() << add(3, 5);
        }
    } else {
        qDebug() << "load error!";
    }
}
【QT隱式載入VC生成的DLL】 [cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. #include "lib/MFCDLL.h"
  4. #include <QDebug>
  5. MainWindow::MainWindow(QWidget *parent) :  
  6.     QMainWindow(parent),  
  7.     ui(new Ui::MainWindow)  
  8. {  
  9.     ui->setupUi(this);  
  10. // DLL隱式載入,只需要.DLL .H和.LIB檔案
  11. // 1需要將DLL放到可執行目錄中
  12. // 2將LIB路徑設定到專案PRO檔案中
  13. // 3將標頭檔案包含進來,如果不包含需要自已宣告函式原型及來源(本質與包含標頭檔案相同)
  14.     hello();  
  15.     qDebug() << add(5, 6);  
  16.     qDebug() << "ok";  
  17. }  
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "lib/MFCDLL.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // DLL隱式載入,只需要.DLL .H和.LIB檔案
    // 1需要將DLL放到可執行目錄中
    // 2將LIB路徑設定到專案PRO檔案中
    // 3將標頭檔案包含進來,如果不包含需要自已宣告函式原型及來源(本質與包含標頭檔案相同)
    hello();
    qDebug() << add(5, 6);
    qDebug() << "ok";
}
pro工程檔案中要設定LIB檔案路徑 [cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. # lib檔案路徑
  2. LIBS += "F:/lib/MFC_DLL_TEST_WITH_QT_2/lib/MFCDLL.lib"
# lib檔案路徑
LIBS += "F:/lib/MFC_DLL_TEST_WITH_QT_2/lib/MFCDLL.lib"
【QT使用QT生成的動態庫,隱式】 [cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. #include <QDebug>
  4. #include "lib/qtdll.h"
  5. MainWindow::MainWindow(QWidget *parent) :  
  6.     QMainWindow(parent),  
  7.     ui(new Ui::MainWindow)  
  8. {  
  9.     ui->setupUi(this);  
  10. // QT使用QT生成的DLL
  11. // 1. 包含標頭檔案
  12. // 2. 在工程檔案中指定lib路徑
  13. // 3. 將動態庫拷貝到可執行檔案目錄
  14.     QTDLL dll;  
  15.     qDebug() << dll.add(3, 5);  
  16. }  
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include "lib/qtdll.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // QT使用QT生成的DLL
    // 1. 包含標頭檔案
    // 2. 在工程檔案中指定lib路徑
    // 3. 將動態庫拷貝到可執行檔案目錄
    QTDLL dll;
    qDebug() << dll.add(3, 5);
}
pro工程檔案中的設定 [cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. LIBS += "F:/lib/QT_DLL_TEST_WITH_DLL/lib/QTDLL.dll"