シングルスレッドアパートメント
シングルスレッド アパートメント (アパートメント モデル プロセス) を使用すると、同時に実行される複数のオブジェクトを処理するためのメッセージ ベースのパラダイムが提供されます。 時間のかかる操作が完了するまでスレッドが待機している間、別のスレッドを実行できるようにすることで、より効率的なコードを作成できるようになります。
アパートメント モデル プロセスとして初期化され、ウィンドウ メッセージを取得してディスパッチするプロセス内の各スレッドは、シングルスレッドのアパートメント スレッドです。 各スレッドは、独自のアパートメント内に存在します。 アパートメント内では、インターフェイス ポインタはマーシャリングなしで渡すことができるため、1 つのシングルスレッド アパートメント スレッド内のすべてのオブジェクトが直接通信します。
すべて同じスレッド上で実行されるため、同期実行が必要な関連オブジェクトの論理グループは、同じシングルスレッドのアパートメント スレッド上に存在できます。 ただし、アパートメント モデル オブジェクトを複数のスレッドに配置することはできません。 他のスレッドのオブジェクトへの呼び出しは、所有しているスレッドのコンテキスト内で行う必要があるため、プロキシを呼び出すと、分散 COM によってスレッドが自動的に切り替えられます。
プロセス間モデルとスレッド間モデルは似ています。 同じプロセス内の別のアパートメント (別のスレッド上) にあるオブジェクトにインターフェイス ポインターを渡す必要がある場合は、異なるプロセスのオブジェクトがプロセス境界を越えてポインターを渡すために使用するものと同じマーシャリング モデルを使用します。 標準マーシャリング オブジェクトへのポインターを取得すると、プロセス間で行うのと同じ方法で、スレッド境界 (アパートメント間) を越えてインターフェイス ポインタをマーシャリングできます。 (インターフェイス ポインターは、アパートメント間で渡されるときにマーシャリングする必要があります。)
シングルスレッド アパートメントのルールは単純ですが、注意深く従うことが重要です。
- すべてのオブジェクトは、(シングルスレッドアパートメント内で) 1 つのスレッドでのみ稼働する必要があります。
- 各スレッドの COM ライブラリを初期化します。
- アパートメント間でオブジェクトを渡すときに、オブジェクトへのすべてのポインターをマーシャリングします。
- 各シングルスレッド アパートメントには、同じプロセス内の他のプロセスおよびアパートメントからの呼び出しを処理するためのメッセージ ループが必要です。 オブジェクトのないシングルスレッド アパートメント (クライアントのみ) にも、一部のアプリケーションが使用するブロードキャスト メッセージをディスパッチするためのメッセージ ループが必要です。
- DLL ベースまたはインプロセス オブジェクトでは、COM 初期化関数は呼び出されません。代わりに、レジストリの InprocServer32 キーの下にある ThreadingModel の名前付き値にスレッド モデルを登録します。 また、アパートメント対応オブジェクトは DLL エントリ ポイントを慎重に記述する必要があります。 スレッドインプロセス サーバーには特別な考慮事項があります。 詳細については、「インプロセス サーバー スレッドの問題」を参照してください。
複数のオブジェクトは 1 つのスレッド上に存在できますが、アパートメント モデル オブジェクトは複数のスレッド上に存在できません。
クライアント プロセスまたはアウトプロセス サーバーの各スレッドは、CoInitialize を呼び出すか、CoInitializeEx を呼び出して dwCoInit パラメーターの COINIT_APARTMENTTHREADED を指定する必要があります。 メイン アパートメントは、最初に CoInitializeEx を呼び出すスレッドです。 インプロセス サーバーの詳細については、「インプロセス サーバース レッドの問題」を参照してください。
オブジェクトへのすべての呼び出しは、そのスレッド (そのアパートメント内) で行う必要があります。 別のスレッドからオブジェクトを直接呼び出すことは禁止されています。このフリースレッド方式でオブジェクトを使用すると、アプリケーションに問題が発生する可能性があります。 このルールの意味は、アパートメント間で渡されるときに、オブジェクトへのすべてのポインターをマーシャリングする必要があるということです。 COM には、この目的のために次の 2 つの関数が用意されています。
- CoMarshalInterThreadInterfaceInStream は、呼び出し元に返されるストリーム オブジェクトにインターフェイスをマーシャリングします。
- CoGetInterfaceAndReleaseStream は、ストリーム オブジェクトからインターフェイス ポインターをアンマーシャルして解放します。
これらの関数は、MSHCTX_INPROC フラグを使用する必要がある CoMarshalInterface 関数と CoUnmarshalInterface 関数の呼び出しをラップします。
一般に、マーシャリングは COM によって自動的に実行されます。 たとえば、別のアパートメント内のオブジェクトに対するプロキシのメソッド呼び出しでパラメーターとしてインターフェイス ポインターを渡す場合や、CoCreateInstance を呼び出すときに、COM は自動的にマーシャリングを行います。 ただし、アプリケーション作成者が通常の COM メカニズムを使用せずにアパートメント間でインターフェイス ポインターを渡す特殊なケースでは、作成者はマーシャリングを手動で処理する必要があります。
プロセス内の 1 つのアパートメント (アパートメント 1) にインターフェイス ポインターがあり、別のアパートメント (アパートメント 2) がその使用を必要とする場合、アパートメント 1 は CoMarshalInterThreadInterfaceInStream を呼び出してインターフェイスをマーシャリングする必要があります。 この関数によって作成されるストリームはスレッド セーフであり、アパートメント 2 からアクセスできる変数に格納する必要があります。 アパートメント 2 では、このストリームを CoGetInterfaceAndReleaseStream に渡してインターフェイスのマーシャリングを解除する必要があり、インターフェイスにアクセスできるプロキシへのポインターが返されます。 メイン アパートメントはメインクライアントが COM のすべての作業を完了するまで再び有効にする必要があります (「インプロセス サーバースレッドの問題」で説明されているように、一部のインプロセス オブジェクトが メイン アパートメントに読み込まれるためです)。 この方法で 1 つのオブジェクトがスレッド間で渡されると、インターフェイス ポインターをパラメーターとして渡すのが非常に簡単になります。 このように、分散 COM は、アプリケーションのマーシャリングとスレッド切り替えを行います。
同じプロセス内の他のプロセスおよびアパートメントからの呼び出しを処理するには、各シングルスレッド アパートメントにメッセージ ループが必要です。 これは、スレッドの作業関数に GetMessage/DispatchMessage ループが必要であることを意味します。 スレッド間の通信に他の同期プリミティブが使用されている場合、MsgWaitForMultipleObjects 関数を使用して、メッセージとスレッド同期イベントの両方を待機できます。 この関数のドキュメントには、この種の組み合わせループの例が記載されています。
COM は、各シングルスレッド アパートメントで Windows クラス「OleMainThreadWndClass」を使用して非表示ウィンドウを作成します。 オブジェクトの呼び出しは、この非表示ウィンドウへのウィンドウ メッセージとして受信されます。 オブジェクトのアパートメントがメッセージを取得して送信すると、非表示のウィンドウがメッセージを受信します。 次に、ウィンドウ プロシージャは、オブジェクトの対応するインターフェイス メソッドを呼び出します。
複数のクライアントがオブジェクトを呼び出すと、その呼び出しはメッセージ キューに入れられ、オブジェクトはそのアパートメントがメッセージを取得してディスパッチするたびに呼び出しを受け取ります。 呼び出しは COM によって同期され、呼び出しは常にオブジェクトのアパートメントに属するスレッドによって配信されるため、オブジェクトのインターフェイス実装は同期を提供する必要はありません。 シングルスレッド アパートメントでは、IMessageFilter を実装して、必要に応じて呼び出しをキャンセルしたり、ウィンドウ メッセージを受信したりできます。
オブジェクトは、そのインターフェイス メソッド実装の 1 つがメッセージを取得してディスパッチするか、別のスレッドに ORPC 呼び出しを行う場合に再入力できます。これにより、別の呼び出しが (同じアパートメントによって) オブジェクトに配信されます。 OLE は同じスレッドでの再入を妨げませんが、スレッドの安全性を確保するのに役立ちます。 これは、メッセージの処理中にウィンドウ プロシージャがメッセージを取得してディスパッチする場合に、ウィンドウ プロシージャに再入できる方法と同じです。 ただし、アウトプロセスのシングルスレッド アパートメント サーバーを呼び出し、そのサーバーが別のシングルスレッド アパートメント サーバーを呼び出すと、最初のサーバーに再入することができます。
関連トピック