Windows 10 でアクティブなウィンドウを別の仮想デスクトップに飛ばすアプリが完成したのでご報告します http://t.co/bOy8LoDFbU
— ぐらばく (@Grabacr07) 2015, 9月 14
このアプリ、「作りかけたけど XXX が原因でダメだった」というブログを書いたら、それを見たつもりんが「できるんじゃね?」ってなってそのまま 2 人で共闘して完成したので情報発信大事ですね
— ぐらばく (@Grabacr07) 2015, 9月 14
作ったよ、と言ってもほとんどは@Grabacr07 が作ったので僕がやったのはC++&Win32でいろいろするライブラリと、それをCLRに接続するラッパー周りを作っただけです。
なにしてるの
このアプリ作る上で問題となったのが先のブログエントリでも言及されているとおり
- 他プロセスのウィンドウは、他の仮想デスクトップに移動させることはできなかった
という一点のみ。では自分のプロセスについてはどうか。という点では、自分のプロセスが所有しているウィンドウについては問題なく他の仮想デスクトップに移動させることができる。 つまり、移動したいウィンドウのプロセスの内側から必要なAPIを呼び出せば目的は達成できるはず。
どうやって乗り込む
WindowsではSetWindowsHookEx APIを使うと、お手軽に他人のプロセスに乗り込むことができます。*1このAPIは本来Windowsの様々な機能に対してフックを仕掛けるのですが、仕組み上グローバルフックを設定した場合にすべてのプロセスに任意のDLLを読み込ませることができます。つまり、すべてのプロセスに対して任意のDLLを読み込ませて、そのDLLで仮想デスクトップを移動させるAPIを呼べば先ほどの、他プロセスのウィンドウを移動することができない。という問題を回避することができます。
と、偉そうに書いてますけど、Windows界隈では昔からごくごく普通に使われてるテクニックです。
ここまでを図にするとこんな感じ。
細かい話
やってることはそんなに大したことなくて
- SetWindowsHookExでWH_GETMESSAGEとWH_CALLWNDPROCRETを仕掛けて、それぞれPostMessageとSendMessageで送られてくるメッセージを処理します。 それとは別にC#のアプリでWH_KEYBOARD_LLを仕掛けてキー入力を待ちます。
- 設定されているキーコンビネーションが押されたら、アクティブウィンドウに対してSendMessageします。
- しかし、仮想デスクトップはGUIDで管理されていてSendMessageは32bit版の場合頑張っても64bitしか送れません。GUIDは128bitあるので半分足りない。 仕方ないのでVirtualAllocExで送信先プロセスにメモリを確保し、WriteProcessMemoryでGUIDを格納して、確保したアドレスをSendMessageで送信とかします。
- フックプロシージャでSendMessageを検出して、IVirtualDesktopManager::MoveWindowToDesktop をよさげに実行します。
と、以上これだけ。
他には64bitで動くコード、32bitで動くコードと微妙に違いがあるのでそのへんはよしなにしたり、CLR向けラッパーをC#(IL)で作って、 それを継承したC++(IL/Native)なアセンブリを作って、実行時にプロセスに合わせて正しいビット数のDLLを読む。とかもやってますが、そっちはもっと大したことない感じ。
64bit Windowsだと、32bitアプリケーションがWoW64で実行されて、32bitプロセスは32bit dll, 64bitプロセスは64bit dll しか読めない。という制限がもちろんあって、 SetWindowsHookEx APIも呼び出し元が32bitなのか64bitなのかでフックできるプロセスが決まってしまうので、32bitプロセスをフックするためだけの バイナリが必要になるとかもありますが、このへんもフックしていろいろするアプリを作るときは一般的な話です。
まとめ
- 他プロセスから操作できないなら目的のプロセスに侵入すればいいじゃない
- SetWindowsHookExでお手軽に乗り込める
- 32bit/64bit 混在環境めんどくさい
以上です。
リポジトリはこちら。もっと説明して!とかあったらmentionとかください。
*1:他にも、CreateRemoteThreadを使っても乗り込めます。今回はキーフックして特定のショートカットでウィンドウを制御する。という話だったのでSetWindowsHookExで全プロセスを対象にしました。