次の方法で共有


OLE スレッド モデルの説明と動作

この記事では、OLE スレッド モデルについて説明します。

元の製品バージョン: OLE スレッド モデル
元の KB 番号: 150777

まとめ

COM オブジェクトは、プロセスの複数のスレッドで使用できます。 "シングル スレッド アパートメント" (STA) と "マルチスレッド アパートメント" (MTA) という用語は、オブジェクトとスレッド間の関係、オブジェクト間のコンカレンシーリレーションシップ、メソッド呼び出しがオブジェクトに配信される手段、およびスレッド間でインターフェイス ポインターを渡すためのルールを記述するための概念フレームワークを作成するために使用されます。 コンポーネントとそのクライアントは、現在 COM でサポートされている次の 2 つのアパートメント モデルから選択します。

  1. シングル スレッド アパートメント モデル (STA): プロセス内の 1 つ以上のスレッドが COM を使用し、COM オブジェクトの呼び出しが COM によって同期されます。 インターフェイスはスレッド間でマーシャリングされます。 特定のプロセス内の 1 つのスレッドのみが COM を使用するシングルスレッド アパートメント モデルの縮退ケースは、シングルスレッド モデルと呼ばれます。 以前は、STA モデルを単に "アパートメント モデル" と呼ぶこともあります。

  2. マルチスレッド アパートメント モデル (MTA): 1 つ以上のスレッドが COM を使用し、MTA に関連付けられている COM オブジェクトへの呼び出しは、呼び出し元とオブジェクトの間でシステム コードを相互に組み合わせることなく、MTA に関連付けられているすべてのスレッドによって直接行われます。 複数の同時クライアントが同時に (マルチプロセッサ システムで同時に) オブジェクトを多かれ少なかれ呼び出す可能性があるため、オブジェクトは内部状態を自分で同期する必要があります。 インターフェイスはスレッド間でマーシャリングされません。 以前は、このモデルを "フリースレッド モデル" と呼ぶこともあります。

  3. STA モデルと MTA モデルの両方を同じプロセスで使用できます。 これは、"混合モデル" プロセスと呼ばれることもあります。

MTA は NT 4.0 で導入され、Windows 95 と DCOM95 で使用できます。 STA モデルは、Windows NT 3.51 と Windows 95 のほか、NT 4.0 と Windows 95 (DCOM95) にも存在します。

概要

COM のスレッド モデルは、さまざまなスレッド アーキテクチャを使用するコンポーネントが連携するメカニズムを提供します。 また、同期サービスを必要とするコンポーネントにも提供します。 たとえば、特定のオブジェクトは、1 つのスレッドによってのみ呼び出されるように設計され、クライアントからの同時呼び出しを同期しない場合があります。 このようなオブジェクトが複数のスレッドによって同時に呼び出されると、クラッシュまたはエラーが発生します。 COM には、スレッド アーキテクチャのこの相互運用性を処理するためのメカニズムが用意されています。

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

"アパートメント" には、相互に関連するいくつかの側面があります。 1 つ目は、スレッドと COM オブジェクトのセットの関係など、コンカレンシーについて考える論理構造です。 2 つ目は、プログラマが COM 環境から期待されるコンカレンシー動作を受け取るために従う必要がある一連のルールです。 最後に、COM オブジェクトに関するスレッドコンカレンシーをプログラマが管理するのに役立つシステム提供のコードです。

"アパートメント" という用語は、プロセスが個別のエンティティとして考えられるメタファーに由来します。たとえば、"建物" は、"アパートメント" と呼ばれる関連する異なる "ロケール" のセットに分割されます。アパートメントは、オブジェクトと場合によってはスレッド間の関連付けを作成する "論理コンテナー" です。 スレッドはアパートメントではありませんが、STA モデルのアパートメントに論理的に関連付けられた 1 つのスレッドが存在する可能性があります。 オブジェクトはアパートメントではありませんが、すべてのオブジェクトは 1 つのアパートメントにのみ関連付けられます。 しかし、アパートは単なる論理構造だけではありません。これらの規則は、COM システムの動作を記述します。 アパートメント モデルのルールに従わないと、COM オブジェクトは正しく機能しません。

[詳細]

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

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

"main 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 内のスレッドは、関連するオブジェクトが着信呼び出しを受信するためにウィンドウ メッセージを取得してディスパッチする必要はありません。 COM は MTA 内のオブジェクトへの呼び出しを同期しません。 MTA 内のオブジェクトは、複数の同時スレッドの相互作用によって内部状態を破損から保護する必要があり、異なるメソッド呼び出し間でスレッド ローカル ストレージの残りの定数の内容について想定することはできません。

