ウィンドウのサブクラス化

サブクラス化の処理は猫Cとかで昔読んだ気がするけど、面倒なので自分では手を付けていないでMFCとかATLやWTLに完全に頼っている。Vistaの調査でMSDNを眺めていたら気付いたんだけれど、ComCtl32.dllのversion 6以降では、サブクラス関連のAPIが追加されているようだ。
http://windowssdk.msdn.microsoft.com/en-us/library/ms649784.aspx
自分でサブクラス化の面倒を見る手間が何やら省ける感じ。というかAPIのサブクラス化の処理をリッチに面倒を見てくれる、というべきなんだろうか?理解が足りなくて上手く言葉に出来ない。

ATLとウィンドウプロシージャ

http://hp.vector.co.jp/authors/VA022575/c/msgmap.html
サブクラス化ってAPIではどうやるんだったっけな、と検索して調べているうちに、上のページの内容が理解したくなった。ページを参考にしてATLのソースを読んだり考えたりしたところ、ATLの、ウィンドウメッセージを処理するウィンドウプロシージャを、何とかしてインスタンスのスコープに回す仕組みが、何とか理解出来た。

  • staticなStartWindowProcというのを各ウィンドウクラスのウィンドウプロシージャとして設定する。設定処理は実際にどこに書かれてるかは今回の調査外。
  • 初回に呼び出されるウィンドウプロシージャ(StartWindowProc)の中で行われる処理は以下の通り
    • thisを取得するのはATLのアプリケーションモジュールにとりあえず任せる。(上のページを見てみるとLinkedListに格納してるようだ。)
    • 各instance毎に用意されるmemberのthunkに実行コードを作らせる。(実行コードを摩り替える処理を行わせる)
    • そのthunkをウィンドウのWindowProcとして再設定する。(SetWindowLongPtr)
    • thunkの実行コードはHWNDをthisに置き換え、別のstaticなWindowProcを呼び出す内容になっている。staticなWindowProcのアドレスはともかく、thisのアドレスは、ウィンドウプロシージャが動いている間は有効にしなければならないんだろう。そもそもthunkとかが死んでいる時にはinstanceも死んでいるだろうし…。
    • staticなWindowProcの内容は、引数のHWNDがthisなのでキャストして得た後は、ProcessWindowMessageを呼んだりとかの、ある程度ユーザーが使う部分に直接繋がっている感じな内容。
  • 2回目以降に呼び出されるウィンドウプロシージャは、thunk。thunkでは、HWNDのパラメータにあたるものをthisにして、設定されたウィンドウプロシージャのアドレスにジャンプ。
  • SetWindowLongPtrされたWindowProcはthunkのものなので、thunkの実行コードが基本的に呼び出される。つまり毎回instanceのthunkの実行コードを経て(設定したので当然なんだろうけど)ウィンドウプロシージャが呼び出される。

もし自分でインスタンスのスコープでメッセージ処理を行う仕組みを作る必要に迫られたら、アプリケーションモジュールでmapみたいな領域を確保して、HWNDをキーにして、thisとウィンドウプロシージャのアドレスをメンバに持つ構造体のinstanceを、毎回ウィンドウプロシージャで検索して取得するやり方を思い付く。ATLのは実行コード中にinstanceのアドレスを書いてしまうある意味ハックというか乱暴なやり方。検索コストが無さそう。コードの実行領域とかが少し膨らむ気はする。

しかしそもそもWindowProcのパラメータが最小限必要な4つしか無い事が、変な仕組みをアプリケーション側で用意する必要が出てくる理由な気がする。ここに何かもう一個数字が入る仕組みにすればアプリ側では、thisとかを取りやすそうなのに。でもWindowProcってかなり頻繁に呼ばれるから、それだとあまりパフォーマンス的に頂けないのかな。。あと昔はC言語の時代だったので、C++のthisの都合なんて知らないって事なのかもしれない。

話を元に戻す

新しいSUBCLASSPROCは、
http://windowssdk.msdn.microsoft.com/en-us/library/ms649784.aspx
パラメータが増えているので、時代が富豪化を許す時代になったという事だろうか?WindowsVistaではRAMは最低1GBの時代だろうし。

サブクラス化は既にウィンドウクラスが登録されたウィンドウの挙動をカスタマイズする為のもので新規ウィンドウを作る時は従来通りのパラメータが少ないウィンドウプロシージャを呼び出して下さりやがるAPIを使うしか無いのかな?サブクラス化しなおせば良いんだろうか?こういう新しく出てきた補助関数はコストはどれくらい従来のものに比べて掛かるんだろうか?

それはともかく判り易い利用例を発見。
http://blogs.msdn.com/oldnewthing/archive/2003/11/14/55678.aspx

挙動が単純な、掛け合わせても問題無いメッセージ処理のカスタマイズを適用するのがOSのAPIで楽に出来る感じだろうか。カスタマイズするロジックを記述するのが楽になるかも。