利用C++实现插件系统

来源:互联网 发布:分期贷款的软件 编辑:程序博客网 时间:2024/05/16 05:28

利用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 __cplusplusextern "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/
0 0
原创粉丝点击