1. 程式人生 > >C++ Qt 呼叫動態庫 外掛

C++ Qt 呼叫動態庫 外掛

1.隱式呼叫

1>新建一個C++庫,工程名為Cal,自動生成三個檔案cal.h, cal_global.h, cal.cpp,編譯後生成我們需要的Cal.lib, Cal.dll

#ifndef CAL_H
#define CAL_H

#include "cal_global.h"

class CALSHARED_EXPORT Cal
{

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

#endif // CAL_H

#include "cal.h"


Cal::Cal()
{
}

int Cal::add(int a, int b)
{
    return a + b;
}

int Cal::substract(int a, int b)
{
    return a - b;
}
2>新建一個Qt應用程式,工程名為CalTest

在CalTest.pro檔案所在目錄下新建一個lib->cal目錄,將cal相關檔案拷貝到這個目錄下:cal.h, cal_global.h, Cal.lib;

在CalTest.pro檔案後面增加兩行程式碼:

INCLUDEPATH += $${PWD}/lib/cal
LIBS += $${PWD}/lib/cal/Cal.lib

我們在建構函式裡面測試,程式碼如下:

#include "dialog.h"
#include "ui_dialog.h"
#include "cal.h"
#include <QDebug>

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);
    Cal cal;
    int a = 100, b = 200;
    int addResult = cal.add(a, b);
    int substractResult = cal.substract(a, b);
    qDebug() << "add:" << addResult << ",substract:" << substractResult;
}

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


編譯完成之後,將Cal.dll檔案拷貝到生成的CalTest.exe目錄下執行即可。

隱式呼叫用起來比較方便,但是不太靈活,如果缺少標頭檔案,庫檔案那麼這個程式就編譯,執行不了了。

2.顯示呼叫

Cal工程程式碼如下:

#ifndef CAL_H
#define CAL_H

#include "cal_global.h"

class CALSHARED_EXPORT Cal
{

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

extern "C" CALSHARED_EXPORT Cal* createCal();
extern "C" CALSHARED_EXPORT void deleteCal(Cal *cal);

#endif // CAL_H
#include "cal.h"


Cal::Cal()
{
}

int Cal::add(int a, int b)
{
    return a + b;
}

int Cal::substract(int a, int b)
{
    return a - b;
}

Cal* createCal()
{
    return new Cal();
}

void deleteCal(Cal *cal)
{
    delete cal;
    cal = NULL;
}

CalTest呼叫的時候,CalTest.pro裡就不需要

INCLUDEPATH += $${PWD}/lib/cal
LIBS += $${PWD}/lib/cal/Cal.lib

這兩行程式碼了,直接看cpp檔案

#include "dialog.h"
#include "ui_dialog.h"
#include <QDebug>
#include <QLibrary>
#include "../Cal/cal.h"


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

    typedef Cal* (*CreateCal)();
    typedef void (*DeleteCal)(Cal *cal);

    QLibrary lib("Cal.dll");
    if (lib.load()) {
        CreateCal createCal = (CreateCal)lib.resolve("createCal");
        DeleteCal deleteCal = (DeleteCal)lib.resolve("deleteCal");
        if (createCal && deleteCal) {
            Cal *pcal = createCal();
            int a = 100, b = 200;
            int addResult = pcal->add(a, b);
            int substractResult = pcal->substract(a, b);
            qDebug() << "add:" << addResult << ",substract:" << substractResult;
            deleteCal(pcal);
        } else {
            qDebug() << "resolve failed";
        }
        lib.unload();
    } else {
        qDebug() << "load failed";
    }
}

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

#include "../Cal/cal.h"標頭檔案還是需要的,因為我們不知道Cal類是怎麼宣告的。

這樣當這個dll不存在的時候我們的程式還是可以通過編譯執行的。

但是這裡還存在問題,Cal類我們還是匯出了,因為我們演示的是一個比較簡單的類,如果這個類比較複雜那麼匯出就有麻煩了,因為匯出類的父類和子類也要匯出,所以我們希望這個類不匯出也可以使用。

3.純虛基類,不匯出類,通過虛表來定位具體的函式

Cal工程程式碼

#ifndef CALINTERFACE_H
#define CALINTERFACE_H

#include "cal_global.h"

class CalInterface
{
public:
    virtual ~CalInterface() {}
    virtual int add(int a, int b) = 0;
    virtual int substract(int a, int b) = 0;
};

