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++插件管理类(下)
相关文章
|
11月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
9月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
368 12
|
7月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
197 0
|
7月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
313 0
|
10月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
196 16
|
10月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
10月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
10月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
535 6
|
10月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
10月前
|
存储 编译器 C++
【c++】多态(多态的概念及实现、虚函数重写、纯虚函数和抽象类、虚函数表、多态的实现过程)
本文介绍了面向对象编程中的多态特性,涵盖其概念、实现条件及原理。多态指“一个接口,多种实现”,通过基类指针或引用来调用不同派生类的重写虚函数,实现运行时多态。文中详细解释了虚函数、虚函数表(vtable)、纯虚函数与抽象类的概念,并通过代码示例展示了多态的具体应用。此外,还讨论了动态绑定和静态绑定的区别,帮助读者深入理解多态机制。最后总结了多态在编程中的重要性和应用场景。 文章结构清晰,从基础到深入,适合初学者和有一定基础的开发者学习。如果你觉得内容有帮助,请点赞支持。 ❤❤❤
1241 0