IMessageFilter
COM公式ドキュメント
同期呼び出しの応答を待機している間、受信および送信される COM メッセージを COM サーバーおよびアプリケーションが選択的に処理できるようにします。
解説(Remarks)
同期呼び出しでは、処理を続行する前に呼び出し側が応答を待つ必要があります。COM は応答を待つ間、モーダルループに入ります。この間も、呼び出し側は引き続き受信メッセージを受け取ってディスパッチできます。
非同期呼び出しでは、呼び出し側は呼び出し先オブジェクトからの応答を待たずに処理を続行できます。現時点の COM において、唯一の非同期呼び出しはオブジェクトの IAdviseSink インターフェイスに対するものです。オブジェクトが非同期呼び出しを処理している間は、呼び出し元オブジェクトへの同期呼び出しを行うことは禁止されています。
フォーカス管理や先行入力(type-ahead)などの動作を正しく機能させるため、入力同期呼び出しでは、呼び出し先オブジェクトが制御を手放す前に呼び出しを完了させる必要があります。
WM_QUERYENDSESSION および WM_ENDSESSION によるアプリケーションのシャットダウン
ユーザーが Windows を終了すると、終了がキャンセルされない限り、開いている各アプリケーションは WM_QUERYENDSESSION メッセージを受け取り、続いて WM_ENDSESSION メッセージを受け取ります。これらのメッセージは SendMessage 関数によって送出されますが、この関数は残念ながらすべての送信 LRPC 呼び出しの開始を制限します。これは、埋め込みオブジェクトを開いているコンテナーアプリケーションがシャットダウン要求を受け取ったときに問題となります。それらのオブジェクトを閉じるには LRPC が必要だからです。ドキュメントを開いているコンテナーアプリケーションおよびコンテナー/サーバーアプリケーションは、通常、WM_QUERYENDSESSION メッセージの受信時に、終了する前に変更を保存するかどうかをユーザーに尋ねるメッセージボックスを表示します。通常は肯定的な応答が既定になっています。上記の状況に対処するための推奨事項は、変更を破棄するかどうかを尋ねる別のメッセージボックスを表示することです。この場合は否定的な応答を既定にすべきです。ユーザーが変更の破棄を選択した場合は、WM_QUERYENDSESSION に対して TRUE を返すべきであり、これは終了して差し支えないことを Windows に伝えます。ユーザーが変更を破棄したくない場合は、FALSE を返すべきです。実行中の埋め込みオブジェクトを閉じたり解放したりしようとしてはなりません。
サーバーアプリケーションは、ユーザーに確認を求めることなく、WM_QUERYENDSESSION に対して TRUE を返すべきです。WM_ENDSESSION メッセージの受信時には、すべての COM アプリケーションは、各アプリケーションのドキュメントおよびオブジェクトに対する通常のクローズ処理を実行すべきです。同時に、プロセス間呼び出しや IUnknown::Release への呼び出しから生じるエラーは無視すべきです。すべてのストレージポインター(IStorage および IStream インターフェイスポインター)は、構造化ストレージの複合ファイル実装によって管理されている一時ファイルを正しくフラッシュするために解放しなければなりません。
メソッド 3
vtbl = vtable インデックス(0始まり)。HSP等からCOMメソッドをインデックス指定で呼ぶ際に使用します。0〜2 は IUnknown。
受信呼び出しに対する単一のエントリポイントを提供します。
| dwCallType | DWORD | in | 受信した受信呼び出しの種類。指定可能な値は列挙型 CALLTYPE のものです。 |
| htaskCaller | HTASK | in | 呼び出し側のスレッド ID。 |
| dwTickCount | DWORD | in | dwCallType が CALLTYPE_TOPLEVEL でない場合、送信呼び出しが行われてからの経過ティックカウント。dwCallType が CALLTYPE_TOPLEVEL の場合、dwTickCount は無視すべきです。 |
| lpInterfaceInfo | INTERFACEINFO* | inoptional | 呼び出されているオブジェクト、インターフェイス、およびメソッドを識別する INTERFACEINFO 構造体へのポインター。DDE 呼び出しの場合、DDE 層はインターフェイス情報を返さないため、lpInterfaceInfo は NULL になることがあります。 |
戻り値
このメソッドは次の値を返すことがあります。
| 戻り値 | 説明 |
|---|---|
| アプリケーションはこの呼び出しを処理できる可能性があります。 | |
| ネットワークが利用できないなどの予期しない問題が発生したため、またはアプリケーションが終了処理中であるため、呼び出しを処理できません。 | |
| 現時点では呼び出しを処理できません。ユーザー制御のモーダル状態にある場合などに、アプリケーションはこの値を返すことがあります。 |
解説(Remarks)
実装されている場合、HandleInComingCall は受信 COM メッセージを受け取ったときに COM によって呼び出されます。
アプリケーションの現在の状態に応じて、呼び出しは受け入れられて処理されるか、あるいは(永続的または一時的に)拒否されます。SERVERCALL_ISHANDLED が返された場合、アプリケーションは呼び出しを処理できる可能性がありますが、成功するかどうかは呼び出しの宛先となるインターフェイスに依存します。呼び出しを処理できない場合、COM は RPC_E_CALL_REJECTED を返します。
入力同期呼び出しおよび非同期呼び出しは、アプリケーションが SERVERCALL_REJECTED または SERVERCALL_RETRYLATER を返した場合でもディスパッチされます。
HandleInComingCall は、バンド印刷などの操作中にオブジェクトへの更新を保留するために使用すべきではありません。その目的には IViewObject::Freeze を使用してください。
また、HandleInComingCall を使用して、呼び出しを将来処理できるようにアプリケーションの状態を設定することもできます。
再試行、キャンセル、またはタスク切り替えのオプションを提示するダイアログボックスを表示する機会をアプリケーションに提供します。
| htaskCallee | HTASK | in | 呼び出されたアプリケーションのスレッド ID。 |
| dwTickCount | DWORD | in | 呼び出しが行われてからの経過ティック数。 |
| dwRejectType | DWORD | in | オブジェクトアプリケーションから返された SERVERCALL_REJECTED または SERVERCALL_RETRYLATER のいずれかを指定します。 |
戻り値
このメソッドは次の値を返すことがあります。
| 戻り値 | 説明 |
|---|---|
|
呼び出しをキャンセルすべきです。COM は元のメソッド呼び出しから RPC_E_CALL_REJECTED を返します。 |
|
呼び出しを直ちに再試行します。 |
|
COM はこのミリ秒数だけ待機してから呼び出しを再試行します。 |
解説(Remarks)
COM は、呼び出し先の IMessageFilter インターフェイスの IMessageFilter::HandleInComingCall メソッドから SERVERCALL_RETRYLATER または SERVERCALL_REJECTED を受け取った直後に、呼び出し側の IMessageFilter インターフェイスの RetryRejectedCall を呼び出します。
呼び出されたタスクが呼び出しを拒否した場合、そのアプリケーションはおそらく(一時的である可能性もありますが)そのような呼び出しを処理できない状態にあります。この場合、COM は呼び出し側に戻り、拒否された呼び出しを再試行すべきかどうかを判断するために RetryRejectedCall を発行します。
アプリケーションは、SERVERCALL_RETRYLATER で戻ってきた呼び出しを黙って再試行すべきです。妥当な時間(たとえば約 30 秒)が経過してもなお処理されない場合は、ビジー状態のダイアログボックスを表示すべきです。このダイアログボックスの標準的な実装は OLEDLG ライブラリで利用できます。呼び出し先が一時的に呼び出しを処理できる状態になることもあります。待機して再試行するオプションは、マクロやスクリプトを実行するバックグラウンドタスクなど、特殊な種類の呼び出しアプリケーションが、目立たない方法で呼び出しを再試行できるように用意されています。
ダイアログボックスが表示された後にユーザーがキャンセルを選択した場合、RetryRejectedCall は -1 を返し、呼び出しは RPC_E_CALL_REJECTED で失敗したかのように見えます。
クライアントが IMessageFilter を実装し、リモートマシン上のサーバーメソッドを呼び出す場合、RetryRejectedCall は呼び出されません。
COM がリモート呼び出しへの応答を待機している間にメッセージが到着したことを示します。
| htaskCallee | HTASK | in | 呼び出されたアプリケーションのスレッド ID。 |
| dwTickCount | DWORD | in | 呼び出しが行われてからの経過ティック数。GetTickCount 関数から計算されます。 |
| dwPendingType | DWORD | in | メッセージまたはイベントを受け取ったときに実行中だった呼び出しの種類。指定可能な値は列挙型 PENDINGTYPE のものです。PENDINGTYPE_TOPLEVEL は送信呼び出しが別のアプリケーションからの呼び出し内にネストされていなかったことを意味し、PENDINTGYPE_NESTED は送信呼び出しが別のアプリケーションからの呼び出し内にネストされていたことを意味します。 |
戻り値
このメソッドは次の値を返すことがあります。
| 戻り値 | 説明 |
|---|---|
| 送信呼び出しをキャンセルします。これは極端な状況でのみ返すべきです。応答も拒否もされていない呼び出しをキャンセルすると、孤立したトランザクションが発生してリソースが失われる可能性があります。COM は元の呼び出しを失敗させ、RPC_E_CALL_CANCELLED を返します。 | |
| 未使用。 | |
| キーボードおよびマウスのメッセージはディスパッチされなくなります。ただし、マウスおよびキーボードのメッセージがシステムをデッドロックさせる可能性がある場合があり、そのような場合にはマウスおよびキーボードのメッセージは破棄されます。WM_PAINT メッセージはディスパッチされます。タスク切り替えおよびアクティブ化のメッセージは従来どおり処理されます。 |
解説(Remarks)
COM は、アプリケーションが COM メソッド呼び出しを行い、その呼び出しが戻る前に Windows メッセージが発生した後に、MessagePending を呼び出します。Windows メッセージは、たとえばユーザーがメニューコマンドを選択したり、オブジェクトをダブルクリックしたりしたときに送信されます。COM は MessagePending の呼び出しを行う前に、元の COM メソッド呼び出しが行われてからの経過時間を計算します。COM は経過時間を dwTickCount パラメーターで渡します。その間、COM はメッセージをキューから取り除きません。
呼び出し側のキューに現れる Windows メッセージは、それらのメッセージがおそらく先行入力の結果ではなく、注意を促そうとする試みであると確認できる十分な時間が経過するまで、キューに残しておくべきです。この遅延は dwTickCount パラメーターで設定します。2 秒から 3 秒の遅延が推奨されます。その時間が経過しても呼び出しが完了していない場合、呼び出し側はキューからメッセージをフラッシュし、OLE の UI ビジーダイアログボックスを表示して、呼び出しを再試行する(待機を続ける)か、指定されたタスクに切り替えるかの選択肢をユーザーに提示すべきです。これにより、次の動作が保証されます。
- 呼び出しが妥当な時間内に完了する場合、先行入力は正しく扱われます。
- 呼び出し先が応答しない場合、先行入力が誤って解釈されることはなく、ユーザーは問題を解決するために行動できます。たとえば、OLE 1 サーバーはモーダルダイアログボックス内にあるときに、応答せずに要求をキューに溜め込むことがあります。
元の COM 呼び出しに応答がない場合、アプリケーションは呼び出しをキャンセルし、ストレージに対して IStorage::Revert を呼び出すことで COM オブジェクトを一貫した状態に復元できます。コンテナーをシャットダウンできる状態になったら、オブジェクトを解放できます。ただし、呼び出しをキャンセルすると、孤立した操作やリソースリークが発生する可能性があります。キャンセルは最後の手段としてのみ使用すべきです。そのような呼び出しをキャンセルできないようにすることを強くお勧めします。
Microsoft 公式リファレンス: 英語 (en-us) · 日本語 (ja-jp) · 原文ソース (GitHub)
HSP用 COM定義
#usecom / #comfunc によるHSPのCOM呼び出し定義。数字は vtbl インデックス(0始まり)。クラスIDが無い場合 #usecom の末尾は "{}"、ある場合は "{CLSID}"。
#define global IID_IMessageFilter "{00000016-0000-0000-C000-000000000046}" #usecom global IMessageFilter IID_IMessageFilter "{}" #comfunc global IMessageFilter_HandleInComingCall 3 int,sptr,int,var #comfunc global IMessageFilter_RetryRejectedCall 4 sptr,int,int #comfunc global IMessageFilter_MessagePending 5 sptr,int,int ; ※数字は vtbl インデックス(0始まり)。0/1/2 は IUnknown(QueryInterface/AddRef/Release)。 ; ※このインターフェースは直接 CoCreateInstance するクラスIDが無いため "{}"(他メソッド/アクティベーションで取得)。 ; ※出力/バッファ引数は var(変数直渡し)。varptr 方式にも切替可。 ; ※ハンドル/void*等の不透明ポインタは IronHSP では intptr 指定が可能。#define global IID_IMessageFilter "{00000016-0000-0000-C000-000000000046}" #usecom global IMessageFilter IID_IMessageFilter "{}" #comfunc global IMessageFilter_HandleInComingCall 3 int,sptr,int,sptr #comfunc global IMessageFilter_RetryRejectedCall 4 sptr,int,int #comfunc global IMessageFilter_MessagePending 5 sptr,int,int ; ※数字は vtbl インデックス(0始まり)。0/1/2 は IUnknown(QueryInterface/AddRef/Release)。 ; ※このインターフェースは直接 CoCreateInstance するクラスIDが無いため "{}"(他メソッド/アクティベーションで取得)。 ; ※出力/バッファ引数はポインタ方式(token=sptr / 呼び出しは varptr(変数))。 ; ※ハンドル/void*等の不透明ポインタは IronHSP では intptr 指定が可能。