プロセスには任意の数の STA を含めることができますが、最大でも 1 つの MTA を持つことができます。 MTA は、1 つ以上のスレッドで構成されます。 STA には、それぞれ 1 つのスレッドがあります。 スレッドは、最大でも 1 つのアパートメントに属します。 オブジェクトは 1 つのアパートメントにのみ属します。 インターフェイス ポインターは常にアパートメント間でマーシャリングする必要があります (ただし、マーシャリングの結果はプロキシではなく直接ポインターである可能性があります)。 CoCreateFreeThreadedMarshalerに関する以下の情報を参照してください。

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

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

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

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

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

複数のクライアントが STA オブジェクトを呼び出すと、STA で使用される制御メカニズムの転送によって、呼び出しがメッセージ キューに自動的にキューに入れられます。 オブジェクトは、STA がメッセージを取得してディスパッチするたびに呼び出しを受け取ります。 呼び出しはこの方法で COM によって同期され、呼び出しは常にオブジェクトの STA に関連付けられている単一のスレッドで配信されるため、オブジェクトのインターフェイス実装で同期を提供する必要はありません。

Note

インターフェイス メソッドの実装がメソッド呼び出しの処理中にメッセージを取得してディスパッチし、同じ STA によって別の呼び出しがオブジェクトに配信される場合は、オブジェクトを再入力できます。 これが発生する一般的な方法は、STA オブジェクトが COM を使用してアウトイング (クロスアパートメント/クロスプロセス) 呼び出しを行う場合です。 これは、ウィンドウ プロシージャがメッセージの処理中にメッセージを取得してディスパッチする場合に再入力できる方法と同じです。 COM は、同じスレッドでの再入を防ぐのではなく、同時実行を防ぎます。 また、COM 関連の再入を管理するための手段も提供します。 詳細については、以下の REFERENCES セクションを参照してください。 メソッドの実装がアパートメントを呼び出さない場合、またはメッセージを取得してディスパッチしない場合、オブジェクトは再入力されません。

STA モデルでのクライアントの責任:

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

特定のプロセスのメイン アパートメントは、一部のインプロセス オブジェクトがメイン アパートメントに読み込まれるため、クライアントがすべての COM 作業を完了するまで存続する必要があります。 (詳細については、以下で詳しく説明します)。

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

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

Note

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

ただし、これは、特定の状況でアクセス違反などの問題を引き起こす可能性のある構成です。 したがって、COM 作業を行う必要がある各スレッドは、 CoInitializeEx を呼び出して COM を初期化し、COM の作業が完了したら、 CoUninitializeを呼び出すことをお勧めします。 MTA を "不必要に" 初期化するコストは最小限です。

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

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

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

  • MTA モデルでのクライアントの責任:

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

混合スレッド モデル

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

Note

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

スレッド モデルの選択

コンポーネントは、混合スレッド モデルを使用して、STA モデル、MTA モデル、または 2 つの組み合わせをサポートすることを選択できます。 たとえば、広範な I/O を実行するオブジェクトは、I/O 待機時間中にインターフェイス呼び出しを実行できるようにすることで、MTA をサポートしてクライアントに最大応答を提供することを選択できます。 または、ユーザーと対話するオブジェクトは、ほとんどの場合、受信 COM 呼び出しを GUI 操作と同期するために STA をサポートすることを選択します。 COM では同期が提供されるため、STA モデルのサポートが簡単です。 オブジェクトで同期を実装する必要があるため、MTA モデルのサポートは難しくなりますが、COM によって提供されるインターフェイス呼び出し全体ではなく、コードの小さなセクションで同期が使用されるため、クライアントへの応答が優れています。

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

In-Proc サーバーのサポートされているスレッド モデルのマーキング

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

CoInitialize API は、クライアント コードとパッケージ化されたオブジェクトのアパートメント制御を提供します。COM ランタイムのスタートアップ コードで必要な方法で COM を初期化できるため、EXEs。

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

  • ThreadingModel 値が存在しない: シングル スレッド モデルをサポートします。
  • ThreadingModel=Apartment: STA モデルをサポートします。
  • ThreadingModel=Both: STA および MTA モデルをサポートします。
  • ThreadingModel=Free: MTA のみをサポートします。

Note

ThreadingModel は名前付き値であり、以前のバージョンの Win32 ドキュメントに誤って記載されている InprocServer32 のサブキーではありません。

