C++开发:插件架构思想

【原文链接】Plugin Architecture – Nuclex Games Blog   -- 基于AI

插件架构


       本文将指导您设计一个简单而强大的插件架构。这需要您具备一定的C++经验,熟悉动态库(.dll、.so)的使用,并理解面向对象编程的基本概念(如接口和工厂模式)。但在开始之前,让我们先看看插件的优势以及为什么要使用它们:

代码清晰度与一致性提升
插件将第三方库及其他团队成员编写的代码封装为明确定义的接口,使您的代码获得高度一致的交互方式。代码中也不再充斥转换例程(如ErrorCodeToException())或特定库的自定义逻辑。

项目模块化增强
代码被清晰地拆分为独立模块,减少项目中需同时处理的文件数量。这种解耦过程创建的组件更易复用,因为它们不会与项目特有的复杂逻辑纠缠。

缩短编译时间
由于实现细节被隐藏,编译器无需解析外部库的头文件来声明内部使用这些库的类,从而显著减少编译时间(您知道windows.h包含约500KB的代码吗?)。

组件替换与扩展
发布用户补丁时,通常只需更新单个插件而非整个安装包。例如,游戏的新渲染器或扩展包的新单位类型(包括用户制作的模组)可通过简单提供一组插件实现。

闭源项目中使用GPL代码
若在代码中直接使用GPL协议的内容,需公开全部源代码。但若将其封装为插件并作为独立进程运行(参见GPL FAQ),则只需公开插件源码。

个人观点
就个人而言,我使用插件并非因为其“酷炫”,也不是为了频繁向用户推送补丁,甚至不是强制自己编写模块化代码。我认为这是组织大型项目的最佳方式——依赖关系大幅减少,且能专注于替换特定系统,而非因代码库重构而阻塞整个项目或团队进度。


引言

插件系统原理
在常规应用中,若需实现特定功能,通常有两种选择:自行编写代码或寻找现有库。但需求变化时,您不得不重写代码或更换库,这两种选择均可能导致依赖该代码或库的其他部分需要同步修改。

插件系统提供了第三种选择:将项目中不希望绑定具体实现的组件(如基于OpenGL或Direct3D的渲染器)提取到动态库中,并通过主代码库定义的接口解耦。插件则提供这些接口的具体实现。插件的关键特性在于加载方式:应用程序不直接链接这些库,而是通过扫描目录动态加载发现的插件,插件随后以统一方式将自己注册到应用中。

常见误区
许多C++程序员在设计插件系统时,会为每个动态库添加如下函数:

PluginBase *createInstance(const char *);  

然后通过名称请求对象,直到某个插件返回实例。更聪明的设计可能让插件自行注册到引擎中:

void dllStartPlugin(PluginManager &pm);  
void dllStopPlugin(PluginManager &pm);  

但这些设计存在严重问题:

  1. 需使用dynamic_cast<>reinterpret_cast<>转换插件返回的对象,类型安全性无法保证。

  2. 难以支持同一接口的多实现。若插件以不同名称注册(如Direct3DRendererOpenGLRenderer),引擎无法自动识别可用选项。

  3. 若在框架中实现此类系统,可能迫使应用层继承其问题,并要求插件开发者同时依赖引擎和应用的头文件,增加版本冲突风险。


独立工厂模式

引擎定义的接口(如图形输出)应由插件实现。因此,我们让插件向引擎注册其实现类的工厂:

template<typename Interface>  
class Factory {  
  virtual Interface *create() = 0;  
};  

class Renderer {  
  virtual void beginScene() = 0;  
  virtual void endScene() = 0;  
};  
typedef Factory<Renderer> RendererFactory;  

此设计消除了类型转换,且通过工厂模板避免了冗余代码。


方案一:插件管理器

插件通过插件管理器注册工厂,引擎通过管理器使用插件:

class PluginManager {  
  void registerRenderer(std::auto_ptr<RendererFactory> RF);  
  void registerSceneManager(std::auto_ptr<SceneManagerFactory> SMF);  
};  

插件需导出注册函数:

void registerPlugin(PluginManager &PM);  

管理器可扫描目录加载所有动态库,或通过XML列表指定插件。


方案二:深度集成

另一种方式是将引擎拆分为多个子系统,由核心管理:

class Kernel {  
  StorageServer &getStorageServer() const;  
  GraphicsServer &getGraphicsServer() const;  
};  

class StorageServer {  
  void addArchiveReader(std::auto_ptr<ArchiveReader> AL);  
  std::auto_ptr<Archive> openArchive(const std::string &sFilename);  
};  

class GraphicsServer {  
  void addGraphicsDriver(std::auto_ptr<GraphicsDriver> AF);  
  size_t getDriverCount() const;  
  GraphicsDriver &getDriver(size_t Index);  
};  

子系统可内置默认实现(如自定义包格式的ArchiveReader),同时允许插件扩展功能(如支持.zip/.rar)。


版本控制

插件与主程序版本不匹配可能导致崩溃。解决方案是在核心系统中定义版本常量,插件通过函数返回该值:

// 核心系统  
#define MyEngineVersion 1;  

// 插件  
extern int getExpectedEngineVersion() {  
  return MyEngineVersion;  
}  

若引擎版本更新而插件未重新编译,管理器可拒绝加载旧版插件。


总结

本文介绍的类型安全、灵活的插件架构既适用于现有代码库,也适合新项目。

下载
Nuclex.PluginArchitecture.Example.7z (10.1 KiB)
感谢Felipe Bulat贡献的Linux移植版本,使示例支持Windows和Linux平台。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值