【Qt】Qt动态加载库(运行时加载)实现

Qt 动态加载库

在 Qt 中动态加载库(运行时加载)主要通过 QLibrary(C 函数库)和 QPluginLoader(Qt 插件)实现。


一、核心机制对比
特性QLibraryQPluginLoader (继承自 QLibrary)
适用对象C 风格函数Qt 插件(QObject 派生类)
接口要求必须实现 Qt 插件接口
类实例化不支持自动实例化插件对象
元数据支持支持 Q_PLUGIN_METADATA
跨编译器兼容extern "C"依赖 Qt ABI 兼容性
典型用途加载第三方 C 库插件化架构扩展

二、QLibrary 加载 C 函数库(详细步骤)
1. 创建动态库(示例)

mylib.pro:

TEMPLATE = lib
TARGET = mylib
DEFINES += MYLIB_LIBRARY
SOURCES += mylib.cpp
HEADERS += mylib.h

mylib.h:

#pragma once
#include <QtCore/qglobal.h>

#ifdef MYLIB_LIBRARY
    #define MYLIB_EXPORT Q_DECL_EXPORT
#else
    #define MYLIB_EXPORT Q_DECL_IMPORT
#endif

#ifdef __cplusplus
extern "C" {
#endif

MYLIB_EXPORT int add(int a, int b);
MYLIB_EXPORT void logMessage(const char* msg);

#ifdef __cplusplus
}
#endif

mylib.cpp:

#include "mylib.h"
#include <QDebug>

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

void logMessage(const char* msg) {
    qDebug() << "[LIB]" << msg;
}
2. 应用程序动态加载
#include <QLibrary>
#include <QDebug>

// 定义函数指针类型
typedef int (*AddFunc)(int, int);
typedef void (*LogFunc)(const char*);

void loadAndUseLibrary() {
    // 1. 加载库(跨平台自动处理后缀)
    QLibrary lib("mylib");
    
    if (!lib.load()) {
        qCritical() << "Load error:" << lib.errorString();
        return;
    }

    // 2. 解析函数
    AddFunc addFunc = reinterpret_cast<AddFunc>(lib.resolve("add"));
    LogFunc logFunc = reinterpret_cast<LogFunc>(lib.resolve("logMessage"));

    if (!addFunc || !logFunc) {
        qCritical() << "Resolve error:" << lib.errorString();
        lib.unload();
        return;
    }

    // 3. 使用库功能
    logFunc("Library loaded successfully");
    qDebug() << "5 + 7 =" << addFunc(5, 7);

    // 4. 卸载(可选)
    lib.unload();
}

三、QPluginLoader 加载 Qt 插件(完整流程)
1. 定义插件接口

plugin_interface.h:

#include <QObject>
#include <QtPlugin>

class PluginInterface {
public:
    virtual ~PluginInterface() = default;
    virtual QString operation(const QString& input) = 0;
};

Q_DECLARE_INTERFACE(PluginInterface, "com.company.PluginInterface/1.0")
2. 实现插件

encrypt_plugin.h:

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

class EncryptPlugin : public QObject, public PluginInterface {
    Q_OBJECT
    Q_INTERFACES(PluginInterface)
    Q_PLUGIN_METADATA(IID "com.company.PluginInterface/1.0" FILE "encrypt.json")

public:
    QString operation(const QString& input) override {
        // 简单加密示例
        QString result;
        for (QChar ch : input) {
            result.append(QChar(ch.unicode() + 1));
        }
        return result;
    }
};

encrypt.json (元数据):

{
    "name": "Encryption Plugin",
    "version": "1.0",
    "author": "Your Company"
}

插件.pro 文件:

TEMPLATE = lib
CONFIG += plugin
TARGET = $$qtLibraryTarget(encrypt_plugin)
3. 应用程序加载插件
#include <QPluginLoader>
#include <QDir>
#include "plugin_interface.h"

