[OLE] OLE スレッド モデルの概要としくみ


概要


COM オブジェクトは、1 つのプロセスの複数のスレッド内で使用できます。"シングルスレッド アパートメント" (STA) および "マルチスレッド アパートメント" (MTA) という用語は、オブジェクトとスレッドとの関係、オブジェクト間の同時実行関係、メソッド呼び出しをオブジェクトに配信する方法、およびスレッド間でのインターフェイス ポインタの受け渡しの規則について説明するための概念的な枠組を形成するのに使用されます。コンポーネントおよびそのクライアントは、現在 COM によってサポートされている以下の 2 つのアパートメント モデルから、いずれかを選択します。


  1. シングルスレッド アパートメント (STA) モデル :1 つのプロセス内の 1 つまたは複数のスレッドが COM を使用し、COM オブジェクトへの呼び出しは COM によって同期されます。インターフェイスは、スレッド間でマーシャリングされます。シングルスレッド アパートメント モデルの低次元な形態の 1 つである、プロセス内の 1 つのスレッドのみが COM を使用するケースは、シングル スレッド モデルと呼ばれます。マイクロソフトの過去の情報やドキュメントでは、STA モデルを単に "アパートメント モデル" と呼んでいる場合があります。
  2. マルチスレッド アパートメント (MTA) モデル : 1 つまたは複数のスレッドが COM を使用し、MTA に関連付けられた COM オブジェクトへの呼び出しは、MTA に関連付けられたすべてのスレッドによって直接実行されます。呼び出し元とオブジェクトとの間にシステム コードは介在しません。オブジェクトは、複数の同時実行クライアントによってほぼ同時に (マルチプロセッサ システム上では同時に) 呼び出される場合があるため、その内部状態を自分で同期する必要があります。インターフェイスは、スレッド間でマーシャリングされません。マイクロソフトの過去の情報やドキュメントでは、このモデルを "フリースレッド モデル" と呼んでいる場合があります。
  3. STA モデルと MTA モデルの両方を同じプロセス内で使用できます。このようなプロセスを "混合モデル" プロセスと呼ぶ場合があります。
MTA は、NT 4.0 で導入され、DCOM95 をインストールした Windows 95 でも使用できます。STA モデルは、Windows NT 3.51、Windows 95、NT 4.0、および DCOM95 をインストールした Windows 95 いずれにも存在します。

詳細


概要

COM のスレッド モデルは、異なるスレッド アーキテクチャを使用するコンポーネントが共存するためのメカニズムを提供し、また、同期サービスが必要なコンポーネントに対して同期サービスを提供します。たとえば、単一のスレッドによる呼び出しにのみ対応可能なオブジェクトは、そのままではクライアントからの同時呼び出しを同期できない場合があります。このようなオブジェクトが複数のスレッドによって同時に呼び出されると、クラッシュまたはエラーが発生します。COM は、このようなスレッド アーキテクチャの相互運用を処理するためのメカニズムを提供します。


スレッド対応のコンポーネントであっても、多くの場合は同期サービスを必要とします。たとえば、OLE/ActiveX コントロール、埋め込み先で編集可能な埋め込み、ActiveX ドキュメントなどのグラフィカル ユーザー インターフェイス (GUI) を持つコンポーネントでは、COM 呼び出しとウィンドウ メッセージの同期およびシリアル化を必要とします。COM がこのような同期サービスを提供することにより、複雑な同期処理のコードを使用せずにこれらのコンポーネントを記述できます。


"アパートメント" は、互いに関連する複数の側面を持っています。第 1 に、"アパートメント" は、スレッドとひとまとまりの COM オブジェクトとがどのように関係しているかなど、同時実行について考慮するための論理構成体です。第 2 に、プログラマが、COM 環境から望みどおりの同時実行動作を受け取るために従う必要がある一連の規則でもあります。最後に、"アパートメント" は、システム提供のコードとして、プログラマが COM オブジェクトに関するスレッドの同時実行を管理するのを助けます。


"アパートメント" という用語は、1 つのプロセスを完全に分離した 1 つのエンティティとみなすメタファに由来しています。ちょうど、1 つの "ビルディング" が、互いに関連しながらも独立した "アパートメント" と呼ばれる棟に分けられるのと似ています。1 つのアパートメントは、オブジェクト間、および場合によってはスレッド間の関連付けを行う "論理コンテナ" に相当します。スレッドとアパートメントは同一ではありません。ただし、STA モデルでは、単一のスレッドが 1 つのアパートメントに論理的に関連付けられる場合もあります。また、オブジェクトもアパートメントと同一ではありません。ただし、各オブジェクトは、1 つのアパートメントにのみ関連付けられています。アパートメントは、単に論理構成体であるというだけでなく、その規則によって COM システムの動作を定義しています。アパートメント モデルの規則に従うことで、はじめて COM オブジェクトは正しく機能します。

詳細

シングルスレッド アパートメント (STA) は、ある特定のスレッドに関連付けられた COM オブジェクトの集合です。これらのオブジェクトは、そのスレッドによって作成されたことによって、より正確にはそのスレッド上の COM システムに最初に公開された (一般にはマーシャリングによって) ことによって、そのアパートメントと関連付けられます。STA は、オブジェクトまたはプロキシが "住んでいる" 場所として考えることができます。オブジェクトまたはプロキシに (同じまたは異なるプロセスの) 別のアパートメントからアクセスする必要がある場合には、オブジェクトまたはプロキシのインターフェイス ポインタをそのアパートメントにマーシャリングして、そこに新しいプロキシを作成する必要があります。アパートメント モデルの規則が遵守されているならば、このオブジェクトに対して、同じプロセス内のほかのスレッドから直接呼び出しを行うことはできません。これは、1 つのアパートメント内のすべてのオブジェクトは単一のスレッド上で実行されるという規則に違反します。このような規則が存在するのは、STA 内で実行されるコードの大部分は追加スレッド上で実行された場合に正しく機能しないためです。