インプロセス サーバーのスレッド モデルについては、この記事の後半で説明します。 インプロセス・サーバーが多数のタイプのオブジェクト (それぞれ固有の CLSID を持つ) を提供する場合、各タイプは異なる ThreadingModel 値を持つことができます。 つまり、スレッド モデルは CLSID 単位であり、コード パッケージ/DLL ごとではありません。 ただし、"ブートストラップ" してすべてのインプロセス サーバー (DLLGetClassObject()DLLCanUnloadNow()) に対してクエリを実行するために必要な API エントリ ポイントは、複数のスレッドをサポートするインプロセス サーバー (つまり、アパートメント、両方、または Free の ThreadingModel 値) に対してスレッド セーフである必要があります。

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

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

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

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

アウトプロセス (EXE ベース) サーバーでのモデルのスレッド処理

アウトプロセス サーバーの 3 つのカテゴリを次に示します。各サーバーは、そのクライアントによって使用されるスレッド モデルに関係なく、どの COM クライアントでも使用できます。

  1. STA モデル サーバー:

    サーバーは、1 つ以上の STA で COM が機能します。 着信呼び出しは COM によって同期され、オブジェクトが作成された STA に関連付けられているスレッドによって配信されます。 クラス ファクトリ メソッドの呼び出しは、クラス ファクトリを登録した STA に関連付けられているスレッドによって配信されます。 オブジェクトとクラス ファクトリは、同期を実装する必要はありません。 ただし、実装者は、複数の STA で使用されるグローバル変数へのアクセスを同期する必要があります。 サーバーは、 CoMarshalInterThreadInterfaceInStreamCoGetInterfaceAndReleaseStream を使用して、場合によっては他のサーバーからのインターフェイス ポインターを STA 間でマーシャリングする必要があります。 必要に応じて、サーバーは各 STA に IMessageFilter を実装して、COM による通話配信の側面を制御できます。 逆のケースは、COM が 1 つの STA で機能するシングル スレッド モデル サーバーです。

  2. MTA モデル サーバー:

    サーバーは、1 つ以上のスレッドで COM が機能し、そのすべてが MTA に属します。 呼び出しは COM によって同期されません。 COM はサーバー プロセスでスレッドのプールを作成し、クライアント呼び出しはこれらのスレッドのいずれかによって配信されます。 スレッドは、メッセージを取得してディスパッチする必要はありません。 オブジェクトとクラス ファクトリは、同期を実装する必要があります。 サーバーは、スレッド間のインターフェイス ポインターをマーシャリングする必要はありません。

  3. 混合モデル サーバー:

    詳細については、この記事の「混合スレッド モデル」というタイトルのセクションを参照してください。

クライアントでのモデルのスレッド化

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

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

    クライアントは、1 つ以上の STA に関連付けられているスレッドで COM が機能します。 クライアントは、 CoMarshalInterThreadInterfaceInStreamCoGetInterfaceAndReleaseStream を使用して、STA 間のインターフェイス ポインターをマーシャリングする必要があります。 縮退のケースは、COM が 1 つの STA で動作するシングル スレッド モデル クライアントです。 クライアントのスレッドは、発信呼び出しを行うときに COM によって提供されるメッセージ ループに入ります。 クライアントは、 IMessageFilter を使用して、呼び出しやその他のコンカレンシーの問題を待機しながら、コールバックとウィンドウ メッセージの処理を管理できます。

  2. MTA モデル クライアント:

    クライアントは、1 つ以上のスレッドで COM が機能し、そのすべてが MTA に属します。 クライアントは、そのスレッド間でインターフェイス ポインターをマーシャリングする必要はありません。 クライアントは IMessageFilterを使用できません。 クライアントのスレッドは、プロセス外のオブジェクトに対して COM 呼び出しを行うと中断され、呼び出しが戻ったときに再開されます。 着信呼び出しは、COM によって作成され、管理されたスレッドに到着します。

  3. 混合モデル クライアント:

    詳細については、この記事の「混合スレッド モデル」というタイトルのセクションを参照してください。

インプロセス (DLL ベース) サーバーのスレッド モデル

