Qt-插件机制

1 qt插件的使用

参考:【QT】QT中插件化开发及其简单使用_bailang_zhizun的博客-CSDN博客_qt插件开发 

代码:qt插件的简单使用,插件创建,和使用-Flutter文档类资源-CSDN下载

2 剖析

Qt-插件机制_第1张图片

QT插件程序开发流程
编写扩展 Qt 应用程序的插件,步骤如下:

(1)、声明一个继承自 QObject 和插件想要提供的接口的插件类

(2)、使用 Q_INTERFACES() 宏来告诉 Qt 元对象系统有关接口的情况

(3)、使用 Q_PLUGIN_METADATA() 宏导出插件

(4)、使用合适的 .pro 文件构建插件
 

抽象接口类:

#ifndef CALINTERFACE_H
#define CALINTERFACE_H

//定义接口
class CalInterface
{
public:
    virtual ~CalInterface() {}
    virtual int add(int a,int b) = 0;
};


#define CalInterface_iid "Examples.Plugin.CalInterface"

QT_BEGIN_NAMESPACE
Q_DECLARE_INTERFACE(CalInterface,CalInterface_iid)
QT_END_NAMESPACE

#endif // CALINTERFACE_H

        创建了一个纯虚函数接口add,这里的DeclareInterface_iid 宏定义字符串一定要是唯一的,然后使用宏Q_DECLARE_INTERFACE来声明该接口

实现类:

#ifndef CALPLUGIN_H
#define CALPLUGIN_H

#include 
#include 
#include "calinterface.h"

class CalPlugin : public QObject,public CalInterface
{
    Q_OBJECT
    Q_INTERFACES(CalInterface)
    Q_PLUGIN_METADATA(IID CalInterface_iid FILE "calplugin.json")

public:
    explicit CalPlugin(QObject *parent = nullptr);
    int add(int a,int b);
};

#endif // CALPLUGIN_H

        注意,在头文件中一定要用Q_INTERFACES宏来声明接口类。

        这里的qtplugin.json是为插件提供插件信息的,并使用Q_PLUGIN_METADATA声明(实例化该对象的)插件的元数据,元数据是插件的一部分。

        Q_PLUGIN_METADATA这个宏所在的类必须是默认可构造的。

        FILE 是可选的,并指向一个 Json 文件。Json 文件必须位于构建系统指定的包含目录之一中(在本工程中和.pro在同级目录下)。当无法找到指定的文件时,moc 会出现错误。如果不想为插件提供信息,当然不会有任何问题,只需保证 Json 文件为空就行

Q_PLUGIN_METADATA

作用:

(1)读取json文件,将内容写入到qt_plugin_metadata

(2)提供插件的实例对象的句柄接口

展开:

#define Q_PLUGIN_METADATA(x) QT_ANNOTATE_CLASS(qt_plugin_metadata, x)

    Q_PLUGIN_METADATA(IID CalInterface_iid FILE "calplugin.json")

与qt_plugin_metadata有关,在moc文件中查看该qt_plugin_metadata的信息,如下

QT_PLUGIN_METADATA_SECTION

作用:

(1)指定当前插件的元数据存储数据段

(2)qt_pluginMetaData数组 :存储了与插件相关的具体信息

#pragma section(".qtmetadata",read,shared)
#  define QT_PLUGIN_METADATA_SECTION \
    __declspec(allocate(".qtmetadata"))