STA に関連付けられたオブジェクトが呼び出しを受け取るためには、STA に関連付けられているスレッド内において、CoInitialize または CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) の呼び出しと、ウィンドウ メッセージの取り出しおよびディスパッチをあらかじめ実行する必要があります。後述するように、COM は、ウィンドウ メッセージを使用して、STA 内のオブジェクトへの呼び出しのディスパッチと同期を行います。


"メイン STA" は、あるプロセス内で最初に CoInitialize または CoInitializeEx(NULL,COINIT_APARTMENTTHREADED) を呼び出すスレッドです。プロセスのメイン STA は、COM 関連のすべての作業が完了するまで存在し続ける必要があります。これは、後述するように、一部のインプロセス オブジェクトが常にメイン STA にロードされるためです。


Windows NT 4.0 および DCOM95 では、マルチスレッド アパートメント (MTA) と呼ばれる新しいタイプのアパートメントが導入されました。MTA とは、プロセス内のあるスレッドの集合と関連付けられた COM オブジェクトの集合であり、すべてのスレッドがシステム コードを介在させることなく、オブジェクトに実装されたあらゆるコードを直接呼び出すことができます。MTA 内の任意のオブジェクトへのインターフェイス ポインタは、その MTA に関連付けられたスレッド間で、マーシャリングなしで受け渡しできます。CoInitializeEx(NULL, COINIT_MULTITHREADED) を呼び出すプロセス内のすべてのスレッドは、MTA に関連付けられます。前述の STA とは異なり、MTA に関連付けられたオブジェクトが呼び出しを受け取るのに、MTA 内のスレッドがウィンドウ メッセージの取り出しとディスパッチを行う必要はありません。その代わり、COM は、MTA 内のオブジェクトへの呼び出しを同期しません。このため、MTA 内のオブジェクトは、複数の同時実行スレッドの相互作用によってその内部状態が破壊されないように保護する必要があります。MTA モデルを使用したプログラミングにおいては、異なるメソッド呼び出しの間スレッド内部のデータが一定に保たれると仮定することはできません。


1 つのプロセスは、任意の数の STA を持つことができますが、MTA は 1 つしか持つことができません。MTA は、1 つまたは複数のスレッドから構成されます。STA は、それぞれ 1 つのスレッドを持ちます。1 つのスレッドは、1 つより多くのアパートメントに属することはありません。オブジェクトは、1 つのアパートメントにのみ属しています。インターフェイス ポインタは、(マーシャリングの結果がプロキシではなく直接ポインタであっても) 常にアパートメント間でマーシャリングする必要があります。CoCreateFreeThreadedMarshaler については、下記を参照してください。


プロセスは、COM によって提供されるスレッド モデルの中からいずれかを選択します。STA モデルのプロセスには、1 つまたは複数の STA が存在し、MTA はありません。MTA モデルのプロセスには、1 つまたは複数のスレッドを持つ 1 つの MTA が存在し、STA はありません。混合モデルのプロセスには、1 つの MTA と任意の数の STA が存在します。

シングルスレッド アパートメント モデル

STA のスレッドは、CoInitialize または CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) の呼び出しと、ウィンドウ メッセージの取り出しおよびディスパッチを行う必要があります。このモデルでは、COM がウィンドウ メッセージを使用してオブジェクトへの呼び出しの配信の同期とディスパッチを行うためです。詳細については、「関連情報」を参照してください。


STA モデルをサポートするサーバー


STA モデルでは、ウィンドウに送信されたウィンドウ メッセージが同期されるのと同じしくみで、オブジェクトへの呼び出しが COM によって同期されます。呼び出しは、ウィンドウ メッセージを使用して、オブジェクトを作成したスレッドに配信されます。その結果、オブジェクトのスレッドでは、Get/PeekMessage および DispatchMessage を呼び出して、呼び出しを受け取る必要があります。COM は、各 STA と関連付けられた非表示のウィンドウを作成します。STA 外部からのオブジェクトへの呼び出しは、COM ランタイムによって、この非表示のウィンドウに送信されたウィンドウ メッセージを使用して、そのオブジェクトのスレッドに転送されます。オブジェクトの STA に関連付けられたスレッドがメッセージを取り出してディスパッチすると、COM によって実装された非表示のウィンドウのウィンドウ プロシージャがこれを受け取ります。COM ランタイムは、COM 所有のスレッド側、および STA のスレッドへの呼び出し側いずれにも存在するため、ウィンドウ プロシージャを使用して、STA に関連付けられたスレッドを "フック" します。(STA のスレッド内で実行中)の COM ランタイムは、COM 提供のスタブを介して、オブジェクトの対応するインターフェイス メソッドの中へ呼び出しを行います。メソッド呼び出しから戻る実行パスは、この呼び出しを逆にたどり、呼び出しはスタブから COM ランタイムに返されます。これにより、ウィンドウ メッセージを介して COM ランタイム スレッドに制御が戻り、COM チャネルを通して呼び出し元に返されます。


複数のクライアントが 1 つの STA オブジェクトを呼び出した場合、STA 内で使用される制御メカニズムの移行によって、これらの呼び出しは自動的にメッセージ キューに入れられます。オブジェクトは、そのオブジェクトの STA がメッセージの取り出しとディスパッチを行うたびに 1 つずつ呼び出しを受け取ります。呼び出しがこのような方法で COM によって同期され、また呼び出しが常にオブジェクトの STA に関連付けられた単一のスレッド上に配信されることから、オブジェクトのインターフェイスでは同期処理をインプリメントする必要はありません。


