博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一种松耦合的分层插件系统的设计和实现
阅读量:5920 次
发布时间:2019-06-19

本文共 2513 字,大约阅读时间需要 8 分钟。

C++编写的桌面软件中传统的App+Dll的架构已经显现出的模块之间的强耦合、维护性差、升级不方便等诸多弊端,为此我进行了一些思考,有兴趣的朋友请看我以前的一个思考片断:《》,今天则具体介绍的我的一个具体设计和实现。

 

        我的目标是要去除功能模块的相互依赖,在模块调用必须采用动态加载的办法,但同时各个模块可以进行自由地进行通讯。我的设想大致是这样的:在动态加载各个模块后调用统一接口后生成插件对象,在某个插件对象都能通过标识符找到其它的插件对象,通过统一的接口将数据传给它们。系统架构图如下:

系统流程图如下:

 

           大家可以看到,要构建这样的系统需要解决两个关键问题:

 

1. 如何动态加载dll创建插件对象以及插件基类对象的接口设计

 

2. 插件模块之间的通讯问题

 

         如何解决这两个问题我已有了基本的思路。为了验证我的思路是可行的,我用VC2008新建了一个LayeredArchit解决方案,该解决方案实现这样一个简单功能:统计指定目录下的文件数,然后将统计结果保存到一个文本文件。这个解决方案由以下工程组成:

 

BaseObjLib   ——   底层库,用于定义底层对象和接口

 

StatFile    ——  统计文件插件

 

OutputFile ——  将统计结果输出到文本文件的插件

 

 CmdApp   ——  一个主调用程序(是一个控制台程序)。

 

 

         在每个插件模块都实现一个插件对象,继承基类插件对象IPluginObj,里都有这样一个统一的插件导出接口来生成插件对象:

[cpp]
 
  1. extern "C" __declspecdllexport )  IPluginObj* __stdcall GetPluginObj(const std::string &strID)  

            在动态加载插件库之后就调用这个接口。这样就解决了动态创建插件对象的问题。

           接下来我们解决插件之间的数据通讯问题。前面的设计已经实现了在任何插件模块里都能找到其它的插件对象。一个很自然的设计是在基类定义这样一个接口:

[cpp]
 
  1. virtual BOOL ProcessData(void* pData){ return FALSE;}  

 

      然后在派生插件对象里重载这个接口。这个设计基本可以实现数据通讯,但是有一个严重弊端是void*是类型不安全的,假如数据接收方将其类型转换错了程序就很可能崩溃。特别是当一个插件需要接受两种类型数据时。因此必须解决类型安全问题。

        之后我想到了利用微软的com模型的万能类型VARIANT,但是看了一篇文章《》就放弃了,感觉转换太繁琐了。后面惊喜地发现原来boost库早已实现了一个万能类型boost::any。boost::any真的很符合我的需求。具体是在基类插件对象类定义一个虚接口:


[cpp]
 
  1. virtual BOOL ProcessData(boost::any& anyData){ return FALSE;}  

       在主程序里找到统计插件对象,并将路径传递给它:

[cpp]
 
  1. IPluginObjPtr ptrStatFile = pPluginFactory->GetPlugin(_T("StatFile.dll"));  
  2. std::string strFolder = _T("D:\\dev-utility-tools\\doc");  
  3. boost::any anyString = strFolder;  
  4. ptrStatFile->ProcessData(anyString);  

// 统计模块的ProcessData函数的实现

[cpp]
 
  1. BOOL CStatFilePlugin::ProcessData( boost::any& anyData )  
  2. {  
  3.     if(anyData.type() == typeid(std::string))    
  4.     {  
  5.         std::string strPath  = boost::any_cast<std::string>(anyData);   
  6.         if(::GetFileAttributes(strPath.c_str())==-1)  
  7.         {  
  8.             std::cout<<"目录不存在"<<std::endl;  
  9.             return FALSE;  
  10.         }  
  11.         m_info.m_strPath = strPath;  
  12.         std::cout<<"现在正在统计的是:"<<m_info.m_strPath<<std::endl;  
  13.         std::cout<<"请等待!!!"<<std::endl;  
  14.           
  15.         std::string strTmpDir = m_info.m_strPath + TEXT("\\");  
  16.         // 统计文件数  
  17.         StatAllFileInFolder(strTmpDir);  
  18.   
  19.         std::cout<<"文件夹数:"<<m_info.m_FolderNum<<std::endl;  
  20.         std::cout<<"文件数:"<<m_info.m_FileNum<<std::endl;  
  21.   
  22. // 找到输出到文件插件对象,将统计结果传给它  
  23.         CPluginFactory* pPluginFactory = CPluginFactory::Instance();  
  24.         IPluginObjPtr ptrOuttoFile = pPluginFactory->GetPlugin(_T("OutputFile.dll"));  
  25.         boost::any anyInfo = m_info;  
  26.         ptrOuttoFile->ProcessData(anyInfo);  
  27.   
  28.         return TRUE;  
  29.     }  
  30.     return FALSE;  
  31. }  

        这样就安全地实现了插件间的数据通讯。需要指出的是需要通讯的插件间不相互依赖,但需要都包含的定义了传递数据结构体的头文件。具体请见源码下的DataExchange文件夹,该文件夹就是定义需要通讯的数据结构体。

程序的效果图如下:


         一个扩展设想是:消息本质上也是一种数据。消息的传递也可以参考数据的传递来实现。

 

        源码已上传至(具体见PluginArchit解决方案),采用GPL V2.0开源协议,有兴趣请下载看看。

 

参考文献:

 

1.     

 

 

from:

转载地址:http://bgnvx.baihongyu.com/

你可能感兴趣的文章
Java利用JDom解析和传递XML格式数据
查看>>
5分钟开通云服务
查看>>
接入qq登录功能出现的问题
查看>>
Squid
查看>>
值得推荐的技术网站和网址
查看>>
李嘉诚无锡演讲:骂到你成功
查看>>
我的友情链接
查看>>
Dev控件GridControl实现CheckBox列和ComBox列
查看>>
Linux red hat 安装ansible
查看>>
结对开发2-二维数组子矩阵和最大值
查看>>
SQL query practice with MySQL
查看>>
理解Javascript的Prototype
查看>>
memset函数
查看>>
Javascript异步数据的同步处理方法
查看>>
9. Palindrome Number(回文数)(leetcode)
查看>>
Dapper:The member of type SeoTKD cannot be used as a parameter Value
查看>>
音频管理控制器
查看>>
JAVA设计模式之责任链模式
查看>>
映射docker搭建
查看>>
飞凌OK6410开发板移植u-boot官方最新版u-boot-2012.10.tar.bz2
查看>>