#else
#  define QT_PLUGIN_VERIFICATION_SECTION
#  define QT_PLUGIN_METADATA_SECTION
#endif

 __declspec(allocate 是让编译器分配一个名字为.qtmetadata的段 

__declspec(allocate("segname"))和__declspec( selectany )(AC1)_Antoinette的博客-CSDN博客.qtmetadata的段的内容是:


QT_PLUGIN_METADATA_SECTION
static const unsigned char qt_pluginMetaData[] = {
    'Q', 'T', 'M', 'E', 'T', 'A', 'D', 'A', 'T', 'A', ' ', ' ',
    'q',  'b',  'j',  's',  0x01, 0x00, 0x00, 0x00,
    0xa0, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
    0x8c, 0x00, 0x00, 0x00, 0x1b, 0x03, 0x00, 0x00,
    0x03, 0x00, 'I',  'I',  'D',  0x00, 0x00, 0x00,
    0x1c, 0x00, 'E',  'x',  'a',  'm',  'p',  'l', 
    'e',  's',  '.',  'P',  'l',  'u',  'g',  'i', 
    'n',  '.',  'C',  'a',  'l',  'I',  'n',  't', 
    'e',  'r',  'f',  'a',  'c',  'e',  0x00, 0x00,
    0x15, 0x09, 0x00, 0x00, 0x08, 0x00, 'M',  'e', 
    't',  'a',  'D',  'a',  't',  'a',  0x00, 0x00,
    0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x9b, 0x0c, 0x00, 0x00,
    0x09, 0x00, 'c',  'l',  'a',  's',  's',  'N', 
    'a',  'm',  'e',  0x00, 0x09, 0x00, 'C',  'a', 
    'l',  'P',  'l',  'u',  'g',  'i',  'n',  0x00,
    '1',  0x00, 0x00, 0x00, 0x05, 0x00, 'd',  'e', 
    'b',  'u',  'g',  0x00, 0x1a, '!',  0xa1, 0x00,
    0x07, 0x00, 'v',  'e',  'r',  's',  'i',  'o', 
    'n',  0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
    '8',  0x00, 0x00, 0x00, 'T',  0x00, 0x00, 0x00,
    'p',  0x00, 0x00, 0x00, '|',  0x00, 0x00, 0x00
};

QT_MOC_EXPORT_PLUGIN(CalPlugin, CalPlugin)

 其中存储的信息:看上面的英文的组合:

QTMETADATA  

IID

Examples.Plugin.CalInterface  //定义的id

MetaData   //插件的元数据:加载的json文件

className  //插件的类名称

CalPlugin  //接口类名称

debug   //编译模式

Version  //版本

QT_MOC_EXPORT_PLUGIN

#  define QT_MOC_EXPORT_PLUGIN(PLUGINCLASS, PLUGINCLASSNAME)      \
            Q_EXTERN_C Q_DECL_EXPORT \
            const char *qt_plugin_query_metadata() \
            { return reinterpret_cast(qt_pluginMetaData); } \
            Q_EXTERN_C Q_DECL_EXPORT QT_PREPEND_NAMESPACE(QObject) *qt_plugin_instance() \
            Q_PLUGIN_INSTANCE(PLUGINCLASS)

#endif

作用:

1 获取当前插件对象的元数据

2 获取qt插件的实例句柄

  Q_PLUGIN_INSTANCE(PLUGINCLASS) 也就是   QT_MOC_EXPORT_PLUGIN(CalPlugin, CalPlugin)

#define Q_PLUGIN_INSTANCE(IMPLEMENTATION) \
        { \
            static QT_PREPEND_NAMESPACE(QPointer) _instance; \
            if (!_instance)      \
                _instance = new IMPLEMENTATION; \
            return _instance; \
        }

在这里创建了插件实现类CalPlugin的实例对象

Q_INTERFACES

#define Q_INTERFACES(x) QT_ANNOTATE_CLASS(qt_interfaces, x)

其中传入的是插件的抽象接口类名称:

    Q_INTERFACES(CalInterface)

在moc文件中,

void *CalPlugin::qt_metacast(const char *_clname)
{
    if (!_clname) return nullptr;
    if (!strcmp(_clname, qt_meta_stringdata_CalPlugin.stringdata0))
        return static_cast(this);
    if (!strcmp(_clname, "CalInterface"))
        return static_cast< CalInterface*>(this);
    if (!strcmp(_clname, "Examples.Plugin.CalInterface"))
        return static_cast< CalInterface*>(this);
    return QObject::qt_metacast(_clname);
}

当:    if (!strcmp(_clname, "Examples.Plugin.CalInterface"))
        return static_cast< CalInterface*>(this);

上面这两行当且使用了Q_INTERFACES才会有。

在将插件进行上行转换时,检查插件的抽象接口ID:Examples.Plugin.CalInterface

Qt-插件机制_第2张图片

 以确保插件的有效性

Q_DECLARE_INTERFACE

Qt-插件机制_第3张图片

#ifndef Q_MOC_RUN
#  define Q_DECLARE_INTERFACE(IFace, IId) \
    //1 获取当前插件的抽象接口id
    template <> inline const char *qobject_interface_iid() \
    { return IId; } \

    //2 获取抽象接口对象 的指针,同时获取qt_metacast(IId)对插件接口进行验证,qt_metacast(IId)的作用在上面有讲述,当子类使用Q_INTERFACES宏定义时会产出moc 中 关于qt_metacast(IId)的代码来校验上行转换
    template <> inline IFace *qobject_cast(QObject *object) \
    { return reinterpret_cast((object ? object->qt_metacast(IId) : Q_NULLPTR)); } \
    template <> inline IFace *qobject_cast(const QObject *object) \
    { return reinterpret_cast((object ? const_cast(object)->qt_metacast(IId) : Q_NULLPTR)); }
#endif // Q_MOC_RUN

 Q_DECLARE_INTERFACE 需要与Q_INTERFACES 宏成对出现,保证插件的有效性

3 调用方式

 

 Qt-插件机制_第4张图片

 

 

    QDir pluginsDir(qApp->applicationDirPath());
#if defined(Q_OS_WIN)
    if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release")
        pluginsDir.cdUp();
#elif defined(Q_OS_MAC)
    if (pluginsDir.dirName() == "MacOS") {
        pluginsDir.cdUp();
        pluginsDir.cdUp();
        pluginsDir.cdUp();
    }
#endif
    pluginsDir.cd("plugins");
    foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
        QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
        QObject *plugin = pluginLoader.instance();
        qDebug() << "--->>>Lynn<<<---" << __FUNCTION__ << pluginLoader.errorString();
        if (plugin) {
            m_pInterface = qobject_cast(plugin);
            if (m_pInterface)
                return true;
        }
    }

    return false;

