画餅展覧会

2009 年 4 月 3 日

二重起動防止考

カテゴリー: KanaInput, Programming, Windows Mobile — jk78 @ 6:35 PM

いつのころからか、Microsoftはアプリケーションの二重起動防止コードサンプルとして、MutexとFindWindow()を組み合わせた方法を推奨するようになった。

なんでアプリケーションの起動ごときにMutexとWindowという二つの仕組みを併用せねばならんのか、と不満だったのだが、今まで深く考えたことが無かったので、見直してみた。

FindWindow

自分のウィンドウをFindWindowして、見つからなかった場合は新規作成。見つかった場合は、既存のウィンドウをSetForegroundWindowする。昔のアプリケーションは皆こうしていた。簡単で非常に結構なのだが、少し考えればまずい点は明らかだ。FindWindowからCreateWindowの流れはアトミックな操作ではないので、どう頑張っても二重起動する可能性を消しきれない。単なるアプリケーションウィンドウの二重起動ならば、それほど神経質にならなくても良かろうが、KanaInputのようにシステムの動作にかかわるアプリケーションは、カーネルオブジェクトを使って、確実に二重起動を制御すべきである。(KanaInput 1.0.1 ではやってません。次のバージョンで対応します )

Mutex

で、カーネルオブジェクトの登場となるわけだが、CreateMutexを使って、自分が作成者かどうかだけを判断の材料にする。自分が作成者であれば、おもむろにウィンドウを作成する。自分が作成者でなければ、既存のウィンドウをFindWindowしにいく。同期オブジェクトとしてMutexを使っているにもかかわらず、FindWindowなどという、どちらかといえば不確かな手段を使わなければならないのが気に入らない。同期オブジェクトを使っているのだから、既存ウィンドウのForeground化も同期オブジェクトを使って行えないものか、と考えてしまう。

Event

Mutexは、所有/非所有の判断しかできないので、Eventを使うことを考えてみた。CreateEventして、自分が作成者ならば、ウィンドウを作成し、メッセージループでEvent待ちを併用する。自分が作成者でなければ、既存のEventオブジェクトに対してSetEventする。既存プロセスが、Eventを受け取ったら、SetForegroundWindowを実行する。きれいではないか。ところが実際にやってみると、難がある。まず、デスクトップでは、Windows 2000以降、SetForegourndWindowの実行に制限がかかっているので、バックグラウンドにくすぶっているプロセスが、自分のウィンドウに対してSetForegourndWindowしても、Foregroundになってくれない。Windows Mobileでは今のところそのような制限は無いが、メッセージループが回っていないと、Eventが取れない。つまり、モーダルループが回っている間は、使えない。モーダルウィンドウを出さないアプリケーションならば、使えるかもしれない。(ChooseIM 1.0.1 がこの方法を使っています)

Semaphore + File Mapping (Shared Memory)

つまり、後から起動したほうが、既存ウィンドウに対してSetForegroundWindowを実行しなければならないわけだが、そのためには、ウィンドウハンドルが必要になる。Windowsでは、特にWindows Mobileでは、プロセス間のデータ共有方法はFile Mappingしかない。で、Semaphoreを使って制御しながら、File Mappingでウィンドウハンドルを後続プロセスに渡す方法で組んでみた。問題なく動作する。FindWindowのような不確かな要素も入らない。しかし、受け渡すデータが4バイトなのに対して、共有メモリーの最小単位が64KBと大きいのである。65532バイトが丸々無駄になる。メモリの潤沢なデスクトップ環境ならばいざ知らず、Windows Mobileでは、このような無駄は極力避けたい。

以上のような理由から、結局、Mutex+FindWindowが落しどころと言うことになるのだろう。Microsoftの推奨にも理由はあるのだろうが、やはり自分で考えてみないと納得がいかない。

もう一度FindWindow

FindWindowは、ウィンドウ名とウィンドウクラス名で検索する。ウィンドウ名というのは、ユーザーに見せるものだから、その表現内容は自ずと限られる。ウィンドウクラス名はユーザーに見えないので、長くて醜い名前、特にURLを利用した一意的な名前を使えば、他アプリケーションとの衝突は事実上避けられる。しかし、ダイアログアプリケーションでは、そうはいかない。ダイアログクラスは、システムが用意するものだから、クラス名は選べない。ダイアログウィンドウの検索は、実質ウィンドウ名だけで行われることになる。これは避けたい。「KanaInput」と言う名前のダイアログを他人が悪意なく作成することなど、いくらでもありえるではないか。これを避けるために、KanaInputや、ChooseIM 1.0.0 などは、表示されないフレームウィンドウを、FindWindowのために持たせていたのである。ところが、この構成は、SetForegroundWindowに難がある

ダイアログを子ウィンドウに

