C++插件管理类(上)——概念背景小Dome

简介: C++插件管理类(上)——概念背景小Dome

@[toc]

一、插件管理类的好处

C++插件化管理库的好处有以下几点:

1.功能扩展: 插件化管理库可以实现动态加载和卸载插件,从而实现功能的动态扩展。通过插件化管理库,可以方便地添加、更新和删除插件,从而实现系统的灵活性和可扩展性。

2. 代码解耦: 插件化管理库可以将系统的核心功能与插件分离,从而实现代码的解耦。通过插件化管理库,可以将不同功能的代码分别实现在不同的插件中,减少代码之间的耦合度,提高代码的可读性和可维护性。

3. 系统稳定性: 插件化管理库可以实现插件的隔离和安全性控制,从而保证系统的稳定性和安全性。通过插件化管理库,可以对插件进行权限控制和安全检查,防止插件对系统的破坏和攻击,保证系统的稳定性和安全性。

4. 跨平台兼容: 插件化管理库可以实现跨平台兼容,从而提高代码的可移植性。通过插件化管理库,可以实现在不同平台上加载和卸载插件,从而实现代码的跨平台兼容。

5. 代码重用: 插件化管理库可以实现代码的重用,从而提高代码的效率和质量。通过插件化管理库,可以将相同的代码实现在不同的插件中,从而减少代码的重复,提高代码的效率和质量。

综上所述,C++插件化管理库可以实现系统的灵活性、可扩展性、稳定性、安全性、跨平台兼容性和代码重用性,是一种优秀的软件设计模式。

二、疑问:动态库已经是运行时候加载了,为啥还需要插件类来管理库?

虽然动态库在运行时加载,但是在程序中需要使用动态库中的函数时,仍需要使用动态库中的函数符号(symbol)来进行调用。而动态库中的函数符号是在编译时确定的,因此需要在编译时将动态库中的函数符号与程序中的函数进行绑定。

使用C++插件类来管理动态库,可以将动态库中的函数封装为插件接口,从而方便程序在运行时进行动态加载和卸载,同时也可以提供一些统一的管理和调用接口,使得程序的可扩展性和可维护性更好。

例如,在插件管理类中可以实现动态加载和卸载插件、查找插件、调用插件接口等功能,从而方便程序在运行时根据需要加载或卸载插件,同时也可以对插件进行统一的管理和调用。这样,程序就可以更加灵活地进行功能扩展和升级,而不需要重新编译整个程序。

三、如何实现一个插件管理类?

实现一个C++插件管理类的一般步骤如下:

1. 定义插件管理类: 定义一个类,用于管理插件的加载、卸载、查找等操作。

2. 定义插件类: 定义一个类,用于描述插件的基本信息,如名称、版本、作者等。

3. 定义插件接口: 定义一个接口类,用于规定插件需要实现的功能接口,如初始化、执行、销毁等。

4. 加载插件: 在插件管理类中实现加载插件的函数,通过动态链接库(DLL)加载插件,并将插件信息和接口保存在插件管理类中。

5. 卸载插件: 在插件管理类中实现卸载插件的函数,通过释放动态链接库(DLL)和插件信息和接口的内存空间实现卸载插件。

6. 查找插件: 在插件管理类中实现查找插件的函数,通过遍历已加载的插件列表,查找指定名称或版本的插件。

7. 调用插件接口: 在插件管理类中实现调用插件接口的函数,通过查找指定名称或版本的插件,调用其实现的功能接口。

8. 销毁插件管理类: 在程序退出时,通过插件管理类的析构函数,卸载所有已加载的插件,并释放插件管理类的内存空间。

需要注意的是,插件管理类的实现需要考虑插件的兼容性、安全性和性能等方面的问题,同时需要遵循C++的面向对象设计原则,提高代码的可读性可维护性

四、插件类实例

C++插件类可以通过动态链接库中导出的函数来实现插件的加载和卸载,同时也可以通过函数指针来调用插件提供的接口。