主要使用 QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));来加载插件

QPluginLoader       

        QPluginLoader 提供对 Qt 插件的访问。Qt 插件存储在共享库(DLL)中,与使用 QLibrary 访问共享库相比,它具有以下优势:

  • QPluginLoader 检查插件是否链接到与应用程序相同的 Qt 版本。
  • QPluginLoader 提供对根组件对象(instance())的直接访问,而不是强制手动解析 C 函数。
  • QPluginLoader 对象的实例对单个共享库文件进行操作。它以独立于平台的方式提供对插件功能的访问。

        如果插件尚未加载,则 instance() 函数会隐式地尝试加载插件。 QPluginLoader 的多个实例可用于访问同一个物理插件。加载后,插件将保留在内存中,直到卸载了 QPluginLoader 的所有实例,或者直到应用程序终止。

加载过程

  QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));

QPluginLoader::QPluginLoader(const QString &fileName, QObject *parent)
    : QObject(parent), d(0), did_load(false)
{
    setFileName(fileName);
    setLoadHints(QLibrary::PreventUnloadHint);
}

加载策略: 

    enum LoadHint {
        ResolveAllSymbolsHint = 0x01,
        ExportExternalSymbolsHint = 0x02,
        LoadArchiveMemberHint = 0x04,
        PreventUnloadHint = 0x08,
        DeepBindHint = 0x10
    };
  • QLibrary::ResolveAllSymbolsHint 0x01 使库中的所有符号在加载时被解析,而不仅仅是在调用 resolve() 时。
  • QLibrary::ExportExternalSymbolsHint 0x02导出库中未解析的和外部符号,以便它们可以在稍后加载的其他动态加载库中解析。
  • QLibrary::LoadArchiveMemberHint 0x04允许库的文件名指定归档文件中的特定对象文件。如果给出此提示,则库的文件名由路径组成,路径是对归档文件的引用,后跟对归档成员的引用。
  • QLibrary::PreventUnloadHint 0x08 如果调用 close(),则防止库从地址空间中卸载。如果稍后调用 open(),则不会重新初始化库的静态变量。
  • QLibrary::DeepBindHint 0x10 指示链接器在解析加载库中的外部符号时,优先选择加载库中的定义而不是加载应用程序中导出的定义。此选项仅在 Linux 上受支持