: メソッド呼び出しの処理中、インターフェイスにインプリメントされたメソッドがメッセージの取り出しとディスパッチを行い、その結果同じ STAからそのオブジェクトに対して別の呼び出しが配信された場合、オブジェクトは再入可能です。このような状況は、一般に、STA オブジェクトが COM を使用して (異なるアパートメント間/異なるプロセス間で) 呼び出しの発信を行った場合に発生します。これは、ウィンドウ プロシージャが、メッセージの処理中にメッセージの取り出しとディスパッチを行ったときに再入可能であるのと同じです。COM は同じスレッド上での再入を禁止しませんが、同時実行は禁止します。また、COM 関連の再入を処理する方法も提供しています。詳細については、「関連情報」を参照してください。メソッド インプリメンテーションがそのアパートメントの外に呼び出しを行わない場合、またはメッセージの取り出しとディスパッチを行わない場合、オブジェクトは再入されません。


STA モデルにおけるクライアントの責任


STA モデルを使用するプロセスやスレッドで実行されるクライアントコードは、CoMarshalInterThreadInterfaceInStream および CoGetInterfaceAndReleaseStream を使用して、アパートメント間でオブジェクトのインターフェイスをマーシャリングする必要があります。たとえば、クライアント内のアパートメント 1 がインターフェイス ポインタを持ち、アパートメント 2 がこれを使用する必要がある場合、アパートメント 1 は CoMarshalInterThreadInterfaceInStream を使用してインターフェイスをマーシャリングする必要があります。この関数によって返されたストリーム オブジェクトはスレッド セーフです。そのインターフェイス ポインタは、アパートメント 2 からアクセス可能な直接メモリ変数に格納される必要があります。アパートメント 2 は、このストリーム インターフェイスを CoGetInterfaceAndReleaseStream に渡してオブジェクト上のインターフェイスのアンマーシャリングを行い、オブジェクトへのアクセスに使用できるプロキシへのポインタを取得する必要があります。


プロセスのメイン アパートメントは、クライアントが COM 関連のすべての作業を完了するまで存在し続ける必要があります。これは、一部のインプロセス オブジェクトがメイン アパートメントにロードされるためです (詳細については下記参照のこと)。

マルチスレッド アパートメント モデル

MTA は、CoInitializeEx(NULL, COINIT_MULTITHREADED) を呼び出したプロセス内のすべてのスレッドによって作成または公開されたオブジェクトの集合です。


: COM の現在の実装では、COM を明示的に初期化しないスレッドも、MTA の一部となることができます。COM を初期化しないスレッドが MTA の一部となるのは、プロセス内の少なくとも 1 つのほかのスレッドが CoInitializeEx(NULL, COINIT_MULTITHREADED) を呼び出した後に、このスレッドが COM の使用を開始した場合に限られます。(MTA を明示的に初期化しているクライアント スレッドが存在しない場合、COM 自身が MTA を初期化していることもあり得ます。たとえば、STA に関連付けられたスレッドが "ThreadingModel=Free" としてマークされた CLSID 上で CoGetClassObject/CoCreateInstance[Ex] を呼び出し、COM が黙示的に MTA を作成し、この MTA にクラス オブジェクトがロードされます。) 後述のスレッド モデルの相互運用についての記述を参照してください。


ただし、このような構成は、特定の状況でアクセス違反などの問題を引き起こす可能性があります。このため、COM 関連の作業を行う必要のあるすべてのスレッドが、それぞれ CoInitializeEx を呼び出して COM を初期化し、作業終了時に CoUninitialize を呼び出すことを強く勧めます。MTA を余分に初期化することで生じるコストはごくわずかにすぎません。


MTA モデルの場合、COM がオブジェクトへの呼び出しの配信にウィンドウ メッセージを使用しないため、MTA スレッドはメッセージの取り出しとディスパッチを行う必要はありません。


MTA モデルをサポートするサーバー


MTA モデルでは、オブジェクトへの呼び出しは COM によって同期されません。オブジェクトがこのモデルをサポートしている場合、複数のクライアントが異なるスレッド上のオブジェクトを同時に呼び出すことができます。オブジェクトは、そのインターフェイス/メソッド中で、イベント、ミューテックス、セマフォなどの同期オブジェクトを使用して同期処理を実装する必要があります。MTAオブジェクトは、そのオブジェクトのプロセスに属する (COM によって作成された) スレッドのプールを介して、複数のアウトプロセス クライアントからの同時呼び出しを受け取ることができます。また、MTA オブジェクトは、MTA に関連付けられた複数のスレッド上で、複数のインプロセス クライアントからの同時呼び出しを受け取ることができます。


MTA モデルにおけるクライアントの責任


MTA モデルを使用するプロセスやスレッド内で実行されるクライアント コードでは、ほかの MTA スレッドとの間でオブジェクトのインターフェイス ポインタをマーシャリングする必要はありません。MTA スレッドは、ほかの MTA スレッドから取得したインターフェイス ポインタをメモリ ポインタとして直接使用できます。クライアント スレッドがアウトプロセス オブジェクトに対して呼び出しを行った場合、そのスレッドは呼び出しが完了するまでサスペンドします。発信される呼び出し上で MTA に関連付けられたすべてのアプリケーション作成のスレッドがブロックされている間に、MTA に関連付けられたオブジェクト上に呼び出しが着信する場合があります。このような場合も含めて、一般に、着信した呼び出しは、COM ランタイムによって提供されるスレッド上に配信されます。メッセージ フィルタ (IMessageFilter) は、MTA モデルでは使用されません。

混合スレッド モデル

