天天看點

COM線程模型(六)

當是程序外元件時,由主函數調用CoInitializeEx或CoInitialize指定元件所在套間,與上面的相同,CoInitialize代表STA,CoInitializeEx( NULL, COINIT_MULTITHREADED );代表MTA,沒有NA。因為NA是COM+提供的,而COM+服務隻能提供給程序内伺服器,是以隻使用上面的系統資料庫項的規則決定DLL元件是否放進NA套間,而沒有提供類似CoInitializeEx( NULL, COINIT_NEUTRAL );來處理EXE元件。而且如果可以使用CoInitializeEx( NULL, COINIT_NEUTRAL );将導緻調用線程和NA套間相關聯了,違背了NA的線程模型,這也是為什麼ThreadingModel鍵在<CLSID>\InprocServer32鍵下。

跨套間調用

STA線程1建立了一個STA對象,得到接口指針IABCD*,接着它發起STA線程2,并且将IABCD*作為線程參數傳入。線上程2中,調用IABCD::Abc()方法,成功或者失敗天注定。由于線程2所在的STA套間不同于線程1所在的STA套間,這樣線程2就跨套間調用另一個套間的對象了。按照前述的STA規則,IABCD::Abc()應該被轉成消息來發送,而如果如上面做法,可以,編譯通過,不過運作就不保證了。

COM之是以能夠實作前面所說的那些規則(STA、MTA、NA),是因為跨套間調用時,被調用的對象指針是指向一個代理對象,不是元件對象本身。而那個代理對象實作前述的那三個實作算法(轉成消息發送,線程切換等),而一般所說的代理/占位對象(Proxy/Stub)等其實都隻是指進行彙集工作的代碼(後述)。而按照上面直接通過線程參數傳入的指針是直接指向對象的,是以将不能實作STA規則,為此COM提供了如下兩個函數(還有其他方式,如通過全局接口表GIT)來友善産生代理:CoMarshalInterface和CoUnmarshalInterface(如果在同一程序内的線程間傳遞接口指針,則可以通過這兩個函數來進一步簡化代碼的編寫:CoMarshalInterThreadInterfaceInStream和CoGetInterfaceAndReleaseStream)。

現在重寫上面代碼,線程1得到IABCD*後,調用CoMarshalInterface得到一個IStream*,然後将IStream*傳入線程2,線上程2中,調用CoUnmarshalInterface得到IABCD*,現在這個IABCD*就是指向代理對象的,而不是元件對象了。

是以,前面所說過的所有線程模型的算法都是通過代理對象實作的。要跨套間時,使用CoMarshalInterface将代理對象的CLSID和其與元件對象建立聯系的一些必要資訊(如元件對象的接口指針)列集(Marshaling)到一個IStream*中,再通過任何線程間通信手段(如全局變量等)将IStream*傳到要使用的線程中,再用CoUnmarshalInterface散集(Unmarshaling)出接口以獲得指向代理對象的接口指針。是以之是以要獲得代理對象的指針是因為想使用COM提供的線程模型(但在COM+中,這不是唯一的理由),如果不想使用大可不必這麼麻煩(不過後果自負),并沒有強制要求必須那麼做。

當線程1和線程2都是MTA時,則可以像最開始說的那樣,直接傳遞IABCD*到線程2中,因為MTA線程模型同意多個線程同時直接調用對象,線程1和線程2在同一個MTA套間中,而那個對象通過某種形式(如ThreadingModel = Free)向COM聲明了自己支援MTA線程模型。

而當a.exe的線程1和b.exe的線程2都是MTA時,則依舊需要像上面那樣進行接口指針的彙集(列集→傳輸→散集這個過程)以得到指向代理而非對象的指針,即使線程1和線程2都是在MTA套間中,卻是在兩個不同的MTA套間中,是以是跨套間調用,需要彙集操作。