QPluginLoader::setFileName

QPluginLoader::QPluginLoader(const QString &fileName, QObject *parent)
    : QObject(parent), d(0), did_load(false)
{
    setFileName(fileName);
    setLoadHints(QLibrary::PreventUnloadHint);
}
void QPluginLoader::setFileName(const QString &fileName)
{
#if defined(QT_SHARED)
    QLibrary::LoadHints lh = QLibrary::PreventUnloadHint;
    if (d) {
        lh = d->loadHints();
        d->release();
        d = 0;
        did_load = false;
    }

    // 1 定位插件
    const QString fn = locatePlugin(fileName);

    //2 查找并创建插件
    d = QLibraryPrivate::findOrCreate(fn, QString(), lh);

    //3 更新插件状态
    if (!fn.isEmpty())
        d->updatePluginState();

#else
    if (qt_debug_component()) {
        qWarning("Cannot load %s into a statically linked Qt library.",
            (const char*)QFile::encodeName(fileName));
    }
    Q_UNUSED(fileName);
#endif
}

查找并创建插件的过程:调用 QLibraryPrivate::findOrCreate-> QLibraryStore::findOrCreate

QLibraryPrivate *QLibraryPrivate::findOrCreate(const QString &fileName, const QString &version,
                                               QLibrary::LoadHints loadHints)
{
    return QLibraryStore::findOrCreate(fileName, version, loadHints);
}

更新插件状态的过程:


void QLibraryPrivate::updatePluginState()
{
    errorString.clear();
    if (pluginState != MightBeAPlugin)
        return;

    bool success = false;

#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
    if (fileName.endsWith(QLatin1String(".debug"))) {
        // refuse to load a file that ends in .debug
        // these are the debug symbols from the libraries
        // the problem is that they are valid shared library files
        // and dlopen is known to crash while opening them

        // pretend we didn't see the file
        errorString = QLibrary::tr("The shared library was not found.");
        pluginState = IsNotAPlugin;
        return;
    }
#endif


    //1 插件句柄如果没有获取,则调用findPatternUnloaded获取插件
     否则调用 qt_get_metadata
    if (!pHnd) {
        // scan for the plugin metadata without loading
        success = findPatternUnloaded(fileName, this);
    } else {
        // library is already loaded (probably via QLibrary)
        // simply get the target function and call it.
        QtPluginQueryVerificationDataFunction getMetaData = NULL;
        getMetaData = (QtPluginQueryVerificationDataFunction) resolve("qt_plugin_query_metadata");
        success = qt_get_metadata(getMetaData, this);
    }

    if (!success) {
        if (errorString.isEmpty()){
            if (fileName.isEmpty())
                errorString = QLibrary::tr("The shared library was not found.");
            else
                errorString = QLibrary::tr("The file '%1' is not a valid Qt plugin.").arg(fileName);
        }
        pluginState = IsNotAPlugin;
        return;
    }

    pluginState = IsNotAPlugin; // be pessimistic

    //2 版本判断
    uint qt_version = (uint)metaData.value(QLatin1String("version")).toDouble();
    bool debug = metaData.value(QLatin1String("debug")).toBool();
    if ((qt_version & 0x00ff00) > (QT_VERSION & 0x00ff00) || (qt_version & 0xff0000) != (QT_VERSION & 0xff0000)) {
        if (qt_debug_component()) {
            qWarning("In %s:\n"
                 "  Plugin uses incompatible Qt library (%d.%d.%d) [%s]",
                 QFile::encodeName(fileName).constData(),
                 (qt_version&0xff0000) >> 16, (qt_version&0xff00) >> 8, qt_version&0xff,
                 debug ? "debug" : "release");
        }
        errorString = QLibrary::tr("The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5]")
            .arg(fileName)
            .arg((qt_version&0xff0000) >> 16)
            .arg((qt_version&0xff00) >> 8)
            .arg(qt_version&0xff)
            .arg(debug ? QLatin1String("debug") : QLatin1String("release"));
#ifndef QT_NO_DEBUG_PLUGIN_CHECK
    } else if(debug != QLIBRARY_AS_DEBUG) {
        //don't issue a qWarning since we will hopefully find a non-debug? --Sam
        errorString = QLibrary::tr("The plugin '%1' uses incompatible Qt library."
                 " (Cannot mix debug and release libraries.)").arg(fileName);
#endif
    } else {
        pluginState = IsAPlugin;
    }
}

static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib)

        这将打开指定的库,将其映射到内存中,然后搜索 QT_PLUGIN_VERIFICATION_DATA。 这种方法的优点是我们无需实际加载库即可获取验证数据。这使我们能够更安全地检测不匹配。 如果版本信息不存在,或者如果信息无法读取。  如果版本信息存在并成功读取,则返回 true。

 

QLibraryStore

 路径:C:\Qt\5.9.8\Src\qtbase\src\corelib\plugin\qlibrary_p.h 中QLibraryPrivate中声明了友元类QLibraryStore:

class QLibraryStore
{
public:
    inline ~QLibraryStore();
    static inline QLibraryPrivate *findOrCreate(const QString &fileName, const QString &version, QLibrary::LoadHints loadHints);
    static inline void releaseLibrary(QLibraryPrivate *lib);

    static inline void cleanup();

private:
    static inline QLibraryStore *instance();

    // all members and instance() are protected by qt_library_mutex
    typedef QMap LibraryMap;
    LibraryMap libraryMap;
};

static QBasicMutex qt_library_mutex;
static QLibraryStore *qt_library_data = 0;
static bool qt_library_data_once;

   QLibraryStore 中维持着一个 typedef QMap LibraryMap 
    LibraryMap libraryMap;表,映射了插件fileName和QLibraryPrivate的关系:是一一对应的关系;且QLibraryStore是作为一个单例使用,从instance 中创建,因此仅仅存在一份映射表;

......

// must be called with a locked mutex
QLibraryStore *QLibraryStore::instance()
{
    if (Q_UNLIKELY(!qt_library_data_once && !qt_library_data)) {
        // only create once per process lifetime
        qt_library_data = new QLibraryStore;
        qt_library_data_once = true;
    }
    return qt_library_data;
}


//创建插件
inline QLibraryPrivate *QLibraryStore::findOrCreate(const QString &fileName, const QString &version,
                                                    QLibrary::LoadHints loadHints)
{

    //1 上锁
    QMutexLocker locker(&qt_library_mutex);
    QLibraryStore *data = instance();


    //2 在map中查找fileName 是否已经加载
    // check if this library is already loaded
    QLibraryPrivate *lib = 0;
    if (Q_LIKELY(data)) {
        lib = data->libraryMap.value(fileName);
        if (lib)
            lib->mergeLoadHints(loadHints);
    }

    //3 如果插件没有加载 则创建QLibraryPrivate
    if (!lib)
        lib = new QLibraryPrivate(fileName, version, loadHints);

    // track this library
    if (Q_LIKELY(data) && !fileName.isEmpty())
        data->libraryMap.insert(fileName, lib);


    //4 增加引用计数
    lib->libraryRefCount.ref();
    return lib;
}

//释放插件
inline void QLibraryStore::releaseLibrary(QLibraryPrivate *lib)
{
    QMutexLocker locker(&qt_library_mutex);
    QLibraryStore *data = instance();

    if (lib->libraryRefCount.deref()) {
        // still in use
        return;
    }

    // no one else is using
    Q_ASSERT(lib->libraryUnloadCount.load() == 0);

    if (Q_LIKELY(data) && !lib->fileName.isEmpty()) {
          //从映射表中移除
        QLibraryPrivate *that = data->libraryMap.take(lib->fileName);
        Q_ASSERT(lib == that);
        Q_UNUSED(that);
    }
    delete lib;
}

QObject *plugin = pluginLoader.instance();

        返回插件的根组件对象。 必要时加载插件。 如果无法加载插件或无法实例化根组件对象,则该函数返回 nullptr。
        如果根组件对象被销毁,调用此函数会创建一个新实例。
        此函数返回的根组件在 QPluginLoader 被销毁时不会被删除。 如果您想确保根组件被删除,您应该在不再需要访问核心组件时立即调用 unload()。 当库最终被卸载时,根组件将被自动删除。
        组件对象是一个 QObject。 使用 qobject_cast() 访问您感兴趣的接口。