混合スレッド モデルをサポートするプロセスでは、1 つの MTA と 1 つまたは複数の STA を使用します。インターフェイス ポインタのマーシャリングは、すべてのアパートメント間で必要ですが、MTA 内部では必要ありません。1 つの STA 内のオブジェクトへの呼び出しは、COM によって同期され、1 つのスレッド上でのみ実行されます。これに対し、MTA 内のオブジェクトへの呼び出しは同期されません。ただし、STA から MTA への呼び出しは、通常の場合、システム提供のコードを経由し、オブジェクトに配信される前に STA スレッドから MTA スレッドにスイッチします。


: ポインタを直接使用できるケース、最初に MTA に関連付けられたオブジェクトに対して STA スレッドから直接呼び出しを行う方法、およびその逆に複数のアパートメントから呼び出す方法については、CoCreateFreeThreadedMarshaler() に関する SDK のドキュメントおよび後述の API についての説明を参照してください。

スレッド モデルの選択

コンポーネントは、STA モデル、MTA モデル、またはその両方を組み合わせた混合スレッド モデルのいずれをサポートするかを選択できます。たとえば、大量の I/O を行うオブジェクトでは、I/O 待ち時間にインターフェイス呼び出しを実行できるようにして、クライアントに最大限のレスポンスを提供するために、MTA のサポートを選択できます。一方、ユーザーとやり取りするオブジェクトは、ほとんどすべての場合、着信した COM 呼び出しを GUI 操作と同期させるために、STA のサポートを選択します。COM によって同期処理が提供されるため、STA モデルをサポートするほうが簡単です。MTA モデルのサポートは、オブジェクトのほうで同期処理を実装する必要があるため、より難しくなります。ただし、COM によって提供される同期処理がインターフェイス呼び出し全体を対象とするのに比べて、オブジェクトでの同期処理はより狭い範囲のコードを対象とするため、クライアントへのレスポンスは速くなります。


STA モデルは、MTS (Microsoft Transaction Server、旧コード ネーム "Viper") でも使用されています。このため、MTS 環境で実行する予定の DLL ベースのオブジェクトでは、STA モデルを使用する必要があります。MTA モデル用に実装されたオブジェクトは、通常の場合、MTS 環境でも正常に動作します。ただし、不必要なスレッド同期プリミティブを使用することになるため、実行の効率は低下します。

インプロセス サーバーがサポートするスレッド モデルのマーキング

スレッドが CoInitializeEx (NULL, COINIT_MULTITHREADED) を呼び出すか、または COM を初期化せずに使用した場合、このスレッドは MTA モデルを使用します。スレッドが CoInitialize または CoInitializeEx (NULL, COINIT_APARTMENTTHREADED) を呼び出した場合、このスレッドは STA モデルを使用します。


CoInitialize API は、クライアント コードおよび .EXE にパッケージされたオブジェクトに対し、アパートメント制御を提供します。これは、COM ランタイムのスタートアップ コードを使用して COM を初期化することが望ましいためです。


ただし、インプロセスの (DLL ベースの) COM サーバーは、CoInitialize/CoInitializeEx を呼び出しません。これは、DLL サーバーがロードされるときまでに、これらの API がすでに呼び出されているためです。このため、DLL サーバーは、レジストリを使用して、サーバーがサポートしているスレッド モデルを COM に知らせる必要があります。これによって、COM は、システムの動作がそのモデルと互換性を持つように配慮することができます。コンポーネントの CLSID\InprocServer32 キーの ThreadingModel と呼ばれる名前付き値は、この目的に使用されます。スレッド モデルは次のように指定します。


  • ThreadingModel 値が存在しない場合 : シングル スレッド モデルをサポートする
  • ThreadingModel=Apartment の場合 : STA モデルをサポートする
  • ThreadingModel=Both の場合 : STA モデルと MTA モデルをサポートする。
  • ThreadingModel=Free の場合 : MTA のみをサポートする
: 旧バージョンの Win32 ドキュメントの中には、ThreadingModel が InprocServer32 のサブキーであると記述しているものがありますが、この記述は誤りです。ThreadingModel はサブキーではなく名前付き値です。


インプロセス サーバーのスレッド モデルについては、後で説明します。1 つのインプロセス サーバーが多くのタイプのオブジェクト (それぞれ固有の CLSID を持つ) を提供している場合は、各タイプごとに異なる ThreadingModel 値を設定できます。つまり、スレッド モデルは、コード パッケージ/DLL ごとではなく、CLSID ごとに設定されます。ただし、すべてのインプロセス サーバーの "ブートストラップ" と問い合わせを行うのに必要な API エントリ ポイント (DLLGetClassObject()、DLLCanUnloadNow()) は、複数のスレッドをサポートする (つまり、ThreadingModel 値が Apartment、Both、または Free に設定されている) すべてのインプロセス サーバーにおいてスレッド セーフである必要があります。


前に述べたように、アウトプロセス サーバーの場合は、ThreadingModel 値を使用して自分自身をマークしません。その代わりに、CoInitialize または CoInitializeEx を使用します。COM の "サロゲート" (代理) 機能 (システム提供のサロゲート DLLHOST.EXE など) を使用してアウトプロセスで実行される予定の DLL ベースのサーバーは、単に DLL ベースのサーバーの規則に従います。その場合。特に考慮すべきことはありません。

クライアントおよびオブジェクトが異なるスレッド モデルを使用する場合