インプロセス サーバーには 4 つのカテゴリがあり、各サーバーは、そのクライアントによって使用されるスレッド モデルに関係なく、任意の COM クライアントで使用できます。 ただし、インプロセス サーバーは、スレッド モデルの相互運用性をサポートする場合に実装するカスタム (非システム定義) インターフェイスのマーシャリング コードを提供する必要があります。これは通常、COM がクライアント アパートメント間のインターフェイスをマーシャリングする必要があるためです。 次の 4 つのカテゴリがあります。

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

    このサーバーによって提供されるオブジェクトは、作成されたのと同じクライアント STA によってアクセスされる必要があります。 さらに、サーバーは、 DllGetClassObjectDllCanUnloadNowなど、すべてのエントリ ポイントと、同じスレッド (メイン STA に関連付けられているもの) によってグローバル データにアクセスすることを想定しています。 COM でのマルチスレッドの導入前に存在していたサーバーは、このカテゴリに含まれています。 これらのサーバーは複数のスレッドによってアクセスされるようには設計されていないため、COM はサーバーによって提供されるすべてのオブジェクトをプロセスのメイン STA に作成し、オブジェクトの呼び出しはメイン STA に関連付けられているスレッドによって配信されます。 他のクライアント アパートメントは、プロキシを介してオブジェクトにアクセスできます。 他のアパートメントからの呼び出しは、プロキシからメイン STA のスタブ (スレッド間マーシャリング) に移動し、オブジェクトに移動します。 このマーシャリングにより、COM はオブジェクトへの呼び出しを同期でき、呼び出しはオブジェクトが作成された STA によって配信されます。 スレッド間マーシャリングは直接呼び出しに比べて低速であるため、複数の STA (カテゴリ 2) をサポートするようにこれらのサーバーを書き換えるようにすることをお勧めします。

  2. シングルスレッド アパートメント モデル (複数の STA) をサポートする In-proc Server - ThreadingModel=Apartmentでマークされています。

    このサーバーによって提供されるオブジェクトは、作成されたのと同じクライアント STA によってアクセスされる必要があります。 そのため、シングル スレッドのインプロセス サーバーによって提供されるオブジェクトに似ています。 ただし、このサーバーによって提供されるオブジェクトはプロセスの複数の STA に作成できるため、サーバーは、 DllGetClassObjectDllCanUnloadNowなどのエントリ ポイントと、マルチスレッドで使用するためのグローバル データを設計する必要があります。 たとえば、プロセスの 2 つの STA がインプロセス オブジェクトの 2 つのインスタンスを同時に作成する場合、 DllGetClassObject は両方の STA によって同時に呼び出される可能性があります。 同様に、 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. アパートメント モデルとフリー スレッドをサポートする In-proc Server - 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 にオブジェクトへの直接ポインターを提供します。

    Note

    フリー スレッド マーシャラーを使用して集計する場合は、注意が必要です。 たとえば、 ThreadingModel "Both" としてマークされているオブジェクト (およびフリー スレッド マーシャラーによる集計) には、 ThreadingModel が "Apartment" である別のオブジェクトへのインターフェイス ポインターであるデータ メンバーがあるとします。 次に、STA が最初のオブジェクトを作成し、作成時に最初のオブジェクトが 2 番目のオブジェクトを作成すると仮定します。 上記の規則に従って、最初のオブジェクトは 2 番目のオブジェクトへの直接ポインターを保持するようになりました。 次に、STA が最初のオブジェクトへのインターフェイス ポインターを別のアパートメントにマーシャリングするとします。 最初のオブジェクトはフリー スレッド マーシャラーで集計されるため、最初のオブジェクトへの直接ポインターが 2 番目のアパートメントに与えられます。 その後、2 番目のアパートメントがこのポインターを呼び出し、この呼び出しによって最初のオブジェクトが 2 番目のオブジェクトへのインターフェイス ポインターを介して呼び出された場合、2 番目のオブジェクトが 2 番目のアパートメントから直接呼び出されるわけではないため、エラーが発生しました。 最初のオブジェクトが直接ポインターではなく、2 番目のオブジェクトへのプロキシへのポインターを保持している場合は、別のエラーが発生します。 システム プロキシは、1 つのアパートメントにのみ関連付けられている COM オブジェクトでもあります。 彼らは特定の円形を避けるために彼らのアパートを追跡します。 そのため、オブジェクトが実行されているスレッドとは別のアパートメントに関連付けられているプロキシで呼び出すオブジェクトは、プロキシから返されたRPC_E_WRONG_THREADを受け取り、呼び出しは失敗します。

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

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

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

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

クライアント アパートメント間のインターフェイスをマーシャリングするために COM を必要とする相互運用性をサポートする場合は、インプロセス サーバーが実装するカスタム インターフェイスに対してマーシャリング コードを提供する必要があります。 詳細については、以下の REFERENCES セクションを参照してください。

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