具体来说,可以在插件类中定义一个函数指针类型,用于指向插件中导出的函数。然后,在加载插件时,可以使用动态链接库中的函数来获取插件中导出函数的地址,并将其赋值给函数指针。这样,就可以通过函数指针来调用插件中的函数。

以下是一个简单的C++插件管理类,可以在WindowsLinux平台上使用。它可以用于加载和卸载动态链接库,并提供一个统一的接口来访问插件中的函数。

// PluginManager.h
#ifndef PLUGIN_MANAGER_H
#define PLUGIN_MANAGER_H

#include <string>
#include <vector>

#ifdef _WIN32
#include <Windows.h>
#else
#include <dlfcn.h>
#endif

class PluginManager
{
   
public:
    PluginManager();
    ~PluginManager();

    bool loadPlugin(const std::string& filename);
    bool unloadPlugin(const std::string& filename);

    template<typename T>
    T* getFunction(const std::string& filename, const std::string& functionName)
    {
   
        PluginHandle handle = getPluginHandle(filename);
        if (handle == NULL)
        {
   
            return NULL;
        }

#ifdef _WIN32
        FARPROC proc = GetProcAddress(handle, functionName.c_str());
#else
        void* proc = dlsym(handle, functionName.c_str());
#endif

        if (proc == NULL)
        {
   
            return NULL;
        }

        return reinterpret_cast<T*>(proc);
    }

private:
    typedef void* PluginHandle;

    struct PluginInfo
    {
   
        std::string filename;
        PluginHandle handle;
    };

    std::vector<PluginInfo> plugins_;

    PluginHandle getPluginHandle(const std::string& filename);
};

#endif

// PluginManager.cpp
#include "PluginManager.h"

PluginManager::PluginManager()
{
   
}

PluginManager::~PluginManager()
{
   
    for (auto& plugin : plugins_)
    {
   
#ifdef _WIN32
        FreeLibrary(plugin.handle);
#else
        dlclose(plugin.handle);
#endif
    }
}

bool PluginManager::loadPlugin(const std::string& filename)
{
   
    PluginHandle handle = NULL;

#ifdef _WIN32
    handle = LoadLibrary(filename.c_str());
#else
    handle = dlopen(filename.c_str(), RTLD_LAZY);
#endif

    if (handle == NULL)
    {
   
        return false;
    }

    PluginInfo info;
    info.filename = filename;
    info.handle = handle;

    plugins_.push_back(info);

    return true;
}

bool PluginManager::unloadPlugin(const std::string& filename)
{
   
    for (auto it = plugins_.begin(); it != plugins_.end(); ++it)
    {
   
        if (it->filename == filename)
        {
   
#ifdef _WIN32
            FreeLibrary(it->handle);
#else
            dlclose(it->handle);
#endif
            plugins_.erase(it);
            return true;
        }
    }

    return false;
}

PluginManager::PluginHandle PluginManager::getPluginHandle(const std::string& filename)
{
   
    for (auto& plugin : plugins_)
    {
   
        if (plugin.filename == filename)
        {
   
            return plugin.handle;
        }
    }

    PluginHandle handle = NULL;

#ifdef _WIN32
    handle = LoadLibrary(filename.c_str());
#else
    handle = dlopen(filename.c_str(), RTLD_LAZY);
#endif

    if (handle == NULL)
    {
   
        return NULL;
    }

    PluginInfo info;
    info.filename = filename;
    info.handle = handle;

    plugins_.push_back(info);

    return handle;
}

在这个示例中,PluginManager类封装了动态链接库的加载和卸载过程,并提供了一个模板函数getFunction,用于获取动态链接库中的函数指针。在Windows平台上,使用LoadLibraryGetProcAddress函数加载和获取函数指针;在Linux平台上,使用dlopendlsym函数加载和获取函数指针。

使用示例:

// main.cpp
#include <iostream>
#include "PluginManager.h"

typedef int (*myFunction)(int);

