melt日記

.NETすらまともに扱えないへたれのページ

ホーム 連絡をする 同期する ( RSS 2.0 ) Login
投稿数  111  : 記事  3  : コメント  491  : トラックバック  40

ニュース

わんくま同盟

わんくま同盟

C# と VB.NET の質問掲示板

iKnow!


Dictation



書庫

現在 STA で COM の exe サーバを作っているのですが、最近「STA より MTA の方が楽なんじゃね?」とか思うようになってきました。


STA であればシングルスレッドなので、自分のメソッドが呼び出されている間に別のメソッドが呼ばれてしまうといった再入の問題について深く考えずにプログラミング出来るかなと思っていたのですが、大きな間違いでした。

というのも、STA は exe 間での通信はメッセージキューを介して行われるわけですが、メッセージというのは明示的に回さなくても勝手に回っていることが多々あるんですね。


例えばモーダルダイアログやメッセージボックスといったものは内部でメッセージを回しているので、当然呼び出している間に再入される可能性があります。

// COM メソッド
STDMETHODIMP CHoge::Foo()
{
    ...                  // 何かの処理
    m_window.DoModal();  // ここでダイアログを表示している間に
                         // 誰かが CHoge::Foo を呼び出すと、
    ...                  // この処理が呼ばれる前に再入されてしまう
}

これはまあ、内部でメッセージが回っているのは Windows プログラマなら一瞬でわかるのでこれはいいとして。


問題は COM のメソッドを呼び出している間にもメッセージは回るということです。

例えば自分の作っている exe サーバはコネクションポイントがあるので、シンクに対してイベントを配信します。

このイベントを配信している間にもメッセージが回るので、これも再入の可能性があります。

あと最近あったのは、余所のライブラリがこっそりと COM のメソッドを呼び出していたパターンです。

// COM メソッド
STDMETHODIMP CHoge::Foo()
{
    ...               // 何かの処理
    m_library.Bar();  // ここの内部で COM メソッドを呼び出していたので、
                      // この処理中に誰かが CHoge::Foo を呼び出すと、
    ...               // この処理が呼ばれる前に再入されてしまう
}

こういったライブラリをあちこちで使用していたとなると、どこで再入される可能性があるのか分からなくなります。


で、マルチスレッドにおいて再入を防ぐにはクリティカルセクションを使っていたわけですが、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 の方がいいんじゃないかなぁとか思うのですがどうなんでしょう。

投稿日時 : 2007年12月13日 18:34

コメント

# re: STA の難しさ 2008/01/28 19:11 渋木宏明(ひどり)
化石レスです (^^;

exe サーバって STA でしか作れないんじゃなかったでしたっけ?


# re: STA の難しさ 2008/01/29 14:36 melt
そんなことは無いような...。
STA で作った exe サーバで atlbase.h をインクルードする前に _ATL_FREE_THREADED を define してやると、当然排他制御なんてしてなかったのですぐに実行時エラーになりましたが、そのエラーがいつの間にか作られていたスレッドから発生していたので多分 MTA になってるんだと思うのですが...。

# Articles about _atl_free_threaded volume 6 « Article Directory 2010/06/15 4:15 Pingback/TrackBack
Articles about _atl_free_threaded volume 6 « Article Directory

Post Feedback

タイトル
名前
Url:
コメント