QObject *QPluginLoader::instance()
{
    if (!isLoaded() && !load())
        return 0;
    if (!d->inst && d->instance)
        d->inst = d->instance();
    return d->inst.data();
}

instance首先调用load

        加载插件,如果插件加载成功则返回true; 否则返回假。 由于 instance() 总是在解析任何符号之前调用此函数,因此无需显式调用它。 在某些情况下,您可能希望提前加载插件,在这种情况下,您将使用此功能。

bool QPluginLoader::load()
{
    if (!d || d->fileName.isEmpty())
        return false;
    if (did_load)
        return d->pHnd && d->instance;
    if (!d->isPlugin())
        return false;
    did_load = true;
    return d->loadPlugin();
}

load 在检查文件名,是否已经加载,是否是一个插件后,开始加载插件:bool QLibraryPrivate::loadPlugin()

bool QLibraryPrivate::loadPlugin()
{

    //1 实例存在,则引用+1
    if (instance) {
        libraryUnloadCount.ref();
        return true;
    }

    // 2 不是插件 返回false
    if (pluginState == IsNotAPlugin)
        return false;

    //3 加载插件
    if (load()) {
        // 4 解析动态库符号
        instance = (QtPluginInstanceFunction)resolve("qt_plugin_instance");
        return instance;
    }

    //4 打印信息
    if (qt_debug_component())
        qWarning() << "QLibraryPrivate::loadPlugin failed on" << fileName << ":" << errorString;
    pluginState = IsNotAPlugin;
    return false;
}


其中第三步 加载插件的过程:

bool QLibraryPrivate::load()
{
    //1 判断是否存在
    if (pHnd) {
        libraryUnloadCount.ref();
        return true;
    }

    //2 判断文件名是否为空
    if (fileName.isEmpty())
        return false;


    //3 调用相应平台的加载函数
    bool ret = load_sys();
    if (qt_debug_component()) {
        if (ret) {
            qDebug() << "loaded library" << fileName;
        } else {
            qDebug() << qUtf8Printable(errorString);
        }
    }
    if (ret) {
        //when loading a library we add a reference to it so that the QLibraryPrivate won't get deleted
        //this allows to unload the library at a later time
        libraryUnloadCount.ref();
        libraryRefCount.ref();
        installCoverageTool(this);
    }

    return ret;
}

其中第三步 //3 调用相应平台的加载函数load_sys ,调用windows平台的:LoadLibrary 加载动态库


bool QLibraryPrivate::load_sys()
{
#ifndef Q_OS_WINRT
    //avoid 'Bad Image' message box
    UINT oldmode = SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
#endif
    // We make the following attempts at locating the library:
    //
    // Windows
    // if (absolute)
    //     fileName
    //     fileName + ".dll"
    // else
    //     fileName + ".dll"
    //     fileName
    //
    // NB If it's a plugin we do not ever try the ".dll" extension
    QStringList attempts;

    if (pluginState != IsAPlugin)
        attempts.append(fileName + QLatin1String(".dll"));

    // If the fileName is an absolute path we try that first, otherwise we
    // use the system-specific suffix first
    QFileSystemEntry fsEntry(fileName);
    if (fsEntry.isAbsolute())
        attempts.prepend(fileName);
    else
        attempts.append(fileName);
#ifdef Q_OS_WINRT
    if (fileName.startsWith(QLatin1Char('/')))
        attempts.prepend(QDir::rootPath() + fileName);
#endif

    for (const QString &attempt : qAsConst(attempts)) {
#ifndef Q_OS_WINRT
       //1 调用  windows api LoadLibrary 显式加载动态库
        pHnd = LoadLibrary((wchar_t*)QDir::toNativeSeparators(attempt).utf16());
#else // Q_OS_WINRT
        QString path = QDir::toNativeSeparators(QDir::current().relativeFilePath(attempt));
        pHnd = LoadPackagedLibrary((LPCWSTR)path.utf16(), 0);
        if (pHnd)
            qualifiedFileName = attempt;
#endif // !Q_OS_WINRT

        // If we have a handle or the last error is something other than "unable
        // to find the module", then bail out
        if (pHnd || ::GetLastError() != ERROR_MOD_NOT_FOUND)
            break;
    }

#ifndef Q_OS_WINRT
    SetErrorMode(oldmode);
#endif
    if (!pHnd) {
        errorString = QLibrary::tr("Cannot load library %1: %2").arg(
                    QDir::toNativeSeparators(fileName)).arg(qt_error_string());
    } else {
        // Query the actual name of the library that was loaded
        errorString.clear();

#ifndef Q_OS_WINRT
        wchar_t buffer[MAX_PATH];
        ::GetModuleFileName(pHnd, buffer, MAX_PATH);

        QString moduleFileName = QString::fromWCharArray(buffer);
        moduleFileName.remove(0, 1 + moduleFileName.lastIndexOf(QLatin1Char('\\')));
        const QDir dir(fsEntry.path());
        if (dir.path() == QLatin1String("."))
            qualifiedFileName = moduleFileName;
        else
            qualifiedFileName = dir.filePath(moduleFileName);
        
         //2 如果是防止卸载策略
        if (loadHints() & QLibrary::PreventUnloadHint) {
            // prevent the unloading of this component
            HMODULE hmod;
            bool ok = GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_PIN |
                                        GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
                                        reinterpret_cast(pHnd),
                                        &hmod);
            Q_ASSERT(!ok || hmod == pHnd);
            Q_UNUSED(ok);
        }
#endif // !Q_OS_WINRT
    }
    return (pHnd != 0);
}


