插件机制能够方便地扩展已有应用程序的功能。用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插件。
实现应用程序
扫描二维码关注公众号,回复:
154611 查看本文章
现在来写一个简单的应用程序,功能是加载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目录中,启动应用程序,插件自动被加载到程序中,得到结果如下图所示: