Qt5 插件机制实现原理

基于Qt插件机制扩展应用程序示例我们展示了怎么使用Qt的插件的机制实现一个插件和加载插件,在本文中我们通过源代码来展示Qt为了实现插件机制做了哪些工作。

定义接口类AppInterface

接口类的原生的代码如下:

class AppInterface
{
public:
    virtual ~AppInterface() {}
    // 插件名字
    virtual QString name() const = 0;
    // 插件返回的QAction列表
    virtual QList<QAction *> actions() const = 0;
};

Q_DECLARE_INTERFACE(AppInterface, "plugindemo_app_interface")

代码1 

这里面有一个最重要的宏Q_DECLARE_INTERFACE,把这个宏展开是这样的:

template <> inline const char *qobject_interface_iid<AppInterface *>()
{
    return "plugindemo_app_interface";
}

template <> inline AppInterface *qobject_cast<AppInterface *>(QObject *object)
{
    return reinterpret_cast<AppInterface *>((object ? object->qt_metacast("plugindemo_app_interface") : Q_NULLPTR));
}

template <> inline AppInterface *qobject_cast<AppInterface *>(const QObject *object)
{
    return reinterpret_cast<AppInterface *>((object ? const_cast<QObject *>(object)->qt_metacast("plugindemo_app_interface") : Q_NULLPTR));
}

代码2 

不难看出,这个宏展开后有3个接口,一个是获取iid,另外两个是转换函数,把QObject*或const QObject*转换成AppInterface*。

定义插件类Plugin1

#include <QObject>
#include "../app/appinterface.h"

#include "plugin1_global.h"

class PLUGIN1SHARED_EXPORT Plugin1 : public QObject, public AppInterface
{
    Q_OBJECT
    Q_INTERFACES(AppInterface)
    Q_PLUGIN_METADATA(IID "Plugin1")
public:
    Plugin1();

    // AppInterface interface
public:
    virtual QString name() const override;
    virtual QList<QAction *> actions() const override;
};

代码3 

在这里面最只要的是Q_INTERFACES和Q_PLUGIN_METADATA这两个宏,这两个宏在源码中没有实际的意义,只是moc工具(在QTDIR/bin)在生成moc_plugin1.cpp(一般情况下在编译输出目录里面)的时候会生成插件相关的代码。如果没有这两个宏,就不会生成 if (!strcmp(_clname, "plugindemo_app_interface"))
        return static_cast< AppInterface*>(this);和QT_MOC_EXPORT_PLUGIN(Plugin1, Plugin1)这两部分的代码。

moc_plugin1.cpp中部分代码如下:

void *Plugin1::qt_metacast(const char *_clname)
{
    if (!_clname) return nullptr;
    if (!strcmp(_clname, qt_meta_stringdata_Plugin1.stringdata0))
        return static_cast<void*>(this);
    if (!strcmp(_clname, "AppInterface"))
        return static_cast< AppInterface*>(this);
    if (!strcmp(_clname, "plugindemo_app_interface"))
        return static_cast< AppInterface*>(this);
    return QObject::qt_metacast(_clname);
}

int Plugin1::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
    _id = QObject::qt_metacall(_c, _id, _a);
    return _id;
}

……

QT_MOC_EXPORT_PLUGIN(Plugin1, Plugin1)

代码4

代码2中object->qt_metacast会调用代码4中qt_metacast接口。

插件的加载QPluginLoader

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

  1. QPluginLoader 检查插件是否与应用程序相同版本的 Qt 链接。
  2. QPluginLoader 提供对根组件对象 (instance()) 的直接访问,而不是强制您手动解析 C 函数。

以上是Qt帮助文档中关于QPluginLoader的一部分内容。

现在通过QPluginLoader的load接口来看看是怎么加载插件。

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();
}

代码5

通过代码5不难看出 ,如果是第一次调用load会调用d->loadPlugin();(什么是d指针可以看Qt-D指针和Q指针及使用),loadPlugin的代码如下:

bool QLibraryPrivate::loadPlugin()
{
    if (instance) {
        libraryUnloadCount.ref();
        return true;
    }
    if (pluginState == IsNotAPlugin)
        return false;
    if (load()) {
        instance = (QtPluginInstanceFunction)resolve("qt_plugin_instance");
        return instance;
    }
    if (qt_debug_component())
        qWarning() << "QLibraryPrivate::loadPlugin failed on" << fileName << ":" << errorString;
    pluginState = IsNotAPlugin;
    return false;
}

代码6 

在loadPlugin中调用了QLibraryPrivate::load(),而QLibraryPrivate::load()中调用QLibraryPrivate::load_sys(),我们直接看load_sys(),注意load_sys()的实现在qlibrary_win.cpp或qlibrary_unix.cpp里面,在windows上部分代码如下:

bool QLibraryPrivate::load_sys()
{

……

    for (const QString &attempt : qAsConst(attempts)) {
#ifndef Q_OS_WINRT
        pHnd = LoadLibrary(reinterpret_cast<const wchar_t*>(QDir::toNativeSeparators(attempt).utf16()));
#else // Q_OS_WINRT
        QString path = QDir::toNativeSeparators(QDir::current().relativeFilePath(attempt));
        pHnd = LoadPackagedLibrary(reinterpret_cast<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;
    }
……
}

代码7 

在unix下部分代码如下:

bool QLibraryPrivate::load_sys()
{
    ……

    bool retry = true;
    for(int prefix = 0; retry && !pHnd && prefix < prefixes.size(); prefix++) {
        for(int suffix = 0; retry && !pHnd && suffix < suffixes.size(); suffix++) {
            if (!prefixes.at(prefix).isEmpty() && name.startsWith(prefixes.at(prefix)))
                continue;
            if (path.isEmpty() && prefixes.at(prefix).contains(QLatin1Char('/')))
                continue;
            if (!suffixes.at(suffix).isEmpty() && name.endsWith(suffixes.at(suffix)))
                continue;
            if (loadHints & QLibrary::LoadArchiveMemberHint) {
                attempt = name;
                int lparen = attempt.indexOf(QLatin1Char('('));
                if (lparen == -1)
                    lparen = attempt.count();
                attempt = path + prefixes.at(prefix) + attempt.insert(lparen, suffixes.at(suffix));
            } else {
                attempt = path + prefixes.at(prefix) + name + suffixes.at(suffix);
            }
            pHnd = dlopen(QFile::encodeName(attempt), dlFlags);

            if (!pHnd && fileName.startsWith(QLatin1Char('/')) && QFile::exists(attempt)) {
                // We only want to continue if dlopen failed due to that the shared library did not exist.
                // However, we are only able to apply this check for absolute filenames (since they are
                // not influenced by the content of LD_LIBRARY_PATH, /etc/ld.so.cache, DT_RPATH etc...)
                // This is all because dlerror is flawed and cannot tell us the reason why it failed.
                retry = false;
            }
        }
    }
……
}

 代码8

通过代码7和代码8可以看到在不同平台上调用了不同的接口来打开插件,在windows上使用LoadLibrary或LoadPackagedLibrary,在unix上使用dlopen。

回到代码6,在load返回true的时候会给instance赋值,instance其实是一个函数指针:

typedef QObject *(*QtPluginInstanceFunction)();

QtPluginInstanceFunction instance;

代码9 

resolve会检索导出函数或变量,在windows上调用GetProcAddress,在unix下调用dlsym。哪qt_plugin_instance这个函数是在什么时候定义的?这个其实是Qt帮我们已经实现好了。回到代码3,在Plugin1中有Q_PLUGIN_METADATA这个宏,moc工具会通过这个宏在moc_plugin1.cpp中生成QT_MOC_EXPORT_PLUGIN(Plugin1, Plugin1),qt_plugin_instance就是在这里面定义的。QT_MOC_EXPORT_PLUGIN在windows上展开之后的代码如下(在unix类似):
extern "C" __declspec(dllexport) const char *qt_plugin_query_metadata()
{
    return reinterpret_cast<const char *>(qt_pluginMetaData);
}

extern "C" __declspec(dllexport) QObject *qt_plugin_instance()
{
     static QPointer<QObject> _instance;
     if (!_instance)
          _instance = new Plugin1;
     return _instance;
}

代码10

通过上面的代码可以看出QLibraryPrivate::instance保存的就是qt_plugin_instance函数的地址。这个函数功能就是创建Plugin1的一个实例。

我们再看看QPluginLoader::instance(),其代码如下:

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

代码11

通过这个QPluginLoader::instance()我们就可以拿到Plugin1的实例了。最后我们就可以通过

qobject_cast<AppInteface *>(object);(调用代码2)转换成我们需要的接口类。

总结

通过上面的代码展示,插件本质上还是一个动态库,只是在运行时动态加载。在各个平台上使用不同的系统api来记载动态库。加载完成之后会获取qt_plugin_instance函数的地址。这个函数为我们创建了插件实例。可以通过QPluginLoader::instance()来获取。得到QObject*之后,通过qobject_cast转换成我们需要的接口类,从而调用在该类中定义的接口。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Qt 插件框架是一种动态加载的机制,它允许在运行时向 Qt 应用程序中添加功能模块。Qt 插件框架的实现原理主要包括以下几个方面: 1. 插件分类 Qt 插件框架将插件分为两类:静态插件和动态插件。静态插件是编译到应用程序中的插件,它们在应用程序启动时被加载。动态插件是独立于应用程序的库,它们在运行时被加载。 2. 插件接口 Qt 插件框架定义了插件接口,插件必须实现这些接口才能被加载。插件接口通常是一个纯虚类,它们定义了插件的功能、属性和行为。 3. 插件元数据 Qt 插件框架使用元数据来描述插件,包括插件名、版本号、作者、描述、依赖关系等信息。插件元数据通常以 XML 格式存储。 4. 插件加载 Qt 插件框架使用 QPluginLoader 类来加载插件。QPluginLoader 会根据插件的元数据查找插件库,并调用插件接口的实现来完成插件的加载和初始化。 5. 插件管理 Qt 插件框架提供了插件管理器来管理插件插件管理器可以枚举、查找、加载和卸载插件,还可以管理插件间的依赖关系。 6. 插件通信 Qt 插件框架提供了信号和槽机制来实现插件之间的通信。插件可以通过信号和槽来传递消息和数据,实现插件之间的互操作。 总之,Qt 插件框架提供了一种灵活、可扩展的机制,使得应用程序可以在运行时加载和卸载插件,从而实现更丰富、更灵活的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小王哥编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值