クライアントとアウトプロセス オブジェクトとの間のやり取りは、異なるスレッド モデルが使用されている場合でも極めて単純です。これは、クライアントとオブジェクトが異なるプロセスの中にあり、クライアントからオブジェクトへの呼び出しの受け渡しには COM が関係しているためです。クライアントとサーバーとの間に COM が介在するため、スレッド モデルの相互運用のためのコードは COM によって提供されます。たとえば、複数の STA または MTA クライアントによって 1 つの STA オブジェクトが同時に呼び出された場合、COM は、対応するウィンドウ メッセージをサーバーのメッセージ キューに入れることによって、これらの呼び出しを同期します。オブジェクトの STA は、メッセージの取り出しとディスパッチを行うたびに呼び出しを 1 つずつ受け取ります。クライアントとアウトプロセス オブジェクトの間では、スレッド モデルの相互運用のすべての組み合わせが使用可能で、完全にサポートされています。


異なるスレッド モデルを使用するクライアントとインプロセス オブジェクトとの間のやり取りは、もっと複雑になります。サーバーがインプロセスであるにもかかわらず、場合によっては、COM が自分自身をクライアントとオブジェクトとの間に介在させる必要があります。たとえば、STA モデルをサポートするように設計されたインプロセス オブジェクトが、クライアントの複数のスレッドによって同時に呼び出される場合があります。オブジェクトがこのような同時アクセスに対応していないため、COM は、クライアント スレッドがオブジェクトのインターフェイスに直接アクセスするのを許可することはできません。代わりに、呼び出しが同期され、オブジェクトの "コンテナ" にあたる STA に関連付けられたスレッドによってのみ行われるように配慮する必要があります。より複雑であるにもかかわらず、クライアントとインプロセス オブジェクトの間では、スレッド モデルの相互運用のすべての組み合わせが使用可能です。

アウトプロセス (EXE ベース) サーバーにおけるスレッド モデル

以下は、アウトプロセス サーバーの 3 つのカテゴリです。各カテゴリは、クライアントで使用されているスレッド モデルに関係なく、あらゆる COM クライアントで使用できます。


  1. STA モデル サーバー


    サーバーは、1 つまたは複数の STA で COM 関連の作業を行います。着信した呼び出しは COM によって同期され、オブジェクトが作成された STA と関連付けられたスレッドによって配信されます。クラス ファクトリのメソッド呼び出しは、そのクラス ファクトリを登録した STA と関連付けられたスレッドによって配信されます。オブジェクトやクラス ファクトリに同期処理を実装する必要はありません。ただし、複数の STA によって使用されるあらゆるグローバル変数へのアクセスを同期するよう実装しておく必要があります。サーバーは、CoMarshalInterThreadInterfaceInStream および CoGetInterfaceAndReleaseStream を使用して、(ほかのサーバーからの可能性もある) インターフェイス ポインタを STA 間でマーシャリングする必要があります。オプションとして、各 STA に IMessageFilter をインプリメントし、COM による呼び出し配信処理を制御することもできます。低次元の形態として、1 つの STA で COM 関連の作業を行うシングルスレッド モデル サーバーがあります。
  2. MTA モデル サーバー


    サーバーは、1 つまたは複数のスレッドで COM 関連の作業を行います。これらのスレッドはすべて MTA に属しています。呼び出しは COM によって同期されません。COM がサーバー プロセスの中にスレッドのプールを作成し、クライアントの呼び出しはこのプールの中の任意のスレッドによって配信されます。スレッドでは、メッセージの取り出しとディスパッチを行う必要はありません。オブジェクトおよびクラス ファクトリには、同期処理を実装する必要があります。サーバーは、スレッド間でインターフェイス ポインタのマーシャリングを行う必要はありません。
  3. 混合モデル サーバー


    詳細については、この資料の「混合スレッド モデル」を参照してください。

クライアントにおけるスレッド モデル

次の 3 つのカテゴリのクライアントがあります。


  1. STA モデル クライアント


    クライアントは、1 つまたは複数の STA に関連付けられたスレッドで COM 関連の作業を行います。クライアントは、CoMarshalInterThreadInterfaceInStream および CoGetInterfaceAndReleaseStream を使用して、STA 間でインターフェイス ポインタをマーシャリングする必要があります。低次元の形態として、1 つの STA で COM 関連の作業を行うシングル スレッド モデル クライアントがあります。クライアントのスレッドは、呼び出しを発信するときに、COM によって提供されたメッセージ ループに入ります。クライアントは、IMessageFilter を使用してコールバックとウィンドウ メッセージの処理を管理しながら、発信される呼び出しと同時実行に関するその他の問題を処理することができます。
  2. MTA モデル クライアント


    クライアントは、1 つまたは複数のスレッドで COM 関連の作業を行います。これらのスレッドはすべて MTA に属しています。クライアントは、そのスレッド間でインターフェイス ポインタのマーシャリングを行う必要はありません。IMessageFilter は使用できません。クライアントのスレッドは、アウトプロセス オブジェクトへの COM 呼び出しを行うときにサスペンドし、呼び出しが返ってきた時点で実行を再開します。呼び出しは、COM によって作成および管理されるスレッド上に着信されます。
  3. 混合モデル クライアント


    詳細については、この資料の「混合スレッド モデル」を参照してください。

インプロセス (DLL ベース) サーバーにおけるスレッド モデル

