一時靈感湧現,有所頓悟,遂記錄下來。注意不保證正確性。
我們知道公寓只是個邏輯概念,操作系統只為線程進程分配資源,不會為公寓分配資源。當客戶程序調用 CoInitializeEx()時指定公寓類型,是STA還是MTA,只有COM才用到公寓這個概念。那為什麼要創建這樣一個概念,從根本上說是為了實現在多線程中同步地訪問組件,盡可能地為我們編寫組件制造方便,免除我們必須自己實現同步控制的麻煩。那它是怎麼實現的呢?
對於STA來說,我們知道當以COINIT_APARTMENTTHREADED參數調用CoInitializeEx()的時侯,系統會自動創建一個隱含的窗口。該隱含窗口的消息隊列被用於同步化並分派對該STA內創建的對象的方法的調用。在這個公寓外其他線程要想調用該公寓內的對象方法,都必須先放在消息隊列裡,因為消息隊列是線性的,所以自然而然的就解決了同步問題。對於MTA也應該是類似的方法,雖然書上沒有說,但我想MTA公寓一樣會有一個隱含的窗口。
公寓間必須要調度,這句話包含了兩個方面的意思,也即是包含了兩種調度。第一個調度是指在公寓間調度接口指針。調用一個對象的方法,首先就是要獲得它的接口指針。COM的接口指針是與公寓相關的,顯然它們不能直接被某線程用於其它公寓。那樣的話就等於跳過了消息隊列而直接調用對象了,沒法保證同步性。所以這時必須要調度。調度方法很簡單,假設在STA2中要得到STA1中的一個對象的接口指針,則在STA1線程裡通過調用CoMarshallInterThreadfaceInStream把對象接口指針調度到一個流對象內,然後在STA2線程內通過調用CoGetInterfaceAndReleaseStream反調度該流對象並獲得一個指向代理的指針,這個代理在STA2內,並與STA1相關。要注意到的是,STA2得到的指向代理Proxy的指針,而不是直接指向對象接口的指針。第二個調度就很自然了,每當STA2要調用STA1中的接口方法時都要通過Proxy,這也叫調度。
聯系到前面講到的公寓使用窗口消息隊列來實現同步化的方法,我們可以猜測這裡代理的作用是與這個消息隊列相關的。很可能代理就是簡單的把對方法的調用放到窗口的消息隊列中。
公寓間的這種調度與進程間以及遠程間的調度顯然是有根本區別的,這一點從另外一個角度也可以看出來。公寓間的調度並非是必須的!如果組件自身已經實現了同步控制,那公寓間的調度就是完全沒有必要的了,因為我們說過公寓的作用完全在於幫助我們實現同步訪問問題。既然我們可以通過自由線程調度器FTM來跨過公寓間的調度而直接調用接口方法,那也可以看出,公寓間的調度不是必須的!進程間以及遠程間的調度肯定都是必須的,那這兩種調度之間的區別何在呢?後者的調度產生原因在於進程處於不同的地址空間,甚至是在不同機器上的,需要調度解決這個問題。既包含調度接口指針,也包含了更為重要的參數打包解包的調度。但對於公寓間的調度不存在這個問題,因為公寓的一個基本點在於它是存在於一個進程內的。所以它不需要完成不同地址空間的映射,不需要把函數參數打包解包,它的調度的目的完全是不同的。另外我們也注意到,公寓間的調度只有代理Proxy,沒有存根Stub。這也應該證明了我們的猜想吧,公寓間的調度只是為了解決同步性問題,代理的作用僅僅是把函數調度放到消息隊列中。
以上內容可以總結為以下幾點:
1、MTA公寓也有一個隱含窗口,也是通過窗口消息隊列來實現同步性的。
2、公寓間必須調度接口指針,調度的結果是獲得一個指向代理的指針。
3、公寓間的接口方法調用,必須通過代理來實現。
4、公寓間的調度完全只是為了實現同步性。
5、公寓間的調度只有代理沒有存根。
6、代理的作用只是簡單的把接口的方法調用放入窗口消息隊列中。