天天看點

一種松耦合的分層插件系統的設計和實作

        我的目标是要去除功能子產品的互相依賴,在子產品調用必須采用動态加載的辦法,但同時各個子產品可以進行自由地進行通訊。我的設想大緻是這樣的:在動态加載各個子產品後調用統一接口後生成插件對象,在某個插件對象都能通過辨別符找到其它的插件對象,通過統一的接口将資料傳給它們。系統架構圖如下:

系統流程圖如下:

           大家可以看到,要建構這樣的系統需要解決兩個關鍵問題:

1. 如何動态加載dll建立插件對象以及插件基類對象的接口設計

2. 插件子產品之間的通訊問題

         如何解決這兩個問題我已有了基本的思路。為了驗證我的思路是可行的,我用VC2008建立了一個LayeredArchit解決方案,該解決方案實作這樣一個簡單功能:統計指定目錄下的檔案數,然後将統計結果儲存到一個文本檔案。這個解決方案由以下工程組成:

BaseObjLib   ——   底層庫,用于定義底層對象和接口

StatFile    ——  統計檔案插件

OutputFile ——  将統計結果輸出到文本檔案的插件

 CmdApp   ——  一個主調用程式(是一個控制台程式)。

         在每個插件子產品都實作一個插件對象,繼承基類插件對象IPluginObj,裡都有這樣一個統一的插件導出接口來生成插件對象:

extern "C" __declspec( dllexport )  IPluginObj* __stdcall GetPluginObj(const std::string &strID)  

            在動态加載插件庫之後就調用這個接口。這樣就解決了動态建立插件對象的問題。

           接下來我們解決插件之間的資料通訊問題。前面的設計已經實作了在任何插件子產品裡都能找到其它的插件對象。一個很自然的設計是在基類定義這樣一個接口:

virtual BOOL ProcessData(void* pData){ return FALSE;}  

      然後在派生插件對象裡重載這個接口。這個設計基本可以實作資料通訊,但是有一個嚴重弊端是void*是類型不安全的,假如資料接收方将其類型轉換錯了程式就很可能崩潰。特别是當一個插件需要接受兩種類型資料時。是以必須解決類型安全問題。

virtual BOOL ProcessData(boost::any& anyData){ return FALSE;}  

       在主程式裡找到統計插件對象,并将路徑傳遞給它:

IPluginObjPtr ptrStatFile = pPluginFactory->GetPlugin(_T("StatFile.dll"));  

std::string strFolder = _T("D:\\dev-utility-tools\\doc");  

boost::any anyString = strFolder;  

ptrStatFile->ProcessData(anyString);  

// 統計子產品的ProcessData函數的實作

BOOL CStatFilePlugin::ProcessData( boost::any& anyData )  

{  

    if(anyData.type() == typeid(std::string))    

    {  

        std::string strPath  = boost::any_cast<std::string>(anyData);   

        if(::GetFileAttributes(strPath.c_str())==-1)  

        {  

            std::cout<<"目錄不存在"<<std::endl;  

            return FALSE;  

        }  

        m_info.m_strPath = strPath;  

        std::cout<<"現在正在統計的是:"<<m_info.m_strPath<<std::endl;  

        std::cout<<"請等待!!!"<<std::endl;  

        std::string strTmpDir = m_info.m_strPath + TEXT("\\");  

        // 統計檔案數  

        StatAllFileInFolder(strTmpDir);  

        std::cout<<"檔案夾數:"<<m_info.m_FolderNum<<std::endl;  

        std::cout<<"檔案數:"<<m_info.m_FileNum<<std::endl;  

// 找到輸出到檔案插件對象,将統計結果傳給它  

        CPluginFactory* pPluginFactory = CPluginFactory::Instance();  

        IPluginObjPtr ptrOuttoFile = pPluginFactory->GetPlugin(_T("OutputFile.dll"));  

        boost::any anyInfo = m_info;  

        ptrOuttoFile->ProcessData(anyInfo);  

        return TRUE;  

    }  

    return FALSE;  

}  

        這樣就安全地實作了插件間的資料通訊。需要指出的是需要通訊的插件間不互相依賴,但需要都包含的定義了傳遞資料結構體的頭檔案。具體請見源碼下的DataExchange檔案夾,該檔案夾就是定義需要通訊的資料結構體。

程式的效果圖如下:

         一個擴充設想是:消息本質上也是一種資料。消息的傳遞也可以參考資料的傳遞來實作。

參考文獻: