利用C++实现插件系统

利用C++实现插件系统

插件机制能够方便地扩展已有应用程序的功能。用C++实现插件机制的基本思路是:应用程序提供接口,由用户或第三方实现这些接口,并编译出相应的动态链接库(即插件);将所有插件放到某个特定目录,应用程序运行时会自动搜索该目录,并动态加载目录中的插件。

应用程序提供接口

为了实现功能扩展,应用程序必须向插件提供接口。在base.h中定义一个抽象类Base作为接口:

#ifndef BASE_H_
#define BASE_H_

class Base {
public:
    virtual ~Base() = default;
    virtual void print(void) = 0;
    virtual double calc(double val) = 0;
};

#endif

实现插件

插件应该包含并实现应用程序提供的接口。在test1.h中定义Test1,让Test1继承并实现Base中提供的所有接口:

#ifndef TEST1_H_
#define TEST1_H_

#include <iostream>
#include <cmath>
#include "main.h"

class Test1 : public Base {
public:
    void print(void) {
        std::cout << "Hello Everybody! Test1!" << std::endl;
    }

    double calc(double val) {
        return sqrt(abs(val / 5 * 1.61));
    }
};

#endif

为了让应用程序动态加载插件,需要将插件编译为dll文件。在main.h中,插件声明两个导出函数:
- getObj:用于新建一个Test1对象并返回该对象的指针;
- getName:用于打印Test1相关信息。

#ifndef __MAIN_HPP_INCLUDED__
#define __MAIN_HPP_INCLUDED__

#define BUILD_DLL

#include <memory>
#include <string>
#include "base.h"

#ifdef BUILD_DLL
    #define DLLAPI __declspec(dllexport)
#else
    #define DLLAPI 
#endif // BUILD_DLL

// DLL导出函数

#ifdef __cplusplus
extern "C" {
#endif

    DLLAPI Base *getObj(void);
    DLLAPI const char* getName(void);

#ifdef __cplusplus
}
#endif

#endif // __MAIN_HPP_INCLUDED__

在main.cpp中,定义getObj和getName以及DLL入口DLLMain函数:

#include <iostream>
#include <cmath>
#include <windows.h>
#include "main.h"
#include "test1.h"

extern "C" Base* getObj(void) {
    return new Test1;
}

extern "C" const char* getName(void) {
    return "Test1:Maths";
}

extern "C" DLLAPI BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            // attach to process
            // return FALSE to fail DLL load
            break;

        case DLL_PROCESS_DETACH:
            // detach from process
            break;

        case DLL_THREAD_ATTACH:
            // attach to thread
            break;

        case DLL_THREAD_DETACH:
            // detach from thread
            break;
    }
    return TRUE; // succesful
}

至此,一个插件就实现了。可以按照此方式实现多个dll插件。

实现应用程序

现在来写一个简单的应用程序,功能是加载plugins目录中的所有dll插件,打印出dll相关信息,并调用在插件中实现的函数。

首先,在my_exception.h中实现一个自己的异常类,用于捕获wstring类型的异常消息:

#ifndef MY_EXCEPTION_H_
#define MY_EXCEPTION_H_

#include <string>
#include <stdexcept>

class MyException : public std::runtime_error {
public:
    MyException(const std::wstring &msg)
        : runtime_error("Error"), message_(msg) {
    }
    ~MyException() throw() {}
    std::wstring message() { return message_;}
private:
    std::wstring message_;
};

#endif

然后,在main.cpp中实现应用程序的相关功能:

#include <iostream>
#include <vector>
#include <memory>
#include <stdexcept>
#include <exception>
#include <windows.h>

#include "my_exception.h"
#include "base.h"

// 功能:加载plugins目录中的所有dll插件
// 参数:modules用于保存所有dll文件句柄,所有句柄最后会在main函数中被FreeLibrary()函数释放
// 返回:
std::vector<Base*> getPlugins(std::vector<HINSTANCE>& modules) {
    std::vector<Base*> ret;
    modules.clear();

    // 在plugins目录中查找dll文件并将文件信息保存在fileData中
    WIN32_FIND_DATA fileData;
    HANDLE fileHandle = FindFirstFile(L"plugins/*.dll", &fileData);

    if (fileHandle == (void*)ERROR_INVALID_HANDLE ||
        fileHandle == (void*)ERROR_FILE_NOT_FOUND) {
        // 没有找到任何dll文件,返回空vector
        return std::vector<Base*>();
    }

    // 循环加载plugins目录中的所有dll文件
    do {
        typedef Base* (__cdecl *ObjProc)(void);
        typedef const char* (__cdecl *NameProc)(void);

        // 将dll加载到当前进程的地址空间中
        HINSTANCE mod = LoadLibrary((L"./plugins/" + std::wstring(fileData.cFileName)).c_str());

        if (!mod) {
            // 加载dll失败,则释放所有已加载dll
            for (HINSTANCE hInst : modules)
                FreeLibrary(hInst);
            throw MyException(L"Library " + std::wstring(fileData.cFileName) + L" wasn't loaded successfully!");
        }
        // 从dll句柄中获取getObj和getName的函数地址
        ObjProc objFunc = (ObjProc) GetProcAddress(mod, "getObj");
        NameProc nameFunc = (NameProc) GetProcAddress(mod, "getName");

        if (!objFunc || !nameFunc)
            throw std::runtime_error("Invalid Plugin DLL: both 'getObj' and 'getName' must be defined.");

        ret.push_back(objFunc());  // 保存objFunc(即getObj)生成的对象指针
        modules.push_back(mod);  // 保存dll句柄

        std::clog << nameFunc() << " loaded!\n";
    } while (FindNextFile(fileHandle, &fileData));

    std::clog << std::endl;

    // 关闭文件句柄
    FindClose(fileHandle);
    return ret;
}

int main() {
    std::vector<HINSTANCE> modules;

    {
        std::vector<Base*> objs;

        // 加载插件
        try {
            objs = getPlugins(modules);
        } catch (const std::exception& e) {
            for (auto& x : objs) {
                delete x;
            }
            std::cerr << "Exception caught: " << e.what() << std::endl;
            return 1;
        }

        // 调用插件中对Base接口的实现
        for (auto& x : objs) {
            x->print();
            std::cout << "\t" << x->calc(10) << std::endl;
        }
        for (auto& x : objs) {
            delete x;
        }
    }

    // 释放所有dll
    for (HINSTANCE hInst : modules)
        FreeLibrary(hInst);


    return 0;
}

运行

将所有插件编译为dll文件并放入当前工程目录下的plugins目录中,启动应用程序,插件自动被加载到程序中,得到结果如下图所示:
运行结果

其他

事实上,Boost1.61.0 Beta中提供了一个新库:DLL,设计这个库的目的就是为了方便利用C++实现插件系统。关于DLL的使用方法,我将在另一篇文章中介绍。

参考文献

  1. Making a Plugin System, http://www.cplusplus.com/articles/48TbqMoL/
  • 17
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值