extern "C" CALSHARED_EXPORT CalInterface* createCal();
extern "C" CALSHARED_EXPORT void deleteCal(CalInterface *cal);

#endif // CALINTERFACE_H

#include "CalInterface.h"
#include "cal.h"

CalInterface* createCal()
{
    return new Cal();
}

void deleteCal(CalInterface *cal)
{
    delete cal;
    cal = NULL;
}

#ifndef CAL_H
#define CAL_H

#include "cal_global.h"
#include "CalInterface.h"

class Cal : public CalInterface
{

public:
    Cal();
    virtual ~Cal();

    virtual int add(int a, int b);
    virtual int substract(int a, int b);
};

#endif // CAL_H
Cal.cpp內容和上面一樣
CalTest工程呼叫:
#include "dialog.h"
#include "ui_dialog.h"
#include <QDebug>
#include <QLibrary>
#include "../Cal/CalInterface.h"


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

    typedef CalInterface* (*CreateCal)();
    typedef void (*DeleteCal)(CalInterface *cal);

    QLibrary lib("Cal.dll");
    if (lib.load()) {
        CreateCal createCal = (CreateCal)lib.resolve("createCal");
        DeleteCal deleteCal = (DeleteCal)lib.resolve("deleteCal");
        if (createCal && deleteCal) {
            CalInterface *pcal = createCal();
            int a = 100, b = 200;
            int addResult = pcal->add(a, b);
            int substractResult = pcal->substract(a, b);
            qDebug() << "add:" << addResult << ",substract:" << substractResult;
            deleteCal(pcal);
        } else {
            qDebug() << "resolve failed";
        }
        lib.unload();
    } else {
        qDebug() << "load failed";
    }
}

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


這樣Cal類就不必要匯出了,而且CalInterface.h標頭檔案是比較獨立的,不依賴於其他的一些東西。

C++呼叫方法跟這類似,只是將QLibrary換成了LoadLibrary,GetProcAddress。

4.外掛編寫(lower-level api)

1>建立C++庫,型別選擇Qt Plugin,名稱CalPlugin,刪掉自動生成的genericplugin.h和genericplugin.cpp兩個檔案

2>建立介面類標頭檔案calinterface.h

#ifndef CALINTERFACE_H
#define CALINTERFACE_H

#include <QtPlugin>

class CalInterface
{
public:
    virtual ~CalInterface() {}
    virtual int add(int a, int b) = 0;
    virtual int substract(int a, int b) = 0;
};

Q_DECLARE_INTERFACE(CalInterface, "org.qt-project.Qt.Examples.CalPlugin.CalInterface")

#endif // CALINTERFACE_H
3>建立實現類Cal
#ifndef CAL_H
#define CAL_H

#include <QObject>
#include "calinterface.h"

class Cal : public QObject, public CalInterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.CalPlugin.Cal" FILE "CalPlugin.json")
    Q_INTERFACES(CalInterface)

public:
    explicit Cal(QObject *parent = 0);
    int add(int a, int b);
    int substract(int a, int b);
};

#endif // CAL_H

#include "cal.h"

Cal::Cal(QObject *parent)
    : QObject(parent)
{
}

int Cal::add(int a, int b)
{
    return a + b;
}

int Cal::substract(int a, int b)
{
    return a - b;
}
編譯預設生成的dll檔案在安裝目錄的plugins子目錄下面,等會我們需要將它拷貝出來。

5.呼叫外掛

1>建立CalPluginTest對話方塊型別的應用程式,編譯執行;

2>在CalPluginTest.exe所在目錄下新建一個plugins子目錄,將上面生成的CalPlugin.dll外掛拷貝到plugins目錄下;

3>直接在建構函式寫測試程式碼如下:

#include "dialog.h"
#include "ui_dialog.h"
#include <QtWidgets>
#include "../CalPlugin/calinterface.h"

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

    QDir dir(QCoreApplication::applicationDirPath() + "/plugins");
    foreach (QString filename, dir.entryList(QDir::Files)) {
        qDebug() << "path:" << dir.absoluteFilePath(filename);
        QPluginLoader loader(dir.absoluteFilePath(filename));
        CalInterface *pcal = qobject_cast<CalInterface*>(loader.instance());
        if (pcal) {
            int a = 12, b = 34;
            int addResult = pcal->add(a, b);
            int substractResult = pcal->substract(a, b);
            qDebug() << "add:" << addResult << ", substract:" << substractResult;
        } else {
            qDebug() << "error";
        }
    }
}

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