QLibrary 載入動態庫

kevinlq發表於2021-01-18

閱讀本文大概需要 6.6分鐘

一般情況下在沒有標頭檔案支援情況下,想要引入某個動態庫,最好的辦法就是使用「動態載入」的方法,在Qt中一般使用QLibyary來操作

常用 api

 QLibrary(const QString& fileName, QObject *parent = nullptr);
 QFunctionPointer resolve(const char *symbol);
 bool load();
 bool unload();
 bool isLoaded() const;

官方說明

下面我們來看下詳細的使用過程

建立動態庫

我們建立一個「子工程」,用來編寫我們的測試方法,該工程需要匯出為動態庫,完整的示例如下所示

├─App
│      App.pro
│      main.cpp
│
└─Libs
        Libs.pro
        SDKCore.cpp
        SDKCore.h
        SDKCoreImpl.cpp
        SDKCoreImpl.h
        SDKCore_global.h
        SDKStruct.h

我們建立一個函式,並且匯出

SDKCore.h

extern "C" SDK_EXPORT int runFunction(const char * funName, const DS_Request &request, DS_Request& response);

SDKCore.cpp

int runFunction(const char *funName, const DS_Request &request, DS_Request &response)
{
    Internal::SDKCoreImpl m_pCoreImpl;
    int nRet = -1;
    QMetaObject::invokeMethod(&m_pCoreImpl, funName,
                              Qt::DirectConnection,
                              Q_RETURN_ARG(int, nRet),
                              Q_ARG(DS_Request, request),
                              Q_ARG(DS_Request&,response));
    return nRet;
}

這裡為了訪問方便,具體的函式都放在了SDKCoreImpl中實現,呼叫採用了反射,呼叫的時候直接傳方法字串即可

上面中入參和出引數據格式是自定義的,為了統一而自定義,具體如下所示

struct DS_Request{
    QString m_strData;
    QByteArray m_baStream;
};

這樣就相容所有的格式了,不管是傳 字串還是資料流都使用統一的格式

這裡寫一個簡單的加法運算

int SDKCoreImpl::addNum(const DS_Request &request, DS_Request &response)
{
    QVariantMap map = QJsonDocument::fromJson(request.m_strData.toLatin1()).toVariant().toMap();
    int nValue1 = map.value("nValue1").toInt();
    int nValue2 = map.value("nValue2").toInt();
    int nResult = nValue1 + nValue2;
    QVariantMap mapResult;
    mapResult.insert("result", nResult);
    QJsonDocument doc = QJsonDocument::fromVariant(mapResult);
    response.m_strData = QString(doc.toJson());
    return SDKCODE_SUCCEED;
}

載入動態庫

載入動態庫就變的很簡單了,只需要幾行程式碼即可獲取到對應的函式符號

 QLibrary library(strSDKName);
 bool bLoad = library.load();

只要動態庫路徑合法就會載入成功,一般載入失敗可能就是路徑後者匯出符號有問題,遇到這些問題可以排查下

使用前先定義一個函式指標

typedef int (* pRunFunctionFun)(const char *, const DS_Request &, DS_Request&);

呼叫addNum函式

pRunFunctionFun pFun = (pRunFunctionFun) library.resolve("runFunction");
if ( pFun)
{
    int nRet = pFun("addNum", request, response);
    qDebug() << "add Resut:" << nRet << response.m_strData;
}

這樣一個完整的呼叫流程就跑通了

優化程式碼

但是,如果你認為完了,那就錯了,仔細看下就會發現上述程式碼有冗餘,難道每呼叫不同的函式,都要重複上述過程麼?肯定不會呀,程式設計師不會自己乾重復的事情,要重複只能讓機器去做

下面優化下,把重複的程式碼單獨封裝下,定義成巨集

通過上述程式碼,我們可以看出來,首先這個方法可以封裝成模板,每一次呼叫都是一樣的

library.resolve("")

封裝成模板呼叫

template <typename T>
T getFunction(QLibrary *lib, const char *symbol)
{
    T f = (T)lib->resolve(func);
    if (f == nullptr)
    {
        return nullptr;
    }
    return f;
}

然後接著優化,獲取函式符號流程封裝成巨集

#define GET_SDK_FUNC(type, func, symbol)  GET_FUNC(sdk, type, func, symbol);
#define GET_FUNC(sdk, type, func, symbol) \
    type func = getFunction<type>(&sdk, symbol); \
    if (func == nullptr) \
{ \
    qDebug()<<"fun is null: "<< symbol; \
    return 0; \
}

優化完成後,我們看下呼叫過程會如何更簡單

QLibrary library(strSDKName);
bool bLoad = library.load();
GET_FUNC(library, pRunFunctionFun, runFun, "runFunction");
int nRet = runFun("addNum", request, response);

完整程式碼已經上傳到GitHub,感興趣的可以自行去下載

demo 原始碼下載

總結

在平時工作中,很多大型工程都是動態庫呼叫,這樣做的好處就是方便他人直接呼叫使用現有功能和業務,減少重複複雜的工作,提高我們的工作效率

上述程式碼中有沒有發現一條迷惑之處,就是動態庫的路徑和名稱沒有看到呀,是怎麼載入的?

這個其實用到了之前介紹的一種技術,qmake語法糖

閱讀過原始碼你就會發現,我在pri檔案中編寫了字串巨集定義,這樣在程式碼中就可以直接呼叫對應的字串了,如果那一天要修改,那麼只修改pri檔案即可,其它都不需要動

DEFINES += LIB_DIR_PATH=\"\\\"$$LIB_DIR_PATH\\\"\"
DEFINES += FILE_DLL_EXT=\"\\\"$$FILE_DLL_EXT\\\"\"
DEFINES += LIB_NAME=\"\\\"$$LIB_NAME\\\"\"

相關文章