跨進程伺服器實作協助程式
有四個可由跨進程伺服器呼叫的協助程式函式可用來簡化撰寫伺服器程式代碼的工作。 COM 用戶端和 COM 進程伺服器通常不會呼叫它們。 這些函式的設計目的是在伺服器有多個 Apartment 或多個類別物件時,協助防止伺服器啟用中的競爭狀況。 不過,它們也可以輕易地用於單個線程和單一類別對象伺服器。 函式如下所示:
若要正確關閉,COM 伺服器必須追蹤已具現化的物件實例數目,以及已呼叫其 IClassFactory::LockServer 方法的次數。 只有當這兩個計數達到零時,伺服器才會關閉。 在單個線程 COM 伺服器中,關閉的決定會與訊息佇列串行化的傳入啟用要求協調。 伺服器在其最終物件實例上收到發行並決定關閉時,會在分派任何啟用要求之前撤銷其類別物件。 如果啟用要求確實出現在這一點之後,COM 會辨識類別對象已撤銷,並將錯誤傳回 Service Control Manager (SCM),這會導致執行本機伺服器進程的新實例。
不過,在 Apartment 模型伺服器中,在不同的 Apartment 上註冊不同的類別物件,而且在所有自由線程的伺服器中,必須協調關閉的這項決定與跨多個線程的啟用要求協調,讓伺服器的一個線程不會決定關閉,而伺服器的另一個線程正忙於分發類別對象或對象實例。 若要解決此問題,一個傳統但繁瑣的方法就是讓伺服器撤銷其類別對象之後,重新檢查其實例計數,並維持運作狀態,直到所有實例都釋放為止。
為了讓伺服器寫入器更輕鬆地處理這些類型的競爭條件,COM 提供兩個參考計數函式:
- CoAddRefServerProcess 會遞增全域每個進程參考計數。
- CoReleaseServerProcess 會遞減全域每個進程參考計數。
當全域每個進程參考計數達到零時,COM 會自動呼叫 CoSuspendClassObjects,以防止任何新的啟用要求傳入。 然後,伺服器就可以在閑置時從其各種線程取消註冊其各種類別物件,而不必擔心另一個啟用要求可能會傳入。 因此,SCM 會處理所有新的啟用要求,以啟動本機伺服器進程的新實例。
使用這些函式的本地伺服器應用程式最簡單的方式,就是在其每個實例物件的建構函式中呼叫 CoAddRefServerProcess,並在 fLock 參數為 TRUE 時,在其每個 IClassFactory::LockServer 方法中呼叫 CoAddRefServerProcess。 當 fLock 參數為 FALSE 時,伺服器應用程式也應該在其每個實例物件的解構函式和其 IClassFactory::LockServer 方法的解構函式中呼叫 CoReleaseServerProcess。
最後,伺服器應用程式應該注意來自 CoReleaseServerProcess 的傳回碼,如果傳回 0,伺服器應用程式應該起始其清除,這對具有多個線程的伺服器而言,通常表示應該向其各種線程發出訊號以結束其訊息迴圈,並呼叫 CoAddRefServerProcess 和 CoReleaseServerProcess。 如果使用伺服器進程存留期管理功能,則必須在物件實例和 LockServer 方法中使用它們;否則,伺服器應用程式可能會過早關閉。
提出 CoGetClassObject 要求時,COM 會連絡伺服器、封送處理類別物件的 IClassFactory 介面、傳回客戶端進程、取消封送 IClassFactory 介面,並將它傳回給用戶端。 此時,用戶端通常會使用TRUE呼叫LockServer,以防止伺服器進程關閉。 不過,當類別物件封送處理和用戶端呼叫 LockServer 時,有一個時間範圍,其中另一個用戶端可以連線到同一部伺服器、取得實例,並釋放該實例,因而導致伺服器關閉,並讓第一個用戶端保持高狀態,並且以中斷連線的 IClassFactory 指標讓第一個用戶端保持乾涸。 為了防止此競爭狀況,COM 會在客戶端釋放 IClassFactory 介面時,將具有 TRUE 的隱含呼叫新增至 LockServer,並在客戶端釋放 IClassFactory 介面時,將隱含呼叫新增至類別物件。 因此,不需要遠端 LockServer 回呼伺服器,而 LockServer 的 Proxy 只會傳回S_OK,而不會實際遠端呼叫。
初始化跨進程伺服器進程時,還有另一個啟用相關的競爭條件。 註冊多個類別的 COM 伺服器通常會針對它支援的每個 CLSID 呼叫 CoRegisterClassObject ,並具有REGCLS_LOCAL_SERVER。 針對所有類別完成此動作之後,伺服器就會進入其訊息迴圈。 對於單個線程 COM 伺服器,所有啟用要求都會遭到封鎖,直到伺服器進入訊息循環為止。 不過,對於在不同 Apartment 中註冊不同類別物件的 Apartment 模型伺服器,以及針對所有自由線程的伺服器,啟用要求可以早於此抵達。 在 Apartment 模型伺服器的情況下,只要任何一個線程進入其訊息循環,啟用要求就會立即送達。 在無線程伺服器的情況下,啟用要求可能會在註冊第一個類別物件時立即送達。 由於啟用可能很早發生,因此最後一個版本也可能發生(因此導致伺服器開始關閉),而伺服器其餘部分有機會完成初始化。
若要消除這些競爭條件並簡化伺服器寫入器的工作,任何想要向 COM 註冊多個類別對象的伺服器都應該使用 REGCLS_LOCAL_SERVER 呼叫 CoRegisterClassObject |伺服器支援的每個不同 CLSID REGCLS_SUSPENDED。 註冊所有類別且伺服器進程已準備好接受傳入啟用要求之後,伺服器應該對 CoResumeClassObjects 進行一次呼叫。 此函式會告訴 COM 通知 SCM 所有已註冊的類別,並開始讓啟用要求進入伺服器進程。 使用這些函式可提供下列優點:
- 不論註冊了多少 CLSID,都只會對 SCM 進行一次呼叫,進而減少整體註冊時間(因此伺服器應用程式的啟動時間)。
- 如果伺服器有多個 Apartment 且不同的 CLSID 在不同的 Apartment 中註冊,或者如果伺服器是自由線程伺服器,則伺服器呼叫 CoResumeClassObjects 之前,不會傳入任何啟用要求,讓伺服器有機會註冊其所有 CLSID,並在處理啟用要求和可能的關閉要求之前正確設定。
相關主題