關(guān)鍵字:DCOM、數(shù)組、自定義類型、Marshal、SafeArray、ICollection
    
本文講述在COM的接口中使用數(shù)組作為參數(shù)的三種方法。它們分別是:數(shù)組指針、SafeArray和ICollection。文章分析了各種方法的優(yōu)缺點。本文的目的不是描述COM的基本原理和開發(fā)方法。為了能夠更好的理解本文中的內(nèi)容,讀者需要具備基本的COM編程知識。
1 相關(guān)的基本概念
在COM中,如果對接口中方法的調(diào)用是跨套間的,就必須對所進(jìn)行的調(diào)用進(jìn)行序列化。序列化哪康氖鞘溝饔媚芄輝謖返奶準(zhǔn)渲蟹⑸1疚牟淮蛩閬晗該枋鎏準(zhǔn)浜托蛄謝睦礪郟齠砸婕暗降囊恍└拍罱興得鰲?lt;/SPAN>
1.1 套間(Apartment)
在一個進(jìn)程中可以包含多個套間,每個套間可以包含一個或多個線程[1]。包含單個線程的套間叫做單線程套間;包含多個線程的套間叫做多線程套間。在一個進(jìn)程中最多可以包含一個多線程套間,但可以擁有0或多個單線程套間。每個使用COM的線程,無論是客戶程序還是COM程序都要通過調(diào)用CoInitialize或CoInitializeEx函數(shù)進(jìn)入套間,同時確定所在套間的類型。在進(jìn)入套間之前不能使用COM功能,否則會導(dǎo)致錯誤結(jié)果。
1.2 序列化(Marshal)
在COM架構(gòu)中調(diào)用者和被調(diào)用者如果在不同的套間,就不可以直接調(diào)用,而必須通過代理(Proxy)和占位(Stub)程序調(diào)用。代理程序和調(diào)用者在同一個套間中,而占位程序和被調(diào)用者在同一個套間中。代理程序模仿COM組件的行為,接受調(diào)用,而占位程序模仿調(diào)用者的行為發(fā)出調(diào)用。這樣就可以保證調(diào)用是在同一個套間中進(jìn)行了。代理和占位程序之間通過特定的網(wǎng)絡(luò)通訊協(xié)議傳遞被調(diào)用的方法、參數(shù)和返回值。把調(diào)用轉(zhuǎn)換成網(wǎng)絡(luò)協(xié)議的過程叫做序列化(Marshal)。由于涉及到指針或數(shù)組類型的傳遞地址型的參數(shù),序列化過程非常復(fù)雜,幸好我們有簡單的方法可以生成效率還算不錯的代理和占位程序。
對于指針類型,具體的指針地址是不重要的,關(guān)鍵是指針指向的內(nèi)存中的數(shù)據(jù)。所以在傳遞指針類型的參數(shù)時,必須傳遞所指向的數(shù)據(jù)。對于數(shù)組類型數(shù)據(jù)也有類似的情況。如果存在雙重或多重指針,情況就會變得更加復(fù)雜。
例如long*類型的參數(shù)。在序列化的時候代理程序把參數(shù)所指向的長整型值傳遞給占位程序,占位程序要為參數(shù)申請內(nèi)存,然后把長整型的值存放到申請到的內(nèi)存中,使用這個新的內(nèi)存地址作為參數(shù)調(diào)用目標(biāo)函數(shù)。在函數(shù)返回的時候,內(nèi)存中的數(shù)據(jù)變化被占位程序傳遞回代理程序。代理程序把數(shù)據(jù)復(fù)制回調(diào)用者的內(nèi)存,然后返回。如果指針?biāo)赶虻臄?shù)據(jù)不是單個的值,而是一塊不定大小的內(nèi)存,序列化時就要確定所要傳遞數(shù)據(jù)的長度。另外,在多重指針的情況下,要傳遞的就不是一塊數(shù)據(jù),而可能是多塊數(shù)據(jù)段了。
在后面的討論中,我們會詳細(xì)地說明如何生成正確的代理和占位程序。
1.3 代理(Proxy)和存根(Stub)
COM通過代理和存根實現(xiàn)序列化功能。每個可能跨套間調(diào)用的接口都必須有相應(yīng)的代理和存根程序。代理和存根程序是在同一個動態(tài)連接庫中的。每個COM接口的設(shè)計者負(fù)責(zé)實現(xiàn)自己的代理和存根程序。一般情況下,代理和存根程序的代碼可以通過一個叫做MIDL的工具自動生成的,我們要做的只是把它編譯出來。
代理和存根的工作方式如下圖:
代理的作用是在客戶套間中“偽裝”成COM對象,供客戶程序調(diào)用。而存根的作用則是在COM對象所在的套間中“偽裝”成客戶程序,發(fā)出調(diào)用請求。代理和存根之間通過網(wǎng)絡(luò)協(xié)議[2]交換調(diào)用請求和返回結(jié)果。
2 概述
在COM中使用數(shù)組可以使用三種方法:數(shù)組指針、SafeArray和ICollection。數(shù)組指針和我們熟悉的C/C++程序中傳遞數(shù)組的方法是相同的、SafeArray是VB中標(biāo)準(zhǔn)的存放數(shù)組的方法,也是Automation中的標(biāo)準(zhǔn)方法、ICollection方法是通過一個獨立的COM對象傳遞數(shù)據(jù)。這三種方法各有優(yōu)缺點,應(yīng)該按照具體的需求決定使用哪種方法。
2.1 數(shù)組指針
數(shù)組指針是標(biāo)準(zhǔn)的C/C++中的數(shù)組參數(shù)傳遞方式。數(shù)組指針實際上就是數(shù)組元素序列化存放時的首地址。數(shù)組指針的操作非常簡單,所以也是效率最高的傳遞方式。但是,這種方式不能夠在VB中使用。數(shù)組指針可以傳遞一維數(shù)組,也可以傳遞多維數(shù)組。如果COM的客戶端是VC++程序的話,這是最好的傳遞方式。
數(shù)組指針作為跨套間的調(diào)用參數(shù)時,需要進(jìn)行marshal。所以,應(yīng)該編譯和注冊proxy/stub。
2.2 SafeArray
SafeArray是標(biāo)準(zhǔn)的VB數(shù)組存放方式。和數(shù)組指針類似,SafeArray可以傳遞一維數(shù)組,也可以傳遞多維數(shù)組。由于SafeArray具有比數(shù)組指針更復(fù)雜的結(jié)構(gòu),所以,編程比使用數(shù)組指針復(fù)雜[3],程序運行效率也相對較低。
使用SafeArray方式傳遞的數(shù)組,可以從VB程序中調(diào)用,也可以從VC++程序中調(diào)用。而且,由于SafeArray是Automation的標(biāo)準(zhǔn)數(shù)據(jù),所以可以通過缺省的基于TLB的proxy/stub進(jìn)行跨套間的調(diào)用,而不必編譯和注冊自己的proxy/stub。
2.3 ICollection
ICollection方式是最復(fù)雜,也是使用最廣泛的。ICollection并不是一個接口的名稱,而是指實現(xiàn)了枚舉器和索引屬性的IDispatch接口。這種數(shù)組傳遞方式的特點是自己實現(xiàn)數(shù)組對象,所以有最大的靈活性,可以實現(xiàn)按需生成數(shù)組元素等高級功能。
ICollection所傳遞的數(shù)組對象不再是普通的指針或特定的結(jié)構(gòu),而是一個獨立的COM對象。由于傳遞的是接口,所以參數(shù)具有面向?qū)ο蟮亩鄳B(tài)性特征,就是說數(shù)組元素可以是自己實現(xiàn)的,也可以是其他人實現(xiàn)的,只要是實現(xiàn)了有特定屬性的IDispatch接口就可以作為參數(shù)。另一方面,由于用作數(shù)組的COM對象可以單獨設(shè)計,所以,可以使用更加合理的實現(xiàn)方式,例如使用列表、hash表或平衡樹等方式實現(xiàn)。
有的數(shù)組實現(xiàn),只需要訪問少數(shù)的幾個元素,或者元素個數(shù)理論上是無窮的,或者每個數(shù)組元素的生成需要耗費大量的資源。這時,應(yīng)該使用ICollection方式實現(xiàn)數(shù)組傳遞。