利用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
#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:
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
至此,一个插件就实现了。可以按照此方式实现多个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"
std::vector<Base*> getPlugins(std::vector<HINSTANCE>& modules) {
std::vector<Base*> ret;
modules.clear();
WIN32_FIND_DATA fileData;
HANDLE fileHandle = FindFirstFile(L"plugins/*.dll", &fileData);
if (fileHandle == (void*)ERROR_INVALID_HANDLE ||
fileHandle == (void*)ERROR_FILE_NOT_FOUND) {
return std::vector<Base*>();
}
do {
typedef Base* (__cdecl *ObjProc)(void);
typedef const char* (__cdecl *NameProc)(void);
HINSTANCE mod = LoadLibrary((L"./plugins/" + std::wstring(fileData.cFileName)).c_str());
if (!mod) {
for (HINSTANCE hInst : modules)
FreeLibrary(hInst);
throw MyException(L"Library " + std::wstring(fileData.cFileName) + L" wasn't loaded successfully!");
}
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());
modules.push_back(mod);
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;
}
for (auto& x : objs) {
x->print();
std::cout << "\t" << x->calc(10) << std::endl;
}
for (auto& x : objs) {
delete x;
}
}
for (HINSTANCE hInst : modules)
FreeLibrary(hInst);
return 0;
}
运行
将所有插件编译为dll文件并放入当前工程目录下的plugins目录中,启动应用程序,插件自动被加载到程序中,得到结果如下图所示:
其他
事实上,Boost1.61.0 Beta中提供了一个新库:DLL,设计这个库的目的就是为了方便利用C++实现插件系统。
参考文献
- Making a Plugin System, http://www.cplusplus.com/articles/48TbqMoL/
- 转载自 http://blog.csdn.net/qtyl1988/article/details/51176151