現在 STA で COM の exe サーバを作っているのですが、最近「STA より MTA の方が楽なんじゃね?」とか思うようになってきました。
STA であればシングルスレッドなので、自分のメソッドが呼び出されている間に別のメソッドが呼ばれてしまうといった再入の問題について深く考えずにプログラミング出来るかなと思っていたのですが、大きな間違いでした。
というのも、STA は exe 間での通信はメッセージキューを介して行われるわけですが、メッセージというのは明示的に回さなくても勝手に回っていることが多々あるんですね。
例えばモーダルダイアログやメッセージボックスといったものは内部でメッセージを回しているので、当然呼び出している間に再入される可能性があります。
STDMETHODIMP CHoge::Foo()
{
...
m_window.DoModal();
...
}
これはまあ、内部でメッセージが回っているのは Windows プログラマなら一瞬でわかるのでこれはいいとして。
問題は COM のメソッドを呼び出している間にもメッセージは回るということです。
例えば自分の作っている exe サーバはコネクションポイントがあるので、シンクに対してイベントを配信します。
このイベントを配信している間にもメッセージが回るので、これも再入の可能性があります。
あと最近あったのは、余所のライブラリがこっそりと COM のメソッドを呼び出していたパターンです。
STDMETHODIMP CHoge::Foo()
{
...
m_library.Bar();
...
}
こういったライブラリをあちこちで使用していたとなると、どこで再入される可能性があるのか分からなくなります。
で、マルチスレッドにおいて再入を防ぐにはクリティカルセクションを使っていたわけですが、STA はシングルスレッドなので使えません。
ではどうするかというと、STA での再入の制御は IMessageFilter という機構を使います。
IMessageFilter については [C++]ビジー状態 で書いたので細かいことは省略するとして、とりあえずこの機構を使えば自分の COM メソッドが呼ばれる前に「今忙しいからちょっと待って!」と言うことが可能になるので、自分の望むタイミングで処理を受け取ることが可能になります。
ただ IMessageFilter にも落とし穴があって、「今忙しいからちょっと待って!」と言われたクライアントはどうするかというと、MFC であれば上記のリンク先にあるビジー状態のダイアログを出します。
しかし IMessageFilter に対して特に何もしていない場合は、サーバが「今忙しいからちょっと待って!」と言っているにもかかわらず RPC_E_CALL_REJECTED というエラーを返します。
これは IMessageFilter::RetryRejectedCall のデフォルトの処理がエラーを返すようになっているのが原因なので、クライアント側もちゃんと IMessageFilter を導入し、::CoRegisterMessageFilter で登録してやる必要があります。
MFC なら上記のリンク先にあるメソッドによってビジーダイアログが出ないようにすればいいでしょう。
しかし、まだ IMessageFilter には罠があります。
例えば MFC でアプリケーションを作っているクライアントが、ワーク用のスレッドを起こして GIT 経由でサーバにアクセスしようとしたけど、そのサーバが「今忙しいからちょっと待って!」と言ってきた場合、MFC ならデフォルトの IMessageFilter がセットされているにもかかわらず、クライアントには RPC_E_CALL_REJECTED が返されます。
::CoRegisterMessageFilter の説明を読めば分かるのですが、IMessageFilter はスレッド単位で設定する必要があるのです。
(英語の全然読めない自分は、この事実に気が付くのにかなりの時間が必要でした……)
つまり STA な exe サーバを作ると、
1.変なところで再入されてしまう可能性があるので IMessageFilter によってガチガチにしてやる。
2.するとサーバは「今忙しいからちょっと待って!」と言ってリトライをお願いすることが多々あり、
3.サーバ的にはリトライをお願いしてるんだけど、クライアントが全員 MFC を使っているわけでもないし GIT を使う可能性もあるし IMessageFilter についてよく知っているわけでもないので、なぜか RPC_E_CALL_REJECTED が返されてしまう。
となり、クライアントが適切に IMessageFilter を導入しないと恐ろしく信頼性の悪いサーバが出来上がってしまいます。
MTA はまだやったことないので何とも言えないのですが、STA はクライアント側に IMessageFilter の知識を要求する(もしくは RPC エラーを許容させる必要がある)ので、これならやっぱり MTA の方がいいんじゃないかなぁとか思うのですがどうなんでしょう。