int main()
{
   
    PluginManager pluginManager;
    pluginManager.loadPlugin("MyPlugin.dll"); // Windows平台下的插件
    pluginManager.loadPlugin("libMyPlugin.so"); // Linux平台下的插件

    myFunction myFunctionPtr = pluginManager.getFunction<myFunction>("MyPlugin.dll", "myFunction");
    if (myFunctionPtr != NULL)
    {
   
        int result = myFunctionPtr(42);
        std::cout << "Result: " << result << std::endl;
    }

    myFunctionPtr = pluginManager.getFunction<myFunction>("libMyPlugin.so", "myFunction");
    if (myFunctionPtr != NULL)
    {
   
        int result = myFunctionPtr(42);
        std::cout << "Result: " << result << std::endl;
    }

    pluginManager.unloadPlugin("MyPlugin.dll");
    pluginManager.unloadPlugin("libMyPlugin.so");

    return 0;
}

五、提供一个动态函数库

以下是一个简单的C++动态库,其中包含两个函数:

// MyLibrary.h
#ifndef MYLIBRARY_H
#define MYLIBRARY_H

#ifdef MYLIBRARY_EXPORTS
#define MYLIBRARY_API __declspec(dllexport)
#else
#define MYLIBRARY_API __declspec(dllimport)
#endif

MYLIBRARY_API int add(int a, int b);
MYLIBRARY_API int subtract(int a, int b);

#endif

// MyLibrary.cpp
#include "MyLibrary.h"

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

MYLIBRARY_API int subtract(int a, int b)
{
   
    return a - b;
}

这个动态库包含两个函数,一个是加法函数add,一个是减法函数subtract。在头文件MyLibrary.h中,使用了预处理指令#ifdef#ifndef来判断是否在动态库中导出函数,以及防止头文件被重复包含。在函数定义前,使用了MYLIBRARY_API宏,它根据MYLIBRARY_EXPORTS宏的定义来决定是导出函数还是导入函数。

Visual Studio中,可以使用以下步骤来编译这个动态库:

  1. 创建一个新的Win32控制台应用程序项目。

  2. 在项目中添加MyLibrary.hMyLibrary.cpp文件。

  3. 在项目属性中,选择“配置属性”->“常规”,将“配置类型”设置为“动态库(.dll)”。

  4. MyLibrary.cpp文件中,使用“生成解决方案”命令来编译动态库。

编译完成后,就可以在输出目录中找到生成的动态库文件MyLibrary.dll。可以使用dumpbin命令来查看动态库中的函数列表。

六、使用GetProcAddress函数获取动态库中的函数地址为空的原因

GetProcAddress函数用于在动态链接库中获取一个导出函数的地址。如果GetProcAddress函数返回NULL,表示无法找到指定的函数。以下是可能导致GetProcAddress函数返回NULL的原因:

1. 函数名错误:GetProcAddress函数需要传入正确的函数名,如果函数名错误或者大小写不匹配,就无法找到指定的函数。在使用GetProcAddress函数时,建议直接从动态库的导出函数列表中复制函数名,避免出现拼写错误。

2. 函数未导出:GetProcAddress函数只能获取动态库中已经导出的函数的地址。如果函数没有被导出,就无法使用GetProcAddress函数来获取它的地址。在动态库中导出函数时,需要使用__declspec(dllexport)或者.def文件来明确指定导出的函数。

3. 调用方式错误:GetProcAddress函数返回的是函数指针,如果调用方式不正确,就会导致程序崩溃或者出现其他错误。在调用GetProcAddress函数获取函数地址后,需要使用正确的调用方式来调用函数,包括参数个数、参数类型、返回值类型等。

4. 动态库加载失败: 如果动态库加载失败,就无法使用GetProcAddress函数来获取函数地址。在使用LoadLibrary函数加载动态库时,需要确保动态库文件存在、路径正确、权限足够等。如果动态库加载失败,可以使用GetLastError函数来获取错误码和错误信息,以便定位问题。

5. 平台不匹配GetProcAddress函数只能在同一平台上加载和调用动态库。如果动态库和应用程序在不同的平台上编译,或者使用不同的编译器或者编译选项,就无法正确加载和调用动态库。在使用GetProcAddress函数时,需要确保动态库和应用程序在同一平台上编译,并且使用相同的编译器和编译选项。