インプロセス サーバーには 4 つのカテゴリがあり、各カテゴリは、クライアントで使用されているスレッド モデルに関係なく、あらゆる COM クライアントで使用できます。ただし、スレッド モデルの相互運用をサポートする場合には、一般にクライアント アパートメント間で COM がインターフェイスのマーシャリングを行う必要があるため、インプロセス サーバーがすべての (システム定義でない) カスタム インターフェイスに対してマーシャリング コードを提供する必要があります。以下の 4 つのカテゴリがあります。


  1. シングル スレッド ("メイン" STA) をサポートするインプロセス サーバー - ThreadingModel 値なし


    このサーバーによって提供されるオブジェクトは、そのオブジェクトを作成したのと同じクライアント STA によってアクセスされることを前提としています。また、サーバーは、そのすべてのエントリ ポイント (DllGetClassObject や DllCanUnloadNow など) とグローバル データが、同じスレッド (メイン STA と関連付けられているもの) によってアクセスされることを前提としています。COM にマルチ スレッドが導入される以前から存在するサーバーは、このカテゴリに属します。これらのサーバーは、複数のスレッドによってアクセスされるように設計されていないため、COM は、サーバーによって提供されるすべてのオブジェクトをプロセスのメイン STA の中に作成し、オブジェクトへの呼び出しは、このメイン STA に関連付けられたスレッドによって配信されます。ほかのクライアント アパートメントは、プロキシを介してオブジェクトにアクセスします。ほかのアパートメントからの呼び出しは、プロキシからメイン STA のスタブを経由し (スレッド間マーシャリング)、オブジェクトに到達します。このマーシャリングにより、COM はオブジェクトへの呼び出しを同期することができ、呼び出しはオブジェクトが作成された STA によって配信されます。スレッド間マーシャリングは、直接の呼び出しに比べて時間がかかるため、このようなサーバーは、複数の STA をサポートする (カテゴリ 2) ように書き直すことをお勧めします。
  2. シングルスレッド アパートメント モデル (複数の STA) をサポートするインプロセス サーバー - ThreadingModel=Apartment のマーク


    このサーバーによって提供されるオブジェクトは、そのオブジェクトを作成したのと同じクライアント STA によってアクセスされることを前提としています。この点では、シングルスレッドのインプロセス サーバーによって提供されるオブジェクトに似ています。ただし、このサーバーによって提供されるオブジェクトは、プロセスの複数の STA で作成されている可能性があります。このため、サーバーは、DllGetClassObject や DllCanUnloadNow などのエントリ ポイントおよびグローバル データを、マルチスレッドで使用できるように設計する必要があります。たとえば、1 つのプロセスの 2 つの STA が、インプロセス オブジェクトの 2 つのインスタンスを同時に作成すると、これらの STA によって DllGetClassObject が同時に呼び出されることがあります。同様に、DllCanUnloadNow についても、サーバー内でまだコードが実行されている間にサーバーがアンロードされないように記述する必要があります。


    サーバーが、すべてのオブジェクトの作成用にクラス ファクトリのインスタンスを 1 つしか提供していない場合は、クラス ファクトリの実装も複数のクライアント STA によってアクセスされるため、マルチスレッドで使用できるように設計する必要があります。DllGetClassObject が呼び出されるたびにサーバーがクラス ファクトリの新しいインスタンスを作成する場合は、クラス ファクトリがスレッド セーフでなくてもかまいません。ただし、すべてのグローバル変数へのアクセスを同期するよう実装する必要があります。


    クラス ファクトリによって作成される COM オブジェクトは、スレッド セーフでなくてもかまいません。ただし、グローバル変数のアクセスの同期を実装しておく必要があります。オブジェクトがスレッドによって作成されると、そのオブジェクトは常にそのスレッドを通してアクセスされ、そのオブジェクトへの呼び出しはすべて COM によって同期されます。オブジェクトが作成された STA 以外のクライアント アパートメントは、プロキシを介してそのオブジェクトにアクセスする必要があります。これらのプロキシは、クライアントがアパートメント間でインターフェイスのマーシャリングを行う際に作成されます。


    クラス ファクトリを経由して STA オブジェクトを作成したクライアントは、すべてそのオブジェクトへの直接ポインタを獲得します。この点は、シングルスレッドのインプロセス オブジェクトと異なります。シングルスレッドのインプロセス オブジェクトの場合は、クライアントのメイン STA のみがオブジェクトへのダイレクト ポインタを獲得し、オブジェクトを作成したほかのすべての STA は、プロキシを介してそのオブジェクトにアクセスします。スレッド間マーシャリングは直接の呼び出しに比べて時間がかかるため、シングルスレッドのインプロセス サーバーを変更して複数の STA をサポートするようにすると、速度が大幅に向上する可能性があります。
  3. MTA のみをサポートするインプロセス サーバー - ThreadingModel=Free のマーク


    このサーバーによって提供されるオブジェクトは、MTA に対してのみ安全です。オブジェクトには独自の同期処理が実装され、複数のクライアント スレッドから同時にアクセスされます。このサーバーは、STA モデルと互換性のない動作をする可能性があります (たとえば、STA のメッセージ ポンプを破壊するような方法で Windows メッセージ キューを使用した場合など)。また、オブジェクトのスレッド モデルを "Free" としてマークすることで、オブジェクトを次のように実装することができます。すなわち、このオブジェクトはどのようなクライアント スレッドからも呼び出せますが、作成したすべてのスレッドに対して直接 (マーシャリングなしで) インターフェイス ポインタを渡すこともでき、これらのスレッドはこれらのポインタを通して呼び出しを行うことができます。したがって、クライアントが、クライアントによって実装されたオブジェクト (シンクなど) へのインターフェイス ポインタをこのオブジェクトに渡した場合、クライアントは、作成したどのスレッドからでも、このインターフェイス ポインタを通してコールバックすることができます。クライアントが STA の場合、シンク オブジェクトを作成したスレッド以外のスレッドからの直接の呼び出しは (上記の 2 で示したように) エラーになります。このため、COM は、常に、STA に関連付けられたスレッド内のクライアントがこのタイプのインプロセス オブジェクトに対してプロキシを通してのみアクセスするように配慮します。また、これらのオブジェクトでは、STA スレッド上での直接の実行を可能にするフリースレッド マーシャラとの集成を行わないでください。
  4. アパートメント モデルおよびフリースレッドをサポートするインプロセス サーバー - ThreadingModel=Both のマーク


    このサーバーによって提供されるオブジェクトは、独自の同期処理を実装し、複数のクライアント アパートメントから同時にアクセスされます。さらに、このオブジェクトは、クライアント プロセスの STA または MTA において、プロキシを通さずに直接作成および使用されます。オブジェクトが STA 内で直接使用されるため、サーバーは、(ほかのサーバーからの可能性もある) オブジェクトのインターフェイスをスレッド間でマーシャリングする必要があります。これにより、あらゆるオブジェクトに対するアクセスがスレッドに適した方法で行われることが保証されます。また、オブジェクトのスレッド モデルを "Both" としてマークすることで、オブジェクトを次のように実装することができます。すなわち、このオブジェクトはどのようなクライアント スレッドからも呼び出せますが、このオブジェクトからクライアントへのコールバックは、オブジェクトがコールバック オブジェクトへのインターフェイス ポインタを受け取ったアパートメント上でのみ行われます。COM は、クライアント プロセスの STA および MTA において、このようなオブジェクトが直接作成されるのを許可します。


    このようなオブジェクトを作成したあらゆるアパートメントが常にプロキシ ポインタではなく直接ポインタを得るため、ThreadingModel が "Both" のオブジェクトは、STA にロードされた場合、ThreadingModel が "Free" のオブジェクトよりもパフォーマンスが向上します。


    ThreadingModel が "Both" のオブジェクトは、MTA アクセスにも対応するように設計されている (内部的に完全にスレッドセーフである) ため、CoCreateFreeThreadedMarshaler によって提供されるマーシャラと集成することによってパフォーマンスを上げることができます。このシステム提供のオブジェクトは、あらゆる呼び出し元のオブジェクトと集成され、オブジェクトへのダイレクト ポインタを、プロセス内のすべてのアパートメントの中へカスタム マーシャリングします。これにより、STA か MTA であるかに関係なく、あらゆるアパートメントのクライアントが、プロキシを通さずに、直接オブジェクトにアクセスできるようになります。たとえば、STA モデルのクライアントが STA1 でインプロセス オブジェクトを作成し、このオブジェクトを STA2 にマーシャリングするとします。このオブジェクトがフリースレッド マーシャラと集成されない場合、STA2 はプロキシを通してオブジェクトにアクセスします。集成された場合は、フリースレッド マーシャラが、STA2 に対してオブジェクトへのダイレクト ポインタを提供します。


    : フリースレッド マーシャラとの集成は、慎重に行う必要があります。例として、ThreadingModel が "Both" にマークされた (フリースレッド マーシャラと集成している) オブジェクトが、ThreadingModel が "Apartment" の別のオブジェクトへのインターフェイス ポインタであるデータ メンバーを持っていると想定します。STA が第 1 のオブジェクトを作成し、この作成中に第 1 のオブジェクトが第 2 のオブジェクトを作成したとします。前述の規則によれば、第 1 のオブジェクトが第 2 のオブジェクトへの直接ポインタを保持していることになります。この状態で、STA が別のアパートメントに対して第 1 のオブジェクトへのインターフェイス ポインタのマーシャリングを行ったとします。第 1 のオブジェクトはフリースレッド マーシャラと集成しているため、第 1 のオブジェクトへのダイレクト ポインタは、第 2 のアパートメントに渡されます。第 2 のアパートメントがこのポインタを通して呼び出しを行い、この呼び出しによって第 1 のオブジェクトが第 2 のオブジェクトへのインターフェイス ポインタを通して呼び出しを行うと、エラーが発生します。これは、第 2 のオブジェクトが、第 2 のアパートメントから直接呼び出されるようになっていないためです。第 1 のオブジェクトが、第 2 のオブジェクトへのダイレクト ポインタでなくプロキシを保持している場合は、異なるエラーが発生します。システム プロキシは、1 つのアパートメントのみ関連付けられている COM オブジェクトでもあり、特定の循環状況を回避するために、そのアパートメントの履歴を保持しています。したがって、オブジェクトが、実行されているスレッドと異なるアパートメントと関連付けられているプロキシ上から呼び出しを行った場合、そのプロキシから RPC_E_WRONG_THREAD が返され、呼び出しは失敗します。