次の 2 つの手順で、インプロセス サーバーが "読み込まれる" アパートメントの正確な定義について説明します。

  1. インプロセス サーバー クラスを含む DLL がオペレーティング システム ローダーによって以前に読み込まれていない (プロセス アドレス空間にマップされている) 場合、その操作が実行され、COM は DLL によってエクスポートされた DLLGetClassObject 関数のアドレスを取得します。 DLL が以前に任意のアパートメントに関連付けられているスレッドによって読み込まれている場合、このステージはスキップされます。

  2. COM は、"読み込み" アパートメントに関連付けられているスレッド (または MTA の場合はスレッドの 1 つ) を使用して、必要なクラスの CLSID を要求する DLL によってエクスポートされた DllGetClassObject 関数を呼び出します。 返されたファクトリ オブジェクトは、クラスのオブジェクトのインスタンスを作成するために使用されます。

    2 番目の手順 (COM による DllGetClassObject の呼び出し) は、同じアパートメント内からでも、クライアントが CoGetClassObject/CoCreateIntance[Ex]を呼び出すたびに発生します。 言い換えると、 DllGetClassObject は、同じアパートメントに関連付けられているスレッドによって何度も呼び出される可能性があります。そのアパートメント内のクライアントが、そのクラスのクラス ファクトリ オブジェクトにアクセスしようとしているクライアントの数によって異なります。

クラス実装の作成者、および最終的には、特定のクラス セットの DLL パッケージの作成者は、 DllGetClassObject 関数呼び出しに応答して返すファクトリ オブジェクトを完全に自由に決定できます。 DLL パッケージの作成者は、単一の DLL 全体の DllGetClassObject() エントリ ポイントのコードが、何を行うかを決定する最初の最終的な権利を持っているため、最終的な言い分を持っています。 3 つの一般的な可能性は次のとおりです。

  1. DllGetClassObject は、呼び出し元スレッドごとに一意のクラス ファクトリ オブジェクトを返します (STA あたり 1 つのクラス ファクトリ オブジェクトと MTA 内の複数のクラス ファクトリを意味します)。

  2. DllGetClassObject 呼び出し元スレッドの ID や呼び出し元スレッドに関連付けられているアパートメントの種類に関係なく、常に同じクラス ファクトリ オブジェクトが返されます。

  3. DllGetClassObject は、呼び出し元のアパートメントごとに一意のクラス ファクトリ オブジェクトを返します (STA と MTA の両方でアパートメントごとに 1 つ)。

DllGetClassObjectの呼び出しと返されたクラス ファクトリ オブジェクト (DllGetClassObjectの呼び出しごとの新しいクラス ファクトリ オブジェクトなど) の間には他にも関係がある可能性がありますが、現時点では役に立たないようです。

In-Proc サーバーのクライアント およびオブジェクト スレッド モデルの概要

次の表は、クライアント スレッドがインプロセス サーバーとして実装されているクラスで最初に CoGetClassObject を呼び出すときの、さまざまなスレッド モデル間の相互作用をまとめたものです。

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

  • クライアントは、"main" STA (CoInitializeを呼び出す最初のスレッドまたは COINIT_APARTMENTTHREADED フラグを持つCoInitializeEx) に関連付けられたスレッドで実行されています。この STA0 (単一スレッド モデルとも呼ばれます) を呼び出します。
  • クライアントが他の STA [ASCII 150] に関連付けられているスレッドで実行されている場合は、この STA* を呼び出します。
  • クライアントは MTA に関連付けられているスレッドで実行されています。

DLL サーバーの種類:

  • サーバーに ThreadingModel キーがありません。この "None" を呼び出します。
  • サーバーは "アパートメント" とマークされています。この "Apt" を呼び出します。
  • サーバーは "無料" とマークされています。
  • サーバーは "両方" とマークされています。

以下の表を読むときは、上の定義がサーバーをアパートメントに"読み込む"ことを念頭に置いておきます。

Client         Server                 Result
STA0           None                   Direct access; server loaded into STA0  
STA*           None                   Proxy access; server loaded into STA0.  
MTA            None                   Proxy access; server loaded into STA0; STA0 created automatically by COM if necessary;  
STA0           Apt                    Direct access; server loaded into STA0  
STA*           Apt                    Direct access; server loaded into STA*  
MTA            Apt                    Proxy access; server loaded into an STA created automatically by COM.
STA0           Free                   Proxy access; server is loaded into MTA MTA created automatically by COM if necessary.
STA*           Free                   Same as STA0->Free
MTA            Free                   Direct access
STA0           Both                   Direct access; server loaded into STA0
STA*           Both                   Direct access; server loaded into STA*
MTA            Both                   Direct access; server loaded into the MTA

関連情報

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