七、动态库如何给插件类提供自己的各类信息?

动态库可以提供插件类自己的各类信息,通常的做法是在动态库中定义一个导出函数,用于返回插件类的信息。以下是一个简单的示例:

// MyLibrary.h
#ifndef MYLIBRARY_H
#define MYLIBRARY_H

#ifdef MYLIBRARY_EXPORTS
#define MYLIBRARY_API __declspec(dllexport)
#else
#define MYLIBRARY_API __declspec(dllimport)
#endif

class PluginInfo
{
   
public:
    std::string name;
    std::string version;
    std::string author;
    std::string description;
};

MYLIBRARY_API PluginInfo getPluginInfo();

#endif

// MyLibrary.cpp
#include "MyLibrary.h"

MYLIBRARY_API PluginInfo getPluginInfo()
{
   
    PluginInfo info;
    info.name = "MyPlugin";
    info.version = "1.0";
    info.author = "John Doe";
    info.description = "A demo plugin.";
    return info;
}

在这个示例中,定义了一个PluginInfo类,用于存储插件类的各类信息。然后在动态库中定义了一个导出函数getPluginInfo,用于返回插件类的信息。在使用这个动态库的应用程序中,可以使用GetProcAddress函数获取getPluginInfo函数的地址,并调用它来获取插件类的信息。

// main.cpp
#include <Windows.h>
#include <iostream>
#include "MyLibrary.h"

int main()
{
   
    HMODULE hModule = LoadLibrary(TEXT("MyLibrary.dll"));
    if (hModule == NULL)
    {
   
        std::cout << "Failed to load library." << std::endl;
        return 1;
    }

    typedef PluginInfo (*getPluginInfo_func)();

    getPluginInfo_func getPluginInfo = (getPluginInfo_func)GetProcAddress(hModule, "getPluginInfo");

    if (getPluginInfo == NULL)
    {
   
        std::cout << "Failed to get function address." << std::endl;
        return 1;
    }

    PluginInfo info = getPluginInfo();

    std::cout << "Plugin name: " << info.name << std::endl;
    std::cout << "Plugin version: " << info.version << std::endl;
    std::cout << "Plugin author: " << info.author << std::endl;
    std::cout << "Plugin description: " << info.description << std::endl;

    FreeLibrary(hModule);
    return 0;
}

在这个示例中,使用GetProcAddress函数获取getPluginInfo函数的地址,并调用它来获取插件类的信息。然后输出各类信息。

八、 具体实例

请参考C++插件管理类(下)
相关文章
|
2天前
|
测试技术 C++
C++|运算符重载(3)|日期类的计算
C++|运算符重载(3)|日期类的计算
|
3天前
|
C语言 C++ 容器
C++ string类
C++ string类
9 0
|
4天前
|
C++ Linux
|
4天前
|
编译器 C++
【C++】继续学习 string类 吧
首先不得不说的是由于历史原因,string的接口多达130多个,简直冗杂… 所以学习过程中,我们只需要选取常用的,好用的来进行使用即可(有种垃圾堆里翻美食的感觉)
9 1
|
4天前
|
算法 安全 程序员
【C++】STL学习之旅——初识STL,认识string类
现在我正式开始学习STL,这让我期待好久了,一想到不用手撕链表,手搓堆栈,心里非常爽
16 0
|
4天前
|
存储 安全 测试技术
【C++】string学习 — 手搓string类项目
C++ 的 string 类是 C++ 标准库中提供的一个用于处理字符串的类。它在 C++ 的历史中扮演了重要的角色,为字符串处理提供了更加方便、高效的方法。
18 0
【C++】string学习 — 手搓string类项目
|
4天前
|
Java C++ Python
【C++从练气到飞升】06---重识类和对象(二)
【C++从练气到飞升】06---重识类和对象(二)
|
4天前
|
编译器 C++
【C++从练气到飞升】06---重识类和对象(一)
【C++从练气到飞升】06---重识类和对象(一)
|
4天前
|
存储 编译器 C语言
【C++从练气到飞升】02---初识类与对象
【C++从练气到飞升】02---初识类与对象