クライアントとインプロセス オブジェクト間でのスレッド モデルの相互運用

クライアントとインプロセス オブジェクトとの間では、スレッド モデルの相互運用のすべての組み合わせが使用可能です。


COM は、クライアントのメイン STA でオブジェクトの作成とアクセスを行い、CoCreateInstance [Ex] を呼び出したクライアント STA に対してマーシャリングを行うことによって、すべての STA モデル クライアントと、シングル スレッドのインプロセス オブジェクトとの相互運用を可能にします。


クライアント内の MTA が STAモデルのインプロセス サーバーを作成すると、COM によってクライアント内の "ホスト" STA が起動されます。このホスト STA がオブジェクトを作成し、インターフェイス ポインタは MTA にマーシャリングされます。同様に、STA が MTA インプロセス サーバーを作成する場合は、COM によってホスト MTA がスピンアップされ、このホスト MTA の中にオブジェクトが作成され、STA にマーシャリングされます。シングル スレッド モデルは単に STA モデルの低次元のケースであるため、シングル スレッド モデルと MTA モデルとの相互運用についても同様に処理されます。


クライアント アパートメント間で COM によるインターフェイスのマーシャリングを必要とする相互運用性をサポートする場合には、インプロセス サーバーが実装するすべてのカスタム インターフェイスについてマーシャリング コードを用意する必要があります。詳細については、下記の「関連情報」を参照してください。

スレッド モデルと返されるクラス ファクトリ オブジェクトとの関係