卸载过程


bool QLibraryPrivate::unload(UnloadFlag flag)
{
    if (!pHnd)
        return false;
    if (libraryUnloadCount.load() > 0 && !libraryUnloadCount.deref()) { // only unload if ALL QLibrary instance wanted to
        delete inst.data();
        if (flag == NoUnloadSys || unload_sys()) {
            if (qt_debug_component())
                qWarning() << "QLibraryPrivate::unload succeeded on" << fileName
                           << (flag == NoUnloadSys ? "(faked)" : "");
            //when the library is unloaded, we release the reference on it so that 'this'
            //can get deleted
            libraryRefCount.deref();
            pHnd = 0;
            instance = 0;
        }
    }

    return (pHnd == 0);
}

widnow api:FreeLibrary

bool QLibraryPrivate::unload_sys()
{
    if (!FreeLibrary(pHnd)) {
        errorString = QLibrary::tr("Cannot unload library %1: %2").arg(
                    QDir::toNativeSeparators(fileName)).arg(qt_error_string());
        return false;
    }
    errorString.clear();
    return true;
}

动态库符号调用过程

Qt-插件机制_第5张图片

 Qt-插件机制_第6张图片

其中:

typedef QObject *(*QtPluginInstanceFunction)();函数指针 返回值是QObject * 类型;

resolve 函数的定义:    QFunctionPointer resolve(const char *symbol);路径:C:\Qt\5.9.8\Src\qtbase\src\corelib\plugin\qlibrary.h

qt_plugin_instance 也就是QT_MOC_EXPORT_PLUGIN 宏定义中的 :

moc_CalPlugin.cpp文件中的QT_MOC_EXPORT_PLUGIN(CalPlugin, CalPlugin);展开:

Qt-插件机制_第7张图片

导出一个qt_plugin_instance 的接口,接口的实现是实例化一个插件类的对象

Qt-插件机制_第8张图片

 

QLibraryPrivate::resolve接口

QFunctionPointer QLibraryPrivate::resolve(const char *symbol)
{
    if (!pHnd)
        return 0;
    return resolve_sys(symbol);
}

 GetProcAddress 获取接口地址

QFunctionPointer QLibraryPrivate::resolve_sys(const char* symbol)
{
    FARPROC address = GetProcAddress(pHnd, symbol);
    if (!address) {
        errorString = QLibrary::tr("Cannot resolve symbol \"%1\" in %2: %3").arg(
            QString::fromLatin1(symbol)).arg(
                    QDir::toNativeSeparators(fileName)).arg(qt_error_string());
    } else {
        errorString.clear();
    }
    return QFunctionPointer(address);
}

你可能感兴趣的:(qt,qt,开发语言)