void loadPlugin() {
    // 1. 定位插件路径
    QString pluginPath = QDir::currentPath() + "/plugins";
    QCoreApplication::addLibraryPath(pluginPath);

    // 2. 加载插件
    QPluginLoader loader("encrypt_plugin"); // 自动添加平台后缀
    
    if (!loader.load()) {
        qCritical() << "Plugin load error:" << loader.errorString();
        return;
    }

    // 3. 获取插件实例
    QObject* plugin = loader.instance();
    if (!plugin) {
        qCritical() << "Invalid plugin instance";
        return;
    }

    // 4. 转换为接口
    PluginInterface* encryptPlugin = qobject_cast<PluginInterface*>(plugin);
    if (!encryptPlugin) {
        qCritical() << "Failed to cast plugin";
        loader.unload();
        return;
    }

    // 5. 使用插件功能
    QString result = encryptPlugin->operation("Hello");
    qDebug() << "Encrypted:" << result;  // 输出 "Ifmmp"

    // 6. 卸载插件
    loader.unload();
}

四、关键技术细节
  1. 路径处理最佳实践
// 设置插件搜索路径
QString appDir = QCoreApplication::applicationDirPath();
QString pluginPath = appDir + "/plugins";
QCoreApplication::addLibraryPath(pluginPath);

// 跨平台加载
QLibrary lib(appDir + "/third_party/math_functions");
  1. 安全加载模式
QPluginLoader loader;
loader.setFileName("plugin_v2");
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint); // 强制立即解析符号

if (loader.load()) {
    // 验证插件元数据
    QJsonObject meta = loader.metaData().value("MetaData").toObject();
    if (meta.value("api_version").toInt() != 2) {
        qWarning() << "Incompatible plugin version";
        loader.unload();
    }
}
  1. 错误处理模板
auto handleError = [](QLibrary& lib) {
    if (lib.isLoaded()) lib.unload();
    qFatal("LIB ERROR: %s", qPrintable(lib.errorString()));
};

if (!lib.resolve("criticalFunction")) {
    handleError(lib);
}
  1. 跨平台注意事项
    • Windows:依赖项(DLL)需在 PATH 或应用目录
    • Linux:使用 ldd 检查依赖,patchelf 修改 RPATH
    • macOS:使用 otool -L 检查依赖,设置 @rpath

五、高级应用场景
  1. 热插拔插件系统
// 监视插件目录
QFileSystemWatcher watcher;
watcher.addPath(pluginDir);

connect(&watcher, &QFileSystemWatcher::directoryChanged, [=] {
    // 动态卸载/重新加载插件
});
  1. 版本化接口
// V2 接口扩展
class PluginInterfaceV2 : public PluginInterface {
public:
    virtual QString advancedOp(const QVariantMap& params) = 0;
};

// 加载时检查元数据版本
QJsonObject meta = loader.metaData();
int version = meta.value("version").toInt();
  1. 依赖注入
// 主程序提供服务接口
class ServiceProvider {
public:
    virtual void registerService(QObject* service) = 0;
};

// 插件获取服务
ServiceProvider* provider = qobject_cast<ServiceProvider*>(parentObject);

六、常见问题解决方案
  1. 加载失败原因

    • 依赖缺失:Windows 用 Dependencies,Linux 用 ldd
    • ABI 不兼容:确保所有组件使用相同编译器(如 MSVC2019)
    • 路径错误:使用 QDir::toNativeSeparators() 处理路径
  2. 符号解析失败

    • C++ 函数:使用 Q_DECL_EXPORT/Q_DECL_IMPORT
    • 名称修饰extern "C" + __attribute__((visibility("default"))) (GCC)
    • 检查符号:Linux 用 nm -D --demangle lib.so
  3. 插件初始化崩溃

    • 静态对象初始化顺序问题
    • QCoreApplication 实例化后加载插件
    • 避免全局静态对象依赖
  4. 跨平台构建技巧

# 自动处理扩展名
linux: TARGET = lib$${TARGET}.so
win32: TARGET = $${TARGET}.dll
macx: TARGET = lib$${TARGET}.dylib

# 设置 RPATH (Linux/macOS)
QMAKE_RPATHDIR += $$OUT_PWD/libs