最近、ダイアログウィンドウを他のウィンドウの子ウィンドウとして貼り付けることができるということを知った(知りませんでした)。この方法を使えば、ダイアログを利用しながら、独自のクラス名を持ったウィンドウを作成することができる。単独のウィンドウだからSetForegroundWindowによるフォーカスの問題も起きにくい。PropertySheetだと、多少厄介なのだが、やり方はここに書いてある。ChooseIMとKanaInputの次バージョンは、この方法を採用する予定である。

2009 年 3 月 16 日

WM_ACTIVATEでキー状態テーブルが更新されない

カテゴリー: C++, KanaInput, Programming, Windows Mobile — jk78 @ 7:22 PM

おお、KanaInputにバグだ。設定ダイアログだけど。

設定ダイアログを、バックグラウンドに持っていき、隠れている間にCaps LockやらカナLockの状態を変更して、設定ダイアログを再表示させると、変更したロック状態が反映されない。何かのキーを押すか、ボタンのフォーカスを変えると直ります。

正確な再現手順は以下の通り:

  1. KanaInput設定ダイアログを立ち上げる。Caps LockはOFFにしておく。
  2. 別の画面に移る。Today 画面でOK。
  3. Caps LockをONにする。
  4. KanaInput設定ダイアログを再表示するのだが、このとき、キーボードを使用してはいけない。[スタート]→[プログラム]から jk78 KanaInput のアイコンをスタイラスで、一瞬だけタッチしてすぐ離す(笑)。
  5. KanaInput設定ダイアログが表示されるが、Caps Lock表示がOFFのままである。

よくもこんなバグらしいバグが残ってたねえ。でも確かにこのテストケースを考えた覚えが無い。

KanaInput設定ダイアログの状態ページは、システムが管理するCaps LockとカナLockの状態を表示する。KanaInput常駐部も独自にLock状態を管理しているが、そちらの値は使わない。システムのキーLock状態はGetKeyState()で取得する。このAPIで得られる値こそが、アプリケーションに直接影響するからである。もし万が一、システムの状態とKanaInputの状態が食い違った場合、画面を見て明らかになるからである。問題があることが判れば、KanaInputを再立ち上げするなどして、解決することができる。(実際にはKanaInput設定ダイアログ開始時に常駐部の状態とシステム状態を強制的に一致させます)

まあいいや。WM_ACTIVATEで更新すりゃ良いんでしょ。簡単だ。と思ったが甘かった。デスクトップWindowsではWM_ACTIVATEで更新すれば良いのだが、Pocket PCではWM_ACTIVATEを受け取っても、システムのキー状態テーブルが更新されていないらしく、GetKeyState()が古い値を返してしまう。バグじゃん(笑)。バグは仕様の始まり、ってことか。う~む。散々悩んだ結果、入力イベント(マウス・キーボードイベント)を受け取った時点でテーブルがアップデートされると判断。WM_ACTIVATE処理でkeybd_event()かmouse_event()を使って副作用のなさそうなイベントを投げておけば、次のWM_PAINT迄には入力イベントを処理していることでしょう。こんなんで良いのか。(追記:WM_MOUSEWHEELを送ってみたところ、PPC2003SEエミュレーターでは動きましたが、Window Mobile 5/6ではキー状態テーブルが更新されませんでした。涙)

アップデートする理由ができましたな。

2009 年 3 月 9 日

矢印キーでPage Up/Down実験中

カテゴリー: KanaInput, Programming, Windows Mobile — jk78 @ 11:28 PM

キー操作でPage Up/Downしたい、というアイディアに取り付かれて、作っちゃいました。

arrowkeyoption

KanaInputに組み込んであります。かな入力とは一切関係ないのに! かな入力に飽きちゃったもんだから、脱線が始まりました(笑)。まあ、リリースすると決定したわけではありませんが。

いろいろテストしていたら、治りかけの親指がまた痛み出してきました。もう少し暖かくなるまで作業は中断かな。

ダブル・プッシュというのは私の造語で、方向キーをダブル・クリックするとイメージしてください。ただし、ダブル・クリックがWM_LBUTTONDOW→WM_LBUTTONDBLCLKの順で渡ってくるように、VK_DOWN→VK_NEXTといった順で渡ってきますので、副作用ゼロではありません。しかし方向キーは最も連打するキーなんで相性が悪いなあ。トリプル・プッシュのほうが良いかなあ。しばらく使って様子をみます。

早速、アドエスのXcrawlと干渉しました(笑)。Xcrawlって上下の方向キーを投げてくるんだよね。しょうがないなあ、とXcrawlのイベントを迂回するコードを組み込んだら、むしろ迂回するより、こいつらも置換したくなりました。一年半もたって、いまさらXcrawlユーティリティでもあるまいに。

また経過報告します。

古い投稿 »

Powered by WordPress