インプロセス サーバーがアパートメントに "ロードされている" 状態の正確な定義は、以下の 2 つの手順で説明されます。


  1. インプロセス サーバー クラスを含む DLL がそれ以前にオペレーティング システムのローダーによってロードされていない (プロセスのアドレス空間にマップされていない) 場合は、この操作が実行され、COM は DLL によってエクスポートされた DLLGetClassObject 関数のアドレスを取得します。DLL がそれ以前に任意のアパートメントに関連付けられたスレッドによってロードされている場合、この手順はスキップされます。
  2. COM は、"ロードしている" アパートメントに関連付けられているスレッド (MTA の場合は、その中の 1 つのスレッド) を使用して、DLL によってエクスポートされた DllGetClassObject 関数を呼び出し、必要なクラスの CLSID を要求します。その後、返されたファクトリ オブジェクトを使用して、そのクラスのオブジェクトのインスタンスが作成されます。


    2 番目の手順 (COM による DllGetClassObject の呼び出し) は、同じアパートメントからの呼び出しも含め、クライアントが CoGetClassObject/CoCreateIntance[Ex] を呼び出すたびに発生します。つまり、DllGetClassObject は、同じアパートメントに関連付けられたスレッドによって何度も呼び出される場合があります。これは、そのアパートメント内で、そのクラスのクラス ファクトリ オブジェクトにアクセスしようとしているクライアントの数に依存します。
DllGetClassObject 関数呼び出しに対してどのファクトリ オブジェクトを返すかは、クラス インプリメンテーションの作成者、そして最終的には一群のクラスで構成される DLL パッケージの作成者が自由に決定できます。、実行内容を最初に、あるいは最終的に決定する権限を持つのは、単一の DLL における DllGetClassObject() エントリ ポイントの後ろにあるコードであるため、最終的な決定は DLL パッケージの作成者に委ねられます。一般には、次の 3 つのケースが考えられます。


  1. DllGetClassObject は、スレッドを呼び出すたびに固有のクラス ファクトリ オブジェクトを返します (つまり、STA ごとに 1 つのクラス ファクトリ オブジェクトが返され、MTA 内では複数のクラス ファクトリが返される可能性があります)。
  2. DllGetClassObject は、呼び出し元のスレッドが何であるか、またはそのスレッドにどのような種類のアパートメントが関連付けられているかにかかわらず、常に同じクラス ファクトリ オブジェクトを返します。
  3. DllGetClassObject は、アパートメントを呼び出すたびに固有のクラス ファクトリ オブジェクトを返します (STA および MTA いずれについても、アパートメントごとに 1つのクラス ファクトリ オブジェクトが返されます)。
DllGetClassObject への呼び出しと、実際に返されるクラス ファクトリ オブジェクトとの間の関係には、これ以外のケース (DllGetClassObject への呼び出しごとに新しいクラス ファクトリ オブジェクトが返されるなど) も考えられますが、現在のところ特別な利点があるとは思われません。

インプロセス サーバーにおけるクライアントとオブジェクトのスレッド モデルのまとめ

以下の表は、クライアント スレッドがインプロセス サーバーとして実装されているクラス上で CoGetClassObject を最初に呼び出すときの、異なるスレッド モデル間のやり取りについてまとめたものです。


クライアント/スレッドの種類


  • クライアントが "メイン" STA (CoInitialize または COINIT_APARTMENTTHREADED フラグ付きの CoInitializeEx を最初に呼び出したスレッド) - 下の表では STA0 と記述 - に関連付けられているスレッド内で実行されている場合 (シングル スレッド モデルとも呼ばれます)
  • クライアントがその他の STA - 下の表では STA* と記述 - に関連付けられたスレッド内で実行されている場合
  • クライアントが MTA に関連付けられたスレッド内で実行されている場合
DLL サーバーの種類


  • サーバーが ThreadingModel キーを持たない場合 - 下の表では "None" と記述
  • サーバーが "Apartment" としてマークされている場合 - 下の表では "Apt" と記述
  • サーバーが "Free " としてマークされている場合
  • サーバーが "Both" としてマークされている場合
以下の表を見る場合には、アパートメントへのサーバーの "ロード" に関する上記の定義を参照してください。

クライアント サーバー 結果
------ ------ -----------------------------------------
STA0 None 直接アクセス。サーバーは STA0 にロードされる。
STA* None プロキシ アクセス。サーバーは STA0 にロードされる。
MTA None プロキシ アクセス。サーバーは STA0 にロードされる。
STA0 は必要に応じて COM によって自動的に作成される。
STA0 Apt 直接アクセス。サーバーは STA0 にロードされる。
STA* Apt 直接アクセス。サーバーは STA* にロードされる。
MTA Apt プロキシ アクセス。サーバーは COM によって
自動的に作成された STA にロードされる。
STA0 Free プロキシ アクセス。サーバーは MTA にロードされる。
MTA は必要に応じて COM によって自動的に作成される。
STA* Free STA0 -> Free の場合と同じ
MTA Free 直接アクセス。
STA0 Both 直接アクセス。サーバーは STA0 にロードされる。
STA* Both 直接アクセス。サーバーは STA* にロードされる。
MTA Both 直接アクセス。サーバーは MTA にロードされる。

関連情報


CoRegisterMessageFilter() および IMessageFilter インターフェイスに関する SDK のドキュメント


関連情報については、次の「サポート技術情報」 (Microsoft Knowledge Base) の資料を参照してください。
136885 OLE Threads Must Dispatch Messages
137629 In-proc Object Custom Interface in Apartment Model Client

関連情報


この資料は米国 Microsoft Corporation から提供されている Knowledge Base の Article ID
150777 (最終更新日 2000-12-15) をもとに作成したものです。