最佳实践总结

  1. 接口设计原则

    • 使用纯虚接口类
    • 接口/实现分离(.h 仅包含接口)
    • 版本化元数据(Q_PLUGIN_METADATA
  2. 资源管理

    // RAII 式加载
    std::unique_ptr<QPluginLoader> loader(new QPluginLoader("plugin"));
    QScopedPointer<PluginInterface> plugin(qobject_cast<PluginInterface*>(loader->instance()));
    
  3. 安全措施

    • 插件签名验证
    • 沙箱执行(QProcess 隔离)
    • 输入/输出数据校验
  4. 调试技巧

    # Linux 调试加载
    LD_DEBUG=libs ./myapp
    
    # Windows 依赖检查
    windeployqt --list plugins myapp.exe
    

通过动态加载机制,Qt 应用程序可实现:

  • 模块化架构(按需加载)
  • 运行时扩展(插件市场)
  • 热更新能力(替换插件无需重启)
  • 跨平台组件复用
### RK3588平台NPU调用方法 #### 创建和初始化NPU环境 为了在RK3588平台上成功调用NPU进行神经网络推理或加速,首先需要确保设备已正确配置并加载了相应的驱动程序。Rockchip的官方固件通常已经预装了RKNPU驱动[^3]。 一旦确认硬件准备就绪,可以通过以下方式创建和初始化NPU环境: ```cpp #include "rknn_api.h" // 初始化模型路径和其他参数 const char* model_path = "./model.rknn"; int ret; rknn_context ctx; ret = rknn_init(&ctx, model_path, 0, 0, NULL); if (ret < 0) { printf("Failed to initialize rknn context\n"); } ``` 这段代码展示了如何使用`rknn_api.h`来初始化一个RKNN上下文对象,这一步骤对于后续的操作至关重要[^2]。 #### 加载和编译模型 接下来,在实际运行之前还需要加载预先训练好的神经网络模型文件(通常是`.rknn`格式)。此过程涉及读取模型二进制数据,并将其传递给RKNN API以便内部处理和优化。 ```cpp // 假设模型已经被转换成 .rknn 文件格式 char *model_data; // 模型的数据指针 size_t model_size; // 模型大小 FILE *fp = fopen(model_path, "rb+"); fseek(fp, 0L, SEEK_END); model_size = ftell(fp); rewind(fp); model_data = (char *)malloc(sizeof(char)*model_size); fread(model_data, sizeof(unsigned char), model_size, fp); fclose(fp); // 将模型数据传入RKNN API ret = rknn_load_rknn(ctx, &model_data, &model_size); free(model_data); if(ret != 0){ printf("Load Model Failed!\n"); } else{ printf("Model Loaded Successfully.\n"); } ``` 这里说明了从磁盘读取模型文件的具体操作流程,并通过API函数将这些信息提交给了底层框架去解析和设置好用于推断所需的资源[^1]。 #### 执行前向传播计算 当一切准备工作完成后就可以开始真正的预测工作——即让NPU执行一次完整的前向传播运算。这个阶段主要是构建输入张量、启动异步任务以及收集输出结果。 ```cpp float input_tensor[INPUT_SIZE]; // 输入特征图数组 float output_tensors[MAX_OUTPUTS][OUTPUT_SIZE]; // 输出特征图数组 struct rknn_input inputs[] = {{input_tensor}}; struct rknn_output outputs[MAX_OUTPUTS]; for(int i=0;i<NUM_ITERATIONS;++i){ memset(inputs, 0 ,sizeof(struct rknn_input)); memcpy(input_tensor, inputData[i], INPUT_SIZE*sizeof(float)); // 启动推理任务 ret = rknn_run(ctx, nullptr); if(ret!=0){ printf("Inference failed at iteration %d", i); break; } // 获取输出结果 for(size_t j=0;j<num_outputs;++j){ struct rknn_output& out = outputs[j]; size_t bufSize = OUTPUT_SIZE * sizeof(float); void* buffer = malloc(bufSize); ret = rknn_get_output(ctx, j, &out.datatype, &buffer, &bufSize, false); if(!ret && buffer){ memcpy(output_tensors[j], buffer, bufSize); free(buffer); } } } printf("All iterations completed successfully."); ``` 上述片段体现了典型的基于RKNN SDK的应用场景:先准备好待测样本作为输入;接着触发内核中的计算逻辑;最后获取到经过变换后的响应值供下一步分析所用[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

晴雨日记

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

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

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

打赏作者

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

抵扣说明:

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

余额充值