多人開發的基礎---元件化程式設計,仿COM篇
引言:
在大型專案的開發中,隨著開發進度的進行,我們經常碰到模組之間耦合度太高的問題:由於開發人員經常要在別的模組中呼叫自己實現的功能,經常隨意在某個函式中隨意新增呼叫程式碼,造成了被修改的那個函式體過長,邏輯混亂。另一個問題是隨意包含標頭檔案:開發人員在開發中經常為了要使用某些類的功能而包含引用類的標頭檔案造成類之間的耦合度太高,被包含類的標頭檔案一處輕微修改經常就會引起整個程式大規模的編譯和連結,當編譯連結時間達到一定程度時,程式設計師就會被誘導去做不會導致大規模重編譯的改動,而不管改動是否會保持原來的設計。
常規解決方案:
1. 靜態類庫:設計良好的靜態類庫能實現功能上的隔離,無法避免類庫實現 必須重新編譯、連結整個應用程式的問題
2. DLL:但仍有自己的缺點:
a) 函式重名問題:我們通過函式名來呼叫DLL的函式,在並行開發中容易造成函式重名。
b) 依賴:如果採用常見的隱式連線,那DLL每發行了一個新版本都有必要和應用程式重新連結一次,因為DLL裡面函式的地址可能已經發生了改變。
3. COM:DLL的缺點就是COM的優點。但是實際開發中我們會發現COM太複雜了。要使用COM程式設計,必須要非常熟悉C++中的COM實現細節, 最好之前要有使用和實現COM物件和伺服器的經驗。開發中而且必須從.idl開始工作才能加入介面屬性和方法,對開發和使用都有很高的門檻。
本文的解決方案—簡化的元件程式設計:
實際上我們只是在開發專案,並不需要跨語言程式設計,也不需要元件的位置透明性。為了專案而引入COM代價往往太過於巨大。然而COM的內部結構對於大多數程式設計師是無關的。因此有必要對COM進行簡化以降低程式設計門檻。使之更符合常規的變成習慣。所以我們借鑑了COM的優秀思想來構建我們的程式架構,使我們的程式能夠像基於COM元件開發那樣的靈活,而開發人員又不需要掌握太多的COM知識。下面我們分步介紹我們的實現過程
一、總體架構:
l應用程式:軟體的可執行程式(.exe),通過元件管理器來建立元件,元件建立起來後應用程式直接訪問元件,不再通過元件管理器中轉。
l 元件管理器:整個框架的核心部分,它本身是一個DLL檔案。應用程式通過它來建立、管理所有的相關DLL。作用類似與COM中的COM庫。它是應用程式載入的第一個DLL。
l 元件模組:以DLL實現的分解後功能模組。軟體的全部功能都在元件中實現,元件與元件之間,元件和應用程式之間並不直接直接耦合,應用程式或一個元件不能直接建立另一個元件的例項,而必須通過元件管理器建立。元件對外並不暴露出類的實現,而僅是通過元件管理器返回介面的指標。
二、應用程式執行過程:
應用程式的執行序列圖:
1. 主程式啟動:應用程式在啟動階段呼叫元件管理器啟動應用程式框架。
2. 元件管理器掃描應用程式目錄下所有的DLL檔案,並動態載入DLL,根據事先約好的註冊函式名判斷是否是框架元件
3. 查詢元件A實現的介面
4. 元件A返回它實現的全部介面ID(CLSID)。
5. 元件管理器把介面ID和對應的元件檔名登記在內部連結串列中。
6. 同3
7. 同4
8. 同5,
9. 啟動過程結束,控制權交還給主程式
10. 業務功能開始:主程式呼叫元件管理器,啟動所有自啟動介面
11. 元件管理器查詢內部連結串列,建立自啟動介面(元件B實現了自啟動介面)
12. 元件B在初始化函式中啟動了相關的業務功能。
13. 元件B需要用到介面A,但元件B並不知道誰實現了介面A,於是它呼叫元件管理器來建立介面A
14. 元件管理器查詢連結串列得知元件A實現了介面A
15. 元件管理器呼叫元件A的匯出函式建立介面A的例項
16. 元件A返回介面A的例項指標
17. 元件管理器將介面A的例項指標傳遞給介面B
18. 元件B呼叫介面A來完成某一功能
19. 元件B使用完介面A,直接呼叫介面A的函式來釋放介面A佔用的資源
20. 主程式執行結束:呼叫元件管理器釋放所有元件佔用資源
21. 元件管理器釋放所有自啟動接口占用資源。直接呼叫介面B的函式釋放
22. 元件B釋放完畢
23. 應用程式退出
三、應用程式的實現:
應用程式的實現比較簡單:僅需在應用程式初始化時載入元件管理器,呼叫管理器提供的啟動框架,啟動自啟動介面。在退出時呼叫元件管理器釋放所有元件佔用的資源即可
元件管理器是應用程式和元件之間的橋樑。它維護了一張元件介面連結串列。負責整個框架的啟動、元件的建立、還有最後框架資源的釋放工作。元件管理器雖然重要,但它的實現卻很簡單,這裡就不在詳講了。
五、元件:
元件是整個專案的核心,整個應用程式的所有功能都由元件完成。一般而言一個功能點需要由兩個元件來完成,一個提供功能服務,一個為自啟動元件,呼叫功能服務。
l 元件對外只暴露出介面,因此每一個元件至少都由兩部分構成,元件介面和元件的實現類。
a) 元件介面:借鑑COM的思想,每一個介面都有唯一的GUID來標示。
元件介面僅定義了一組類的純虛擬函式,並不包含實現的任何細節
b) 實現類:是介面的實現。包含全部的實現細節
l 跟COM類似,介面分為單例項和多例項介面。因此需要把建立部分分離出來。建立的程式碼很相似,所以可以用模板來實現。將公用程式碼寫成靜態庫,每個元件包含一份可以減少元件的程式碼編寫量。
元件結構圖
單例項介面的第一次建立過程與多例項一樣。第二次以後的建立為:
結果:
a) 開發獨立:每個模組可以單獨開發,單獨編譯,甚至可以單獨除錯和測試。當所有的元件開發完成後把它們組合在一起就得到了完整的應用系統。當需求發生部分變更時並不需要對所有的元件進行修改,只需修改受影響的元件即可。
b) 修改獨立:新增功能只需將實現的DLL放入應用程式目錄即可,不需更改原有程式碼。 除了核心模組,其餘功能拼湊可簡單通過增刪DLL實現
c) 模組獨立:在開發過程中強迫程式設計師和介面而不是具體的類打交道,防止出現耦合性很強的程式碼。
d) 智慧擴充套件,只需將實現特定介面的COM類(DLL)防入程式所在的目錄,程式自動建立它,可以在類的初始化函式內實現程式功能。
e) 可重用性強,因為是針對介面開發,只要符合介面規範就可以重用DLL
下面我們給出了一個按照仿COM架構實現的Demo
1. 單獨一個Exe也能執行,雖然只是個空殼子沒有功能。
2. 加入ComManager.DLL,於是程式具有了自動擴充套件功能。
3. 加入了ModuleA.DLL,主介面出現了一個按鈕,右機視窗彈出了一個選單,按鈕和選單均可以響應命令。選單和按鈕的建立和響應命令均在ModuleA.DLL中實現
4. 加入了ModuleB.DLL,主介面出現了另一個按鈕,右機視窗彈出的選單又多了一項,按鈕和選單均可以響應命令。新增的選單和按鈕的建立及響應命令均在ModuleB.DLL中實現
5. 加入Sking.DLL,於是整個程式的介面都具有了膚化效果
6. 加入Log.DLL,於是程式具有了日誌功能,可以紀錄模組建立的順序
7. 。。。。。。。。。。。。。。
8. 。。。。。。。。。。
因為程式是基於介面開發的,所以功能的實現和模組的名字無關,和模組載入的順序也無關(有興趣可以試一下)----當然ComManager.DLL必須是第一個載入,並且不能更名。