COM本質論——COM是一個好的C++

1.1 軟件的分發和C++


        把C++作爲組件的基礎底層結構所帶來的問題:以前,C++一直以分發源代碼的形式來發布庫,這樣做是可行的,但存在問題:如果多個程序都使用一個代碼庫,則各個程序都將在自己程序中編譯同樣的代碼,並生成可執行文件,導致內存的浪費。同時,更新困難。如:假設一段代碼變成可執行的代碼大小爲16MB,如果一個用戶安裝了3個程序,這3個程序都使用了這段代碼,則將佔用48MB的空間,都知道計算機上可能有很多程序都使用了基礎類庫,如果基礎類庫一味的拷貝,則所佔的空間將可想而知。如下圖可知。FastString類庫編譯了FastingString.obj。

      動態鏈接和C++:解決共享代碼的問題的一種技術是把類庫代碼以動態鏈接庫的形式包裝起來。使用__declspec(dllexport)關鍵字。使用該方法聲明的方法,將被加到dll的引出表中,允許在運行時把每個方法的名字解析到內存中對應的地址。並且引入庫暴露了方法的符號。注意:引入庫很小(引出符號的文本的兩倍),當類庫從DLL中引出時,它的機器碼在用戶的硬盤上只保留一份,當多個客戶訪問代碼時,操作系統的裝載器可以很靈活地讓所有的客戶程序共享一份dll的只讀可執行代碼的物理內存頁。並且廠商可以統一地進行更新DLL文件。


        C++和可移植性:一旦確定使用DLL的形式發佈C++類,就將面臨C++的根本弱點:C++缺少二進制級的統一標準。許多編譯器廠商有自己獨特的鏈接和編譯方式,使得編譯出來的DLL不能互用。對於創建多個二進制的、基於組件的可執行代碼來說,這是個嚴重問題,因爲每個組件很可能是用不同的編譯器和連接器建立起來的。C++缺少二進制標準,這限制了語言特徵在跨越DLL邊界時的應用。

        封裝性和C++:在C++中建立二進制組件另一障礙是與封裝有關。考慮情形:一個組織應用中使用了FastString,而他們想在產品發貨前兩個月完成開發測試,或者在發佈FastString的DLL之後想改變其中的個別函數。但是雖然C++通過private和public關鍵字可以支持語法上的封裝,但是C++並沒有定義二進制層面上的封裝,因爲編譯器需要知道內存的佈局,這樣才能構造實例。如下圖:1.0版本FastString實例大小爲4字節,因爲只有一個char*,而2.0版本增長到8字節,因爲多了一個int類型。針對1.0用戶,類定義編寫的客戶分配4個字節的內存,並傳遞給構造函數。而2.0版本的構造函數和方法都假定客戶爲每個實例分配了8個字節的內存,並且毫無保留的寫入這8個字節,而對於原先1.0客戶來說,這完全是粗魯的,因爲後4個字節是別的代碼的並不是FastString的。如下圖。


        把接口從實現中分離出來封裝以把一個對象的外觀(接口)同其實際工作方式(實現分離開爲基礎,而C++沒有把這條規則運用二進制層面上,因爲C++的類既是接口優勢實現。這個弱點可以通過下面這種方法解決:構造一個模型,把接口和實現做成兩個分離的實體,即C++類。定義一個C++類代表指向一定數據類型的接口;定義另一個C++類,作爲這個數據類型的實現,於是從理論上講,對象的實現可以修改實現的細節而接口保持不變。注意:這個接口類的二進制佈局結構並不會隨着實現類數據成員的增加或刪除而改變。並且FastString類的聲明不需要被包含在這個頭文件中就可進行編譯了,可以方便的把實現隱藏起來。雖然有以上優點,但是,接口必須把每個方法調用顯示地傳遞給實現類,這種工作對於非常大的類庫無疑是一種巨大的負擔。



        抽象基類作爲二進制接口:如前所述,兼容性問題起源於2個方面:1、運行時表現的語言特性;2、在鏈接時刻如何表達符號的名字。爲了與解決編譯器無關的特性,爲了實現獨立性我們必須確定語言的那些放免具有統一的實現形式。

                        假設一:任何基於C的系統,都必須遵守:複合類型(C風格的struct)在運行時的表現形式對於不同的編譯器往往保持不變

                        假設二:所有的編譯器都強制使用同樣的順序傳遞參數(從右至左/從左至右)

                        假設三:某個給定的平臺上的所有C++編譯器都實現了同樣的虛函數調用機制(只要不定義數據成員即可?)



                        根據前面的假設,可以解決編譯器依賴性問題。假設三成立,定義接口類IFastString,表明所有的編譯器將爲IFastString產生等價的機器碼,這個等價的條件是:接口沒有數據成員(因爲接口沒有數據成員,所以任何人不能用任何實在的方式來實現這些方法?);接口不能從其他接口派生。爲了進一步加強思想,把方法設計爲純虛函數,這樣子避免了用接口的實現。對於實現類從接口重載每個純虛函數,實現這些方法。因爲FastString從IFastString繼承而來,所以FastString是IFastString的一個超集。這意味着FastString將包含一個vptr,指向一個與IFastString兼容的vtbl。


                          把實現類的定義暴露給客戶等於繞過了接口的二進制封裝,從而破壞接口的基本意圖。爲了使客戶能夠構造fastString對象,讓DLL引出一個全局函數,由它代表客戶的new操作,這個函數必須以extern “C”的方式引出,這樣任何一個C++編譯器都可以訪問。如同句柄方法一樣,new操作符僅僅在FastString DLL內部調用,這意味着對象的大小和佈局結構將使用與編譯實現類的所有方法相同的編譯器建立起來



                            對象的析構,


                              並不會從外層向基類型遞歸地銷燬。因爲FastString沒有被調用到。解決方法:增加delete方法。




                                  注意:在FastStringDLL中,除了一個入口函數之外,其他的所有入口函數都虛函數,接口類的虛函數總是通過保存在vtbl中的函數指針被間接調用,客戶不需要再開發時鏈接這些函數的符號名。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章