none
ネイティブなC++のクラスでWINAPIのウインドウクラスやプロシージャを使うにあたって RRS feed

  • 質問

  • 初めて投稿させていただきます。

     

    あるコミュニティで質問しましたが、1週間待って回答が得られなかったので、こちらのMSDNフォーラムで少し変更を加えつつ、再質問させてください。

    (元サイトへの投稿は削除済みです)

     

    //////

     

    後で一部変更するとき他のところがなるべく影響受けないようにと思って、今までほぼウインドウ毎にウインドウクラスを登録、としてきたのですが

    大規模アプリケーションを作るなら、プロシージャのアドレス以外が一致するウインドウクラスは、わざわざそれぞれを別々に登録するよりも、あらかじめ汎用的なウインドウクラスを作って、同じものを指定してウインドウを生成した直後に、プロシージャの変更をするのが一般的なのでしょうか?


    現在、(C++の)クラス内にstatic関数としてプロシージャを用意し、SetPropとGetPropでインスタンスの受け渡しを行っています。

    つまりコードとしてはたとえばクラスが


    class ABC{

         HWND wnd;
          static LRESULT CALLBACK Proc(HWND, UINT, WPARAM, LPARAM);
    public :
          static BOOL InitABC(); //インスタンス生成前に呼び出す初期化関数
          ABC(void);
          ~ABC(void);

    };

    のような状態になっているとすると

    ↓こんな感じに初期状態でDefWindowProcとかをプロシージャとするウインドウクラスを登録をしておき

    BOOL ABC::InitABC(){
         WNDCLASSEX wca={ sizeof wca, CS_DBLCLKS, DefWindowProc , 0,0,HINST::me,NULL,  LoadCursor(NULL,IDC_ARROW), NULL, NULL, _T("ウインドウクラス名" )} ;
         return RegisterClassEx(&wca);
    }


    その後で
    コンストラクタかコンストラクタ周辺の初期化関数等で↓

    wnd=CreateWindowEx( ・・・・


    SetWindowLong(wnd,GWL_WNDPROC,(LONG)Proc);
    SetProp(wnd, _T("何々" ), (HANDLE)this );


    のように、生成後に切り替え、

    LRESULT CALLBACK ABC::Proc(HWND hw, UINT msg, WPARAM wp, LPARAM lp){
             ABC* abc = (ABC*)GetProp(hw, _T("何々" ));


    でインスタンスのアドレスをキャッチし、プロシージャ内では
    abc->メンバ
    という風に使うという方法をメインに考えています。
    (もちろん、プロシージャ内で一度もインスタンス変数・インスタンス関数を使用しなければSetProp・GetPropの必要はありませんが)

    冒頭で書きましたが、今まではほとんどウインドウ毎にウインドウクラスを作るという感じだったので

    実際には

    DefWindowProcではなく、この場合最初からProcを指定して登録し

    SetWindowLongで切り替えを行うことはなかったのですが

    もし今までの方法だと

    WM_SIZEやWM_MOVEは生成時も送られるため、その中でインスタンスメンバにアクセスする事があると、SetPropの前でthisが送られない状態でメンバにアクセスしようとして落ちる可能性があるため

    そういう時はさらにstaticな変数とかを作っておいて、最初に生成されるときに大丈夫なような判定を作らなければならないのでは…?

    と思っていたのですが、生成時に別のプロシージャを設定しておくなら、そこはわざわざ変数用意したりせずとも、生成時のWM_SIZE、WM_MOVEは回避出来ます。
    (もし切り替え直後にそれらのメッセージを送りたいなら、例えば SendMessage(wnd,WM_SIZE,0,0); などでOKでしょうか?)


    また、プロシージャだけ違うウインドウを沢山作る場合は、毎回ウインドウクラスを登録するより、使いまわしてプロシージャだけ変更する方が、私の環境では平均では速度的に勝るようです。

    (また、メモリも本当に若干ですが得にはなると思います)



    ただし、プロシージャまで全く同じウインドウを沢山作るなら、切り替えなしで出来るように、最初から目的のプロシージャを設定する、それ専用のウインドウクラスを登録した方が良い気もします。


    この辺、皆さんはどのようにしていますか?
    この考え通り、それらのことを考慮してケースバイケースでやればいいですかね?


    また、プロシージャの切り替えを行う場合は、生成時のWM_SIZEやWM_MOVE等に加えWM_CREATE等が自動的にそのプロシージャ内では処理されなくなる以外の点で、かわりに注意すべきことはあるでしょうか?


    そして、C++のクラス内にこのように作る場合に、この構造自体、もうちょっとスマートな方法、改良出来る点はあるでしょうか?

    • 編集済み 七空白 2010年4月1日 14:32 クラスの最後に「;」がついてなかったので一応w
    • 種類を変更済み 七空白 2010年4月1日 17:38 若干趣旨が変化しましたので
    2010年4月1日 12:57

回答

  • ATL や MFC のような既存のクラスライブラリを利用するのではなく、完全独自で同じようなクラスライブラリを作りたい。。。ってことですかね。

    やりたいことは、まさにATLやMFCが実現していることになります。もちろんこれらを使わず自分で実装するのもありです。
    #というか。。。私も独自実装もってるしw<今は使ってないけど...

    軽量なウィンドウプロシージャだけがほしいというのであっても、ATLの実装は参考になりますよ。

    ソースもVSのStandard以上なら添付されています(Ver.6以上ならどのバージョンにも入ってる)し、ウィンドウ周りだけなら、新旧問わず参考になります(というか同じだしw)。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月1日 17:38
    2010年4月1日 13:31
  • Express ですか。。。だとすると、ATLもMFCもないですね。どちらも、Standard 以上に含まれています。

    今ならVS2010 のRCあるいはβ2があるので、それをインストールすれば、MFCやATLのソースは参照できます。ただし、開発途中のものなので、バグとか含まれてる可能性もあります(ウィンドウプロシージャ周りとかは変わってないと思いますけどね)。

    ATL(Active Template Library)は、ActdiveX Control などを作成する際に MFC のようなDLL添付型ではなく、軽量なスタティックライブラリベースで利用できるようにと作られたフレームワークです。

    初期の設計こそCOMサーバー用とされていましたが、実際にリリースされたものは、COMに無関係に使えるように作られたコンパクトなライブラリとなっています。コンパクトとはいっても、巨大じゃない。。。というだけで全体となるとそれなりの規模にはなってますけどね。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月1日 17:38
    2010年4月1日 16:40
  • 自分も同じ手法でMFC風のファンデーションライブラリを作りました。
    アイデアはほぼ同じですが、構築手法がやや異なります。
    ちゃんと動いてますよ(^^;)。
    1.WNDCLASSEXを継承したM_WNDCLASSEXを作成。ウインドウ拡張BYTEに
      必要な量を確保します。コールバックはインディペンデントなインスタンス
      を指定します。
            lpfnWndProc = ( WNDPROC)callback;
            cbWndExtra  = ( int)8;
          :
            BOOL M_WNDCLASSEX::Register(クラス名){...}
      の様にしておきます。
    2.コールバックは独立した関数です。肝はWM_NCCREATEです。
            LRESULT CALLBACK  callback(
                HWND     hwnd, // Window Handle
                UINT       msge, // WM メッセージ
                WPARAM  wpar, // param 1
                LPARAM  lpar) // param 2
             {
                // HWNDの拡張領域からオブジェクトを取得
                WND_CALLBACK * obj = ( WND_CALLBACK *)::GetWindowLongPtr( hwnd, 0);
                switch( msge){
                case WM_NCCREATE:{
                  // CreateWindowsEx()から渡されたWindowObjectを取得する
                    CREATESTRUCT * cs = ( CREATESTRUCT *)lpar;
                    WND_CALLBACK * obj = ( WND_CALLBACK *)cs->lpCreateParams;
                    // HWNDの拡張領域にオブジェクトを保管
                    ::SetWindowLongPtr( hwnd, 0, ( LONG_PTR )obj);
                    // HWNDをセットしてオブジェクトのcallbackを呼ぶ
                    obj->HWND_Set( hwnd);
                    return obj->callback( msge, wpar, lpar);//インスタンスのコールバック
                    }break;
                default:
                    // 有効なオブジェクトならそのcallbackを呼ぶ
                    if( obj)  return obj->callback( msge, wpar, lpar);
                    // オブジェクトが無効の場合
                    else      return ::DefWindowProc( hwnd, msge, wpar, lpar);
                }
                return 0;
             }
    3.ウインドウオブジェクトは次のようになります。WND_CoreはHWNDのラッパークラスです。
             class WND_CALLBACK : public WND_Core
             {
                 public:
                 // クラスごとのコールバック関数
                 virtual LRESULT CALLBACK callback(
                    UINT    msge,     // WM メッセージ
                    WPARAM  wpar,     // param 1
                    LPARAM  lpar)     // param 2
          {
                    switch( msg){
                        :
                    }
                    return 0;
                 }
             };
    4.アプリケーションクラスはメッセージループと、メインフレームを持ちます。
            WPARAM Run()//メッセージループ
            {
                 MAIN_Frame_Create();
                 while( ::GetMessage( &msg, NULL, 0, 0)){...}
            }
            virtual BOOL MAIN_Frame_Create() = 0; // メインフレームの構築
             (その他省略)
    5.メインフレームは概ね次のようになります。
            class Q_WND_Frame : public WND_CALLBACK
            {
                M_WNDCLASSEX wnd_class; // ウインドウクラス
                // ウインドウクラスの登録
                BOOL Register(){ return wnd_class.Register( _T "MAIN_APP_FRAME")); }
            };
            // ウインドウの作成
           virtual BOOL Win_Create(){
                 // クラスを登録
                 Register();
                 // ウインドウの作成
                 :  
                 WND_Core::Create(  wnd_class.lpszClassName,
                            HWND_DESKTOP,
                            rc,
                            NULL,
                            WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                            NULL, 0, m_Instance,
                           this); // ★これが重要でCreateWindowExの最後の
                   // 引数lpParamになり、WM_NCCREATE時の
                                  // CREATESTRUCT::lpCreateParamsに渡されます。肝です(笑)
                  }
    6.HWNDのラッパーの実装です。
            class WND_Core {
                HWND hwnd; // ウインドウハンドル
                // HWNDのセット
                void HWND_Set( HWND ex_hwnd) { hwnd = ex_hwnd;}
                // ウインドウの作成
                HWND Create( ....) // 省略
            };
    てな感じですね。他にも技をいっぱい使わなければいけませんが
    動き始めると楽しいもんです(^^;)。
    でも、ダイアログは別立てで実装しなければならないので注意しましょう。

    参考になれば幸いです。

    • 回答としてマーク 七空白 2010年4月2日 8:31
    2010年4月2日 1:03
  • 補足...

    仮想関数のコストを気にするなら、呼び出し形式コストを気にしたほうがトータルでは速くなると思います。

    ただし、例外があるとすれば、クラスメンバーに無意味に virtual が付きまくってる場合。。。これはNGですね。

    そうじゃなければ、それほど気にしなくてもいいと思いますよ。DLLの呼び出し(APIを含む)も、呼び出し手段的には仮想関数と同じですし(関数テーブルの位置がインスタンスがもってるのか、リンカーが用意したテーブルなのかの違いだけ)。

    この辺りは、アセンブラをはきだしてみればすぐにわかります(もちろん読めるなら。。。ですが、読めないとこのレベルの最適化は議論の意味すらないので)。

    ついでなので、API関係についても...

    SetProp/GetProp ですが、利用は確かに容易ですが、コストが非常に高いです。

    まず実行コスト...文字列については、ATOMを渡すことで多少なりとも軽減できますが、そもそもそのATOMの値がシステムグローバルな値なので、何も考えずに文字列のまま利用すると、一度作ったら最後システムリブートまでずーっと生き残ります(そういうものなので)。

    なので、これについては、よほどのことがない限り利用しないほうがいいです。SetProp/GetPropするなら、個人的には、std::map<std::string,obj*> や、std::map<HWND,obj*> のほうがよっぽど低コストで作成できる気がします(オブジェクトは、スレッドローカルストレージに格納したグローバル変数でよい)。

    最後にメッセージについても...

    ちゃんと調べられたから書いておきます。

    トリガーに使うのは、割り当ては、WM_NCREATEを、解除には WM_NCDESTROY を使うようにしてください。

    これらのメッセージの外側でもメッセージが流れ続けますが、それらはすべてデフォルトプロシージャに流しこむようにしてください。

    あと、メッセージの順番は絶対に期待してはいけません。実際、XPとVista、Win7では違う場合もありますし、VisualStyle の有無、MDIかどうか?ダイアログなのか、子ウィンドウがあるか?などいろいろな部分でメッセージの発生順が変わります。

    ウィンドウのリサイズ一つとっても、OSの設定によってメッセージの流れが変わります。

    チューニングする際にやってしまいがちなことの一つですが、意識しておかないとOSが変わるたびに書きなおしということになりかねないので。。。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月3日 7:33
    2010年4月3日 2:48
  • 呼び出しコストは、トータルで考えないとだめなんですが...

    基本的には、呼び出し形式にどれを選ぶか?(cdeclなのかstdcallなのかなど)と、スタックにどれだけ積むか?(引数の数)の2つです。

    fastcall や thiscall をつけてもレジスタの数が限られているため、それを超える数の引数を与える場合必ず push/pop が入ります。

    ですが、これを構造体にしてそのアドレスを渡すようにすれば、push/pop がなくなる(fastcallなどの場合)とか、絶対的な数を減らすことができるため、高速化が期待できます。

    もちろん期待できるだけで、本当に早くなるのか?はアセンブラを見てみないとわからんですがw

    もちろん関数を呼び出す数も少ないほうが呼び出しコストがなくなる分速くなります。

    ただ、このレベルのチューニングは、今やコンパイラレベルで行ってることなので、ライブラリを作るなどであれば、不用意にvirtual を多用しない、何でもかんでも引数にしない...という程度で十分期待できます。

    むしろ、ソースレベルの可読性を上げることを考えたほうがよっぽど効率よく開発できると思います。

     

    APIの本来の使い道...

    SetWindowLong(Ptr)と、SetProp はそれぞれ前者がコントロール用(ボタンやエディットなどではなくユーザーコントロール)、後者がグローバルフックを行うルーチン用に用意されたものです。

    そのため、前者はウインドウクラスレベルで設定可能なものとなっており、後者は後から誰でも使え、かつシステムグローバルな識別子をもつものとなっています。

    なので、特定のアプリケーション内部で完結するものについては、システムレベルで余計なエリアを確保しないで、アプリケーション内でのみ利用可能なものにしておくほうが余計な管理コストが発生しない分全体効率(自分以外を含むOS全体の利用効率)が上がるといえます。

    もっとも、このあたりの設計思想やその前提条件あるいは知識は、Windows 3.x 以前の時代にまでさかのぼるため、C++ ではなく、C言語ベースで開発を前提としているなどもあるのですが...

     

    >cbWndExtraとSetWindowLongPtrを使う場合、拡張したメモリは単に

    拡張領域は、そこに何か(エリアとして一致していれば何を収めてもいい)を保持しておくエリアを用意するだけで中身が何か?はWindowsは一切考慮しません。

    それがポインタであっても、ポインタ自体は、数値型のデータの一形式とみなせますので、他の数値データと同じように扱います。

    ですので、拡張領域に収めた内容がmallocなどで確保したものなら、別途アプリケーションサイドで解放する必要があります。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月3日 14:43
    2010年4月3日 10:28
  • アセンブラ周りについては、わからん!ということであれば、深入りしなくてもいいと思いますし、しない方がいいと思います。

    現在のWindowsプログラミングなどいわゆるデスクトップ系PCなどでの開発であれば、よっぽどひどいコードを書いていない限り、それが原因で体感できる速度差は発生しないと思います。

    むしろ、速度的な最適化は、コンパイラに任せ、可能な限りシンプルな設計と実装を心掛ける。式、文の絶対数を減らすという方向でコーディングしていくスタイルをとればいいと思います。

    そのうえで、むやみに virtual はつけない、本当の意味で一過性のあるもの以外は引数にしないなどを心掛ければそれだけで良質なコードを目指せると思います。

    >まぁ普通に意味考えて作ってる限り、水面下での軽いアセンブリ命令数個程度の差なら、そんなに心配はないかなとw

    Pentimum4シリーズくらいから後のCPUであれば、数個どころか10~20は、パイプラインに飲み込まれてしまう可能性が高いですし、いい感じで投機実行できれば数十どころか、数百ステップ程度は実行速度としての差が出なくなります。いまどきのCPUの性能は、アセンブラコードレベルでは測りきれないので。。。

     

    ・std::map の速度

    最適化の有無で大きく差が出る理由がわかるようになったら、もう一度今までの一連の流れを読み直してみるといいでしょう。それこそ。。。それが最適化というものです。。。というくらいな世界です。

     

    ・拡張領域そのものは、ちゃんと解放されるのでしょうか?

    はい。ことらは確実に解放されています。ウィンドウを作成したときに確保するメモリ(プロシージャやウィンドウの状態保持用のエリア)サイズにプラスして、メモリアロケートしていますので、器そのものは奇麗に解放されています。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月3日 18:32
    2010年4月3日 16:51
  • 数が十分少ないのなら、汎用のコレクションクラスを使うより特化したクラスを使う方が圧倒的に有利です。汎用性の高いもの vs それ専用ではやはり専用のほうが圧倒的に有利です。

    ま、この辺りは凝り始めるときりがない世界でもあるので、逝っちゃう前にほかのところを詰めておくことをお勧めしますw

     

    おまけ。。。だなw

    適当に書いたコードのやつに突っ込み入れるのはどうよ?とも思うのですが。。。

    WM_TIMER、WM_DESTROY の二つについては、DefWindowProc も呼び出すようにした方がいいです。

    また、WM_KEYUP についても、if文外れた場合はやはりデフォルトを呼び出すことをお勧め(この場合はifの中を通っても呼び出してもいいけどw)します。

    これもまたお作法の一つ。自分自身で作ったウィンドウであっても、自分のプロシージャはシステムデフォルト処理の横取りをしています。

    横取りしないといけないもの(画面再描画(WM_PAINT)など)ももちろんありますが、基本は、自分で処理してもデフォルトは呼び出すという形のほうがいいです。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月4日 5:37
    2010年4月4日 0:55
  • >公式な一覧表とかってありますか?

    たぶんないと思います。

    基本的なところは、

    • デフォルトに任せると都合が悪いところは自分で行う(Ex. WM_PAINT)
    • 知らないものは触らない(Ex.WM_TIMERで未知のIDで通知が来たなど)

    というところですかね。

    むずかしいことを考えるのではなく、つねに自分以外にも同時にプログラムが動いており、お互いがお互いのことを思いやって動作するように心がけるということを考えていればいいと思います。

    実際Windowsアプリケーションの設計思想の根幹は互いのプログラムが性善説に基づいて動作しており、譲り合いの精神がいきわたっていること。。。ですからね。Ver.1.0のころから。。。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月4日 13:19
    2010年4月4日 6:02
  • HWND自体はシステムグローバルなオブジェクトです。残念ながらC++のようなアクセス制御を持っていません。プログラム内部で抱えているHWNDはあくまでも参照情報で、その実体は、Windowsそのものが管理しているものです。

    また、EnumWIndowsやGetWindowなど、いくつもの手段でウィンドウハンドルを探し出すことができます。

    この辺りは、Spy++というツールがあるので、一度立ち上げて自分自身のアプリケーションのウィンドウをスパイしてみるといいです。

     

    あと、寄生していない場合でも、マウスの右クリックのメッセージをデフォルトに流しておくと、後で WM_CONTEXTMENU が送られてくるなどという、標準的な拡張なども行われています。

    一番最近の例でいえば、Win7のマルチタッチのメッセージもデフォルトに流すといろいろなもの(おもに拡大縮小や、スクロールですが)に変換して改めてメッセージを送ってくれたりします。

    マルチタッチなどは新しいメッセージなのでブロックされることはないでしょうけど、マウスメッセージなどは、意図せずブロックしてしまうことも多々ありますので、やはり意識的に注意しておく必要があると言えるでしょう。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月4日 17:29
    2010年4月4日 14:01
  • 読みきっていただいてありがとうございます。

    もちろんウインドウコールバックを単一にするのと
    各ウインドウオブジェクトのインスタンスのコールバックが
    virtualになるのはトレードオフです。ただ、

    ・現在のCPUはキャッシュが数MByteあるため、v-tableごとキャッシュされると予測できる。
    ・本設計上の全てのウインドウは、このコールバックを通過する。

    ため、特に問題ならないと予想できます。
    それよりも、

    ・CWnd* CWnd::GetParent()など、MFCでは「一時オブジェクト」しか戻せない仕様の
     同等関数で、ウインドウオブジェクトの実体を戻せる(★キャストできる(笑))。
    ・クライアント矩形等の取得を、より基本的クラスに実装済みにできる。
     事実上WM_SIZEでクラスメンバの矩形に取得すれば十分。
    ・WM_COMMAND等、引数の上下WORDを分離してから下位に通達できる。
    ・WM_MOUSEWHEEL等、フォーカスウインドウに通達されるメッセージを
     マウスカーソル直下のウインドウに事前にパッチできる。
    ・マウス系メッセージ、キーボード系メッセージなど、一つの物理デバイスに
     複数のメッセージが用意されているのを一つのストラテジ
     (例えばOnMouseMsg()、OnKeybordMsg())にまとめてパッチできる。

    等の利便性を優先させた結果です。また、

    ・MFCと共存できる

    のも良いところです。

     

    • 回答としてマーク 七空白 2010年4月5日 15:02
    2010年4月5日 1:01
  • ・Spy++

    ツールバーにある、双眼鏡マークで、ウィンドウ検索ができます(出てきたダイアログからドラッグして照準を合わせる)。

    それで、自分のアプリのどこかのウィンドウにフォーカスを当てればツリーの該当ウィンドウ上に移動してくれますよ。

     

     

    ・ポインタの扱いとアライメント問題とか

    こっちは、Windows で動いてるかどうかではなく、利用するコンパイラやCPUの特性の問題ですね。どう扱われるか(扱うほうがいいか)は、仕様とかきちんと当たらないとだめだと思います。

    基本的に32bitCPUであれば、通常はアライメント境界は4か8がいいです。が、SSEを使うなら16にしておいてもいいでしょう。

    64bitなら8か16か。。。でしょう。ですが、これらはあくまでもCPUのメモリアクセス特性に合わせた形にしてやるのがよいのであって、言語仕様的にどうか?とかとはまた少し違う次元の話になります。

    あとは。。。パフォーマンスと同時にレイテンシも考慮しておくといいのかなぁ?

     

    ・MFCのCWnd::GetParent()とか。

    えっと。。。もともとそのウィンドウが現在のMFCのスレッド管轄下にない(==CWnd派生クラスにアタッチされていない)場合は、テンポラリオブジェクトが

    そうではない場合(そのスレッドで何らかのCWnd派生クラスにアタッチされている)場合は、そのウィンドウオブジェクトのポインタが返ってきます。

    なので「一時オブジェクト」しか返さないわけではありません。誤解してる人が多いので、ここは突っ込んでおかねばw

    正直な話、ほぼすべての状況において、CWnd::GetParent は必ず CWnd 派生クラスの何かを返すはずです。

    そうじゃない条件は...

    1. 別のスレッドにある子ウィンドウ(作ると効率が劇的に悪くなるため絶対にやってはいけないマルチスレッド化の一例)
    2. OLEインプロセスサーバー(含:ActiveXControl)における、アプリケーション的なメインウィンドウ(実際は子ウィンドウ)
    3. 2の時のツールバー

    おそらくは、この程度です。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月5日 15:02
    2010年4月5日 2:38
  • >レイテンシというと...

    ご、ごめんなさい。そこはレスポンスです...なんでレイテンシなんだ?>おれ

    一応。。。チューニングのかなめとしては、パフォーマンス(絶対的な速度)、レスポンス(反応の遅延)、レイテンシ(処理の遅延)の3つをどうするか?というバランスの上に成り立ちます。メモリだけじゃないということは覚えておいてください(英語がカタカナのままの場合ってあやふやな意味でとらえられてることが多いんですよねw)。

    あとは、必要になってから考えればいいと思います。

     

    ・CWnd::GetParent()...

    CWnd というのは、MFCのウィンドウクラスの基本クラスです(HWNDをもつクラス)。その派生クラスとして、ボタンとか、エディットコントロールとかいろんなものがあります。

    ちなみに、ATLは、CWindowです。実装スタイルは全く違うので注意してください。どちらもMSDNライブラリ(オンラインでよい)にリファレンスがありますので、メソッドの一覧(すべてではない)はある程度見れます。

    WindowsAPIのレベルでは一時オブジェクトは、プロシージャのパラメータ(おもにLPARAM)ですかね。様々なコントロールの通知系メッセージのパラメータの大半はその場限りのオブジェクトです。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月6日 8:44
    2010年4月5日 16:09
  • >レスポンス(反応の遅延)やレイテンシ(処理の遅延)とかとはどういうところで分類されるものなのでしょうか?

    こちらも、基本は計測です。

    おおざっぱにいえば、

    パフォーマンス=一つのメッセージを処理する時間(プロシージャに入ってきてそれを抜けるまでの時間など)。

    レスポンス=処理を要求してから実際に処理されるまでにかかる時間(再描画を行う必要が発生してから実際に再描画され始めるまでの時間など)。

    レイテンシ=処理を要求したときに発生する待機時間など(自分でどうにかできるものではない時間)

    という感じです。

     

    クラスオブジェクト...

    のほうは、なんか無理やりシングルトンオブジェクトを作ろうとしてるからなのか、かなり歪みがでてる気がします。

    と。。。それとは別に...

    テンプレートの実装を inline スタイルにしていなくてもコンパイル時点でインライン化されると思いますよ。多少なりとも最適化をすれば...

    あとは、オブジェクト(C++のクラスも、HWNDなどなども)の個々の寿命とその関係性をどう管理していくか?なので、これがベストという解はおそらくないでしょう。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月6日 13:16
    2010年4月6日 11:32
  • >レスポンスの改善となると...

    OSやCPUはもちろんですが、時代の変化もありますよ。

    CPUはマルチコア化が急速に進んでいます。つい先日、1ダイ(ニコイチではない)で8コアなCPUが出てきました。後5年もすれば物理コア二桁という時代が来るのでは?と思います。

    それに、今までのように32bitOnlyではなく64bitも同時に考慮(バイナリが異なるのでソース互換)していかなければという時代になっています。

    難しいですけどねw

    >なかなか避けがたいところではないでしょうか

    です。だからこそ設計に時間をかけるし、ライブラリを創れるだけの技量のある人は重宝される(このご時世でも転職先がある)のです。

    それだけ難しい世界だし、一朝一夕でどうにかなるというものでもないのですが...

    >テンプレートに渡すT

    ここは重要ですよ。今までTが何の役割も与えられていなかったのが、明確に役割をもつようになっています。

    この辺りは、もう少しさまざまなテンプレートライブラリの実装に触れてみることをお勧めしたいところですが...STLのソースを眺めるだけでも大分違う気がします。

    あと。。。ここでTは「必ず」ポインタとして使われるんですよね?

    であれば、T に Hoge* を指定するのではなく、たんに Hoge とする形でもいいと思います。

    template<class T>
    class Single
    {
     T* me;
    //... 以下省略...
    };
    

    こんな感じ。こうすると、p として、渡しているプロシージャを T::WndProc という形で直接指定することができるようになります。名前が固定されますが、仮想関数だと思えば同じですし、

    引数が減る分タイプ量も減りますので、バグ発生率が減ります(タイプ量から測定される定量的な値)。

    また、引数が減るため、呼び出しコストが下がりその分速度向上が期待できます。

    今の実装だと、クラスであるということの利点が半減してませんか?わざとかもしれませんけど...

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月7日 6:45
    2010年4月7日 2:37
  • >というのは解決不可能でエラー

    あれ?エラー?あ。。。private 派生だからかな?

    えっと。。。従来のOOPにこだわる必要がありますか?

    ちょっと見なおして思ったのですが、OOPにこだわるよりも、テンプレートメタプログラミング(genericsプログラミング)と呼ばれる技法を使うほうが、いい気がします。

    従来のOOPの利点であるカプセル化をある部分で否定してますよね(仮想関数とか)。であればそもそもそれを必要としない技法を使ったほうがいいのでは?と思います。

    となると、あまり書き換えずに。。。とはいきませんが、使えるようになるとコンパイル時間を犠牲にして、実行速度を改善することができます。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月7日 8:27
    2010年4月7日 7:48
  • >特に何が難しいかというと、意図しない余計なコードが可能な限り生成されないようにすることが、ですw

     意図しない余計なコードが何を指しているのかわからんのですが...書かれてあるもの以外には生成されないはずですが?

    私がわかってないだけかもしれませんけど。

     

    ・デストラクタに virtual を付ける必要がある場合

    オブジェクトを親クラスの型ポインタで保持しており、なおかつ delete する場合だけですよ?

    今回の場合は、親のクラスの型で保持する箇所がないので、多分不要です。

     

    派生しないで書いてみました。大きな変更点はHWNDをもつオブジェクトがSingleじゃなくなってることくらいです。同じ部分は、... とかやってるので、コンパイルできませんけどw

    template<typename T>
    class Single
    {
    public:
      static T* me;
    
      HWND Create( T* p, HWND hw,  LPCTSTR cls, LPCTSTR c=_T("") )
      {
        if (me) return;
        me = p;
        HWND wnd=CreateWindowEx(0, cls, c, WS_OVERLAPPEDWINDOW|WS_VISIBLE,
            0,0,200,150,hw,null ,HINST::me,0); 
        SetWindowLongPtr(wnd,GWLP_WNDPROC,(LONG_PTR)T::WndProc);
        return wnd;
      }
    };
    class Success 
    {
    private:
    static Single<Success> me;
    HWND wnd;
    static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    public:
      Success(HWND hw=NULL)
      {
        wnd = me.Create( this, hw, ... );
      }
    };
    LRESULT CALLBACK Success::WndProc( HWND hw, ... )
    {
      _ASSERTE( me.me->hwnd == hw );
      ...
      _stprintf_s(c,30,_T("%p MOUSEMOVE " ),me.me );
      ...
    }
    

    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月7日 15:29
    2010年4月7日 9:24
  • ば、バグ見つけたw

    Success クラスの WndProc は、public にしてください。いくらなんでも、private では。。。アクセスできないw

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月7日 15:29
    2010年4月7日 10:21
  • えっと。。。なんとなく危険信号を感じました。

    継承しないとか、メタプログラミングとか手段が目的になりかけてる気がする!

    一応は、それなりに形になっていますのでこれまでのやり取りが無駄だったとは思いません。

    ですが

    後で一部変更するとき他のところがなるべく影響受けないようにと思って、今までほぼウインドウ毎にウインドウクラスを登録、としてきたのですが

    大規模アプリケーションを作るなら、プロシージャのアドレス以外が一致するウインドウクラスは、わざわざそれぞれを別々に登録するよりも、あらかじめ汎用的なウインドウクラスを作って、同じものを指定してウインドウを生成した直後に、プロシージャの変更をするのが一般的なのでしょうか?

    という質問に至った経緯も含めてもう一度考えてみることをお勧めします。

    Windows プログラミングは、C++とは異なるオブジェクト指向プログラミングを要求しているということも加味して。。。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月7日 17:52
    2010年4月7日 16:42
  • >あくまで「こういうことも考えられる」という考え方がたくさん集まれば、それは全てに波及する可能性があります。

    よかった。

    手段が目的化してしまうと、気が付いたら使えないものに。。。なんてこともあるのでw

     

    >取り出し用ハッシュ(?)的なものを作ってみました。

    そうか!順番は特に必要ないんだから、ハッシュでもいいのか。なら、対抗馬として std::hash_map(std::tr1::unordered_map) を提示しましょう。こうなったらとことんソース書かない方向でがんばってやる!<おい!

    unordered_mapは、TR1(C++0xの先行公開版と思ってよい)でhash_mapから名前の変わったものです。内容はほとんど同じらしいので、使い方も一緒ですが、ネームスペースが違います。

    VS2010でどうなるかは未確認です。こちらは 4/13 を待って。。。だな。あと一週間どういう発表があるのか楽しみです。<脱線しまくり!

    リストは、std::list がありますね。こちらも単純な線系リストとしてなら性能差はあまりないと思いますが、アロケータとかも絡んでくるので、それなりに微妙。。。かな?

    あと、ポインタを数値型としたいのなら、Win系なら INT_PTR(UINT_PTR)がいいと思います。SDK定義の互換型なので、VC以外でも利用できると思います。ただしほかの環境(Linux/BSDなど)で定義されているのかはわかりません。

     

    あと。。。HWNDが1アプリで1000個とかのオーダーになったとしたら、こっちがんばる前にアプリの設計を見直してください(実験レベルなら問題ありませんがw)。

    目安としては、膨大なウィンドウを使うという場合でも3ケタにならない程度しか同時に存在しないくらいにしておくことをお勧めします。

    理由は、HWNDなどのシステムリソースは有限で、システムグローバルに管理されているからです(XPまでは確か全体で8192とかその程度だったはず)。

    このアプリが動いてると動作が不安定になるなどという状況は自分専用でも、あってはならないことですのでw

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月8日 9:16
    2010年4月8日 2:24
  • ・unordered_map

    使い方は、std::map<K,V> や、std::hash_map<K,V> と同じです。VS2008ProSP1では、<unordered_map> をインクルードするだけで使えてます。

    Hash32<T>の置き換え的に書くと...

    #include <unordered_map>
    
    
    
    std::tr1::unordered_map<HWND,int> a;
    int i=257275;        //適当にテンプレートに渡す型のものを作ります 
    
    a[wnd] = i;
    
    //取り出し
    std::tr1::unordered_map<HWND,int>::iterator itr = a.find( wnd );
    if( itr != a.end() ){
      int j=itr.second;
    }
    //値のセットが保証できるのなら...
    int j=a[wnd];
    

    と。。。こんな感じです。std::map と同じように使えますよ。VS2008ProSP1なら、これで動問題なく動きます。コンパイルエラーがなければw

    ・INT_PTR

    typedef された値なので、これをそのまま使ってもいいと思いますよ。セットされていない環境では、これに相当するものを別途定義すればいいだけですし。

     

    ・ウィンドウの数

    同時に存在できるウィンドウの数には制限があります。これは空きメモリ量ではなく、システムで固定された値(Vistaで変更されてるとは思いますが、資料がないので不明)なので、それを超える数のウィンドウを作成することができません。少なくともXPの初期までは、8192個とされているといわれていました(数えられるわけじゃないので詳細は分かりませんが)。

    8000程度しかウィンドウが作れないところで1000近くとなると、1割以上食いつぶされることになります。実際は、OSやらいろいろな常駐ソフトやらがウィンドウを作ってるので、利用するときに起動して使うアプリで1000というのはほぼ限界に近い値と思ったほうがいい値です。

    これから先少しずつあるいは、大きく制限は緩和されるとは思います。が、それでも限界数が8192じゃなく、10240とか16384とかそういう数字になるというだけで、上限撤廃とはならないと思います。

    それに、WPFなんかは内部的にはウィンドウ(OSでいうウィンドウ)を作らず、そのような感じ。。。で個別に領域処理してますし。きっといろいろ限界が見えているんだと思います。

     

    ・ドラッグドロップとか。。。

    マウスオペレーションで画像を動かしたり。。。だと思いますが。。。こちらはやり方はいろいろあります。

    ですが、移動するためのウィンドウを作ったりはしません。

    アプリないだけであれば、マウスメッセージをキャプチャーしてその操作が終わったところで、データを適切な位置に貼り付けるなどを行います。

    ほかに、OLEのDrag&Dropの仕組みを使う場合もあります。通常はアプリケーション間でのやり取りで使いますが(エクスプローラからファイルを落とすなど)、それ以外でも使えるので、うまく活用すればある程度はお任せ。。。とできます。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月8日 22:00
    2010年4月8日 17:11
  • ・unordered_map

    エラーが出てしまうんだとすると...ファイルが壊れてるか、なんか別の要因のような気がしますがエラー内容がわからないとなんとも言えませんね。

    んと。。。挿入速度で負けてる。。。と...

    operator[] はすごくわかりやすいけど、速度面でいまいちなので、insert にすれば追加速度はちょっと期待できるかな?

    それでも、専用のほうが多分早いですけどねw

     

    >なかった場合は作って返却するようになってるのかな?

    std::map, std::hash_map も同様ですが、左辺に [] が来る場合は、受け入れる側なので、器がなければ用意します。右辺に来る場合は、const が優先されると思うので、作らない(なかったら例外)はずです。

     

     

     

    >追及の余地ありということになりますか

    おおいにあります。

    なぜか。。。というと、HWNDはその存在自体がかなりのオーバーヘッドをもちます。領域管理も多いほうが煩雑になりますし、さまざまな状態管理も数が多いより少ないほうがやりやすいのは、それなりに経験を積んだプログラマなら容易に想像できるのではないか?と思います。

    もちろんむやみに減らせばいい(なくせばいい)とはなりませんが、少ないほうがオーバーヘッドになる分が減るので確実に速度向上は期待できます(おもにレスポンスタイムの向上や、レイテンシの低減)。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月9日 8:28
    2010年4月9日 8:06
  • >挿入時のポインタ移動中にWM_PAINTが飛んできて

    ここがすごく気になります。実装がわからないので、なんとも言えませんが、普通にやってたらそのようなことはまずもって発生しないはず。

    あるとすれば、コレクションに突っ込む処理を別スレッドでやっていて排他制御していないとか、ソート処理の中で、メッセージポンプ回すとか。。。

    今その作業中にあってはならない事例をやっているというパターン以外にはありえないはずなんですが。。。

     

    ちなみに、エラーメッセージは、テンプレートクラスを使ってるので、ほとんどすべての状況においてわけのわからん内容のことが多いです。なので、エラーメッセージ見ないと助言できないです。

     

     

    HWNDをどうするか?はそこで何を表現するかに依存するので、なんとも言えません。ただ一つ言えることは

    その部分は、汎用的に表現するのは非常に難しいことが多い。

    というところかな。

    汎用的に扱える部分もあると思いますが、多くはそれ専用か専用に近いものになることが多いです。

     

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月9日 14:17
    2010年4月9日 10:18
  • >特定のデータが取り出せなくなってしまうという単純なバグだったようですw

    なるほど。。。なら、納得です。単一のスレッドからアクセスしているのなら再帰しない限り重複アクセスは発生しないのになぜ???

    と思ったので。

    ・エラー

    うーん。。。テンプレートパラメータに渡してる型に問題がある気がする。。。けどこれだけじゃよくわからん。

    ちゃんとエラーを突き詰めるなら、新規にコンソールプロジェクトを作成し、

    ソースの stdafx.h の次の行に

    #include "stdafx.h"
    #include <unordered_map>
    
    std::tr1::unordered_map<int,int> hoge;
    
    int _tmain( ... )
    {
    ...
    }
    
    

    となるようにしてみてください。

    これでエラーが出るなら、ソースが壊れてます。そうじゃなければ、もうエラー詳細とソースを見ないとちょっと無理かも。。。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月11日 10:00
    2010年4月10日 2:10
  • ソースは。。。結構な量があるので見てません。

    全部のウィンドウをなくしてしまうという方向は、環境依存でいろいろ出るので、あんまり現実性はないと思います。ただ、仕組みそのものはドロー系ツールで役に立つと思うので、研究対象としては悪くないと思いますけどね。

     

     

    WS_CLIPCHILDRENであっさり実現できる

    仕組みそのものは、SelectCilpRgnでできます。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月11日 15:57
    2010年4月11日 11:05
  • >とりあえず載せておけば...

    そういってもらえると助かりますw<ただの手抜きじゃん!


    int main(array <System::String ^> ^args)

    こいつは...C++/CLIですね。てっきり全部Nativeオンリーだと思ってた。

    だからと言って、エラーになるというわけじゃないはずなので、とりあえず C++/CLI でも新規にプロジェクトを起こして(TestCppCli)追記してみました。

    #include "stdafx.h"
    #include <unordered_map> //※
    
    std::tr1::unordered_map<int,int>	hoge; //※
    
    using namespace System;
    
    int main(array<System::String ^> ^args)
    {
    	hoge[0] = 1; //※
    
        Console::WriteLine(L"Hello World");
        return 0;
    }
    

    エラー出ませんでした(記述箇所はコメントでマーク入れたところ)。なにかがなにかが壊れているのかもしれません。

     

    ・ValidateRect

    これは、無効化していない領域ですと通知するもので、WM_PAINT の中(BeginPaintを呼び出した時点)で利用しているHDCには影響を与えません。

    やってみるとわかりますが、、、がっつり描画してくれますよw

    WM_PAINT は、頻繁に呼ばれるとちらつきが発生するのと、描画の最適化は、他のリソースをたくさん使ってしまうため、限界があることから、頻繁に呼び出されないようにする工夫がされています。

    その一つが、複雑になりうる更新範囲。これについては、長くなるので割愛。

    ValidateRect は再描画を待たずに描画したので、ここは更新しなくていいです!というための通知用です。

    最終的には、次回WM_PAINTで更新すべき範囲として、常にリージョンに追加削除しながら範囲を管理しています。

     

    ・円が汚い

    これは、いわゆるジャギーがでるのだが。。。というものですね。

    アンチエイリアスをどうするか?ということに収束するのですが、まぁいろいろあるのでまずは調査してみてください。

    ただ、クリップ任せではどうすることもできない部分です。クリップはピクセル単位で描画するエリアを限定させるものですので、描画の途中のエリアをぶった切るもので境界部分ではありませんので。

    円がきれいに描画される...については、これ以上はとりあえず意見を言うのは差し控えます。この先はプロの仕事に影響しますのでwww

    マウス処理は。。。

    1.WNDCLASS で指定されているマウスカーソル

    2.SetCursor で指定したマウスカーソル

    の2つではなく...

    1.WM_SETCURSOR メッセージでデフォルト処理される1を含む何らかのマウスカーソル(場所による)

    2.WM_SETCURSOR メッセージで自分でSetCursor したマウスカーソル

    3.どこかのメッセージでSetCursorしたマウスカーソル

    のいずれかになります。

    マウスカーソルの寿命は、WM_SETCURSOR が呼ばれるか、SetCursorするかのどちらかとなっています。

    通常、ウィンドウのどこかにいるかどうかでマウスカーソルを変更する場合は、

    WM_NCHITTEST でHTCLIENT ならその座標から、カーソルを何にするかをチェックしておき、次に来る WM_SETCURSOR でマウスカーソルをセットするという形で設定します。

    基本的にSetClassLong はやってはいけません。SetWindowLong はまだ外部変数を書き換える程度の意味と同じだからいいですが、SetClassLong は、class 定義を実行時に書き換えてしまうので、そうそう行っていいものではありません。

    たとえ、そのクラスを使うウィンドウが一つしかないという場合でも、OS内部の整合性維持などの都合で莫大な変更コストが伴いますので。

    なので、ある一定条件の場合だけ背景色を変えたいという場合は、WM_ERASEBKGND で背景描画を行うようにすることで、そのウィンドウのその時の状態だけ背景を変えるという形をとります。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月12日 15:37
    2010年4月12日 7:06
  • ・エラー

    Expressだからなのかな?いまはExpressをインストールしている環境がないのでちょっとわかりかねます。。。でもこの辺り変わらないと思うんだよなぁ。。。とりあえず、こちらについてはちゃんと調査するなら改めてスレッドを立ててください。

    ・マウス関係...

    まず、計測対象が間違っています。SetClassLongは、内部的にはWNDCLASS::???? = param; という処理を行うものです。

    当然ですが、それを利用して何かをする(カーソルをセットする、背景を塗りつぶす)という部分は呼び出しタイミングでは行われません。したがって計測データとしては全く役に立ちません。

    マウスカーソルの設定を計測するのであれば、HTCLIENT でWM_SETCURSORが来たときのデフォルトプロシージャの処理時間VS独自にセットした場合の処理時間(こちらもWM_SETCURSORレベル)で比較するのが一番公平です。

    SetCaptureしている場合のマウスメッセージですが。。。SetCaptureすることで、マウスメッセージは常にそのウィンドウの「クライアント領域にある」マウスメッセージとして送られてきます。

    したがって、マウスカーソルが今どこを指しているか?というWM_NCHITESTや、場所に合わせたマウスカーソル設定を行う WM_SETCURSOR などが要求されることはありません。詳しくは、WM_SETCURSOR メッセージのリファレンスなどを読んでもらいたいところです。

    ちなみに、さっきリファレンスを見ていて気が付きましたが、WndClass の部分でのクラスマウスの部分でWM_MOUSEMOVEとなっている個所がありました。これはWM_SETCURSORの間違いです。WM_MOUSEMOVEは要求ではなく通知メッセージですので。

    ・ウィンドウの更新

    ValidateRect については、記述の通り。。。で、切り札が「DDB」によるBitBltか?といわれるとこちらは疑問。そのDDBを生成するコストも考慮してなおであれば、最速の部類にはなります。が今でもそれが本当かどうかははっきり言ってドライバ次第なので、どこにチューニングしてあるかはわかりません。

    一応。。。WindowsGDIが扱うビットマップ形式は、DDB(Device-Dependent Bitmaps)と、DIB(Device-Independent Bitmaps)の2つと、CreateDIBSection API でのみ作成可能な 通称SectionDIBと呼ばれる特別なDDBの3つがあります。

    ビットマップとしてどのように扱うか?にも影響があるので一概にどれがいいとは言えません。また、ハードウェアによって最適化可能なポイントが異なるため、DDBが最速であるというわけでもありません。

    また、Vista ではGDIはソフトウェアですべて処理する形になっているため、DDBという名前でも内部的にはDIBと同義だったりしますし、Win7ではDirect2Dが入るため、再びDDBが意味をなしてはいても最速の座はDirectX11対応の場合はDirect2Dに譲る形になると思います。

    このように、画面描画系は特定環境で早ければ最速とは言えないため、トータルコストでみてどれが「一番無難なのか?」が意味をなしてきます。

    というところをまずは踏まえてもらったうえで...

    描画処理は、非常に単純であり、常に上書きが行われます。

    この上書きの条件が

    1. 背景で塗りつぶして(WM_ERASEBKGND)から、前景描画(WM_PAINT)を行う
    2. WM_ERASEBKGNDは行わないようにして、WM_PAINTだけを行う

    のどちらかになります。

    一応注釈だけ。あくまでもイメージで、現行のOSではちょっとだけ異なる部分がありますが、通常は意識しなくてもいいようになっているのと動作そのものが変わるわけではないので割愛しています。

    具体的には、2のパターンは、InvalidateRect( ..., FALSE )で無効化した(=再描画を要求した)エリアかどうかという違いになります。

    どのくらい違うか?は、背景をどうやって処理してるか次第なので何とも言えません(ちらつきも同様)。この辺りは再描画テクニックの一つなのでじっくりと研究してください。

    あと。。。WM_SIZE はサイズが変わったよーという通知です。サイズ変更は、ユーザーの処理なのでメッセージじゃありません(この考え方は非常に重要なものです。ウィンドウは常に何らかの操作結果を受け取って動く受動的な仕組みの上に成り立っています)。

    ま、それはともかくとして。。。どうしてちらちらして見えるのか?をまず把握してからですね。ちらつき低減を考慮するのは。

    すでに書いていますが、Windowsの画面描画は非常に単純で、常に上書きです。どんなAPIをどのように呼び出しても必ずです。例外は一切ありません(強いて挙げればAlphaは少しだけフィードバックを得ますけど...実質的には上書き)。

    ですので、背景で塗りつぶしてからほかのものを描画というだけで大きくイメージが変わることになり、結果ちらちらする格好に見えます。

    また、WM_PAINTはシステムレベルで最適化されていますので、強制呼び出し(UpdateWindow)をしない限りは適度に呼び出しそのものが抑え込まれています。

     

    ・WM_PAINT

    WM_PAINT自体は、システムレベルで最適化されるので、UpdateWindow API を頻繁に呼び出さない限り、何度もセットされてくることはありません(もちろん再描画の必要と判断された領域がない場合はUpdateWindowを呼び出しても再描画されません)。

    なので、WM_PAINT自体が発生される可能性を減らす努力が必要か?という点では考慮しなくてもいいと思います。

    ただし、描画を行う必要があるという現象が発生した時点で、即座に描画するのか(WM_PAINTだけが描画ではない、その場でGetDCすればいくらでも描画可能)、描画範囲を指定して InvalidateRectをし、しかるべきタイミングを待つのか?では当然ながら、ユーザーの目に変化が表れるまでの時間は変わります(前に書いたレスポンスタイム)。

     

    ・リソース

    OSからみれば、そこでプログラムが動いていることそのものが想像以上のリソースなので、少しくらい増えた減ったは案あり意味はないですよ。

    ただし、最適化というのは単に速くすることではありません。ここは非常に重要なことなのできちんと書いておきます。

    速度を向上する(パフォーマンス、レスポンス、レイテンシ)は確かに最適化としてはよくある事例の一つですが

    もうひとつ重要な最適化の一つにサイズの低減があります。

    実行ファイルサイズ、データサイズ(圧縮するなど)、メモリの使用量そのものの低減などなどこれもまた最適化の一つです。

    最適化とは、何らかの犠牲により、何らかの向上を図ることを言います。

    犠牲とするものが何か?それによって何が向上できるのか?は一意ではありませんので、なんとも言えません。もしかしたらやらなくてもいいことかもしれません。

    ですので、何を犠牲にして何を向上したいのか?を明確にしない限り、

    >BitBlt1回(及びそのためのバックバッファ)のみでも、既に裏で想像以上のリソースを使ってしまうことになるのでしょうか?

    これは想像以上に無駄なリソースを用意しているともいえるし、それによって劇的な速度向上が行えるので想像の範囲内でのリソース利用であるともいえます。

    これだけでは何をやっているか(1回にしたことでどんなメリットが出るのか)がわからないので無駄なのか効率的なのかはわかりません。

    速ければいいのが、無駄に10メガのメモリを消費しても、計測結果でわずかに速くなればいいのなら、必要なメモリでしょう。

    ですが、10メガもメモリを使って、違いがわからない程度の速度向上では意味がないといえば、無駄なメモリです。

    それはこれだけでは測ることはできませんので何とも言えません。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月13日 10:13
    2010年4月13日 7:39
  • とりあえず。。。一度仕切り直しが必要かな。。。

     

    ちょっと話題がばらけ過ぎましたが、ここまでのやり取りで率直に感じたのは、「Windows のメッセージシステムをちゃんと理解できていない?」です。

    まずは、正しい Windows の動く仕組み(特にメッセージのもつ意味とその作用)を学ぶ必要があると感じました。

    今持っている知識は全般的なプログラミング技術が大半で、Windows特有の技術にはそれほど詳しくない?と感じました。

    私の単なる勘違いかもしれません。そうであれば私の理解力が足りないだけなので申し訳ないです。

    時間が許すのであれば、大学の図書館とかで借りてで構わないので、まずは Programming Windows(C#やVB用のではない) あたりを読破してみることをお勧めします。

    ちなみに。。。C#やVB用のものも出ていますが、こちらは .NET Framework の場合用で書かれてあり、論点から異なるため、今のタイミングで必要な情報源としては意味をもちませんので注意してください。

    Programming Windowsの第5版あるいはそれ以前(5版より後のものは、.NET 向けしかないため意味をなさない)のものを読むようにしてください。

    もしすでに読んだことがあるというのであれば、今一度読み直してみる(コードは読み飛ばしてもかまわない)ことをお勧めします。

    この本ならWindowsの本質をつかさどるメッセージの流れもそれとなく気を使って書いていますし、どういうタイミングでどういうメッセージが来てどういう処理を行うべきなのか?ということも基礎的な部分は一通り網羅しています。

    おそらくは大半が知ってることだと思います。ですが、気が付かないでいたところや本当はそうなのか!という部分は少なからず絶対にありますので、まずは読破してみてください。

     

    ・HDC

    数字として表れる程度まで豪勢には使ってないと思います。ですが、HDC を一つ構築するだけでUSERやGDIのハンドル数が変わります。ただし、短命(メッセージ処理を終えるタイミングで解放される)な場合はタスクマネージャに数字として出てこないと思います(1秒程度の間隔で自己更新するため)。

    デバイスコンテキストとしてユニークに保持する情報としては...SelectObject で渡すユニークなGDIリソース種別ごとのエリア、SetBkModeなど、HDCを引数として受け取るSet/GetできるAPIごとのデータとなります。

    どれくらいあるのかは数えたことがないのでわかりませんが、100バイト以上は余裕であると思いますよ(100バイトを微々たるものと見るか、貴重なシステム共有リソースとみなすかは人それぞれなので言及しません)。

    ・HBITMAPなど

    GDIリソースは、GetObject で取得できる情報+データとしてそれを実現するための情報で構成されています。具体的に何バイト使ってるのかわかりません。ビットマップであればBITMAP構造体+ビットイメージを格納するだけの連続したメモリイメージ+その他制御に必要な情報程度は保持しているでしょう。それがタスクマネージャに出てくるかどうかはわかりませんが。

    ほかにも、HWND なら、WNDCLASSへの参照(ATOM値)+ウィンドウ状態管理情報(情報量不明)はもっているでしょう。さらに、更新領域情報(HRGNとしてもっていると思われる)も随時変化しながら保持していると考えられます。

     

    >ウインドウを非表示にするときはウインドウのクライアント領域のサイズを0*0にする、とかやってた方が俄然メモリ使用量が減る可能性が高いように思います。

    えーっと。。。思いっきり勘違いしてます。ウィンドウを最小化したりした場合に、メモリをスワップアウトする仕組みが発動します。

    詳しくは、NyaRuRuさんの日記(http://d.hatena.ne.jp/NyaRuRu/)にあるのですが、あまりにも古すぎて探せなかったw

    ま、他にも色々と有用な情報が載ってるので、暇を見てチェックしてみることをお勧めします。

    >あらゆるものはプログラマーが「何も」書かない限りは発生しないので(デフォルトの「要求」(?)も、ウインドウがなければそもそも存在しない)ある意味全部受動的だと思うのですが

    違います。

    ウィンドウを作成した場合は、それがどういうウィンドウであれ、要求はOSサイドからやってきます。プログラマが何もしない場合はデフォルトプロシージャがそれを肩代わりしてくれることで、何もしていないように見せかけているだけです。

    裏ばかり見ようとせず、まずは正しく表を見てください。そこにあるべきものあるはずのものが見えていない気がします。

    識者に頼るという勉強のスタイルもあります。がそれはあくまでも対面で質問ができるような場合であり、そうではない場合ある程度以上のレベルで自己研鑽が必須となります。扱う内容が複雑になればなるほど、そこで要求される知識レベルも高くなります。

    が、それ以上にしっかりとした基礎知識が必要となります。この部分はこういったやり取りの場では、全部すっ飛ばしていることが多く、それゆえにたがいに通じない(現にいくつかは通じていないと私は感じました)ということが起こります。

    たがいに通じない部分があるのは仕方ないとは思いますが、もう一歩突っ込んだ世界で花開いてほしいと思えばこその苦言でもあるので、まぁ親父のたわごとと思って、もう少し勉強してみてください。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月14日 15:19
    2010年4月13日 13:33
  • 私はあくまで 「何も書かなければ」 「ウインドウがなければそもそも存在しない」 と書いたのであって
    その「ウインドウを作成する」のはプログラマの行動です。

    そうですか。。。私はウィンドウがなければなんてことは一言も触れられていないと思っていましたが、ここの時点で齟齬があったようです。まさか、ウィンドウがない状態のことを記述しているとは思いもしませんでした。思い込みが過ぎたようです。申し訳ありません。

     

    「プログラマの行動」で「ウィンドウを作成する」ことができるウィンドウのお話をしていたのですか。大変申し訳ないのですが私が想定しているウィンドウとは別のもののようです。

    勉強不足で申し訳ありませんが、これ以上なにかを伝えることは私には無理です。識者の登場をお待ちいただくか、新たにスレッドを起こすなどして、話題にしていただければと思います。

     

     

     

     

     

     

     

     

     

     

    どこかにカチンとくるものがあったともいます。ひとえに私の文才のなさが原因だと思います。それについては本当に申し訳ないと思います。

    質問されっぱなしで、答えないことについても申し訳ないと思いますが、気力がそがれました。暇人じゃないのでこれ以上付き合うことは私にはできません。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月14日 15:21
    2010年4月14日 8:53

すべての返信

  • ATL や MFC のような既存のクラスライブラリを利用するのではなく、完全独自で同じようなクラスライブラリを作りたい。。。ってことですかね。

    やりたいことは、まさにATLやMFCが実現していることになります。もちろんこれらを使わず自分で実装するのもありです。
    #というか。。。私も独自実装もってるしw<今は使ってないけど...

    軽量なウィンドウプロシージャだけがほしいというのであっても、ATLの実装は参考になりますよ。

    ソースもVSのStandard以上なら添付されています(Ver.6以上ならどのバージョンにも入ってる)し、ウィンドウ周りだけなら、新旧問わず参考になります(というか同じだしw)。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月1日 17:38
    2010年4月1日 13:31
  • こんにちは、とっちゃんさんありがとうございます♪

     

    実は私は、本当はStandard以上を使いたいのですが(そのうち購入したいのですが)

    資金不足で今のところ(2008の)Express Editionを使っていまして…w

     

    これだとMFCが使えない…だったかで、MFCはよく分からないのですが

    ATLというのは名前だけ聞いたことがある程度で、より一層知りませんでしたw

    COM関連の何かでしたっけ…?

    ATLはどのようにすれば(あるいはどこにいけば)使ったり調べたりすることができるのでしょうか?

    2010年4月1日 13:43
  • Express ですか。。。だとすると、ATLもMFCもないですね。どちらも、Standard 以上に含まれています。

    今ならVS2010 のRCあるいはβ2があるので、それをインストールすれば、MFCやATLのソースは参照できます。ただし、開発途中のものなので、バグとか含まれてる可能性もあります(ウィンドウプロシージャ周りとかは変わってないと思いますけどね)。

    ATL(Active Template Library)は、ActdiveX Control などを作成する際に MFC のようなDLL添付型ではなく、軽量なスタティックライブラリベースで利用できるようにと作られたフレームワークです。

    初期の設計こそCOMサーバー用とされていましたが、実際にリリースされたものは、COMに無関係に使えるように作られたコンパクトなライブラリとなっています。コンパクトとはいっても、巨大じゃない。。。というだけで全体となるとそれなりの規模にはなってますけどね。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月1日 17:38
    2010年4月1日 16:40
  • ありがとうございます♪

     

    なるほど、そういうことでしたか。

    最終的にはVS2010の正式版を購入したいと思いますが、(特に小耳に挟んだalignofとかテンプレートの制限緩和とかのC++0xの細かい仕様改善がとても頼もしいと思うので)現在仮に

    VC++2008EEをアンインストールしてVS2010 のRCあるいはβ2、をインストールするとすると、もし何かの理由でVC++2008EEにいったん戻したくなったときに、全く不具合なく戻ってくることは出来るでしょうか?

    …その辺じっくり調べないと勇気が要りますw

    2010年4月1日 16:57
  • 自分も同じ手法でMFC風のファンデーションライブラリを作りました。
    アイデアはほぼ同じですが、構築手法がやや異なります。
    ちゃんと動いてますよ(^^;)。
    1.WNDCLASSEXを継承したM_WNDCLASSEXを作成。ウインドウ拡張BYTEに
      必要な量を確保します。コールバックはインディペンデントなインスタンス
      を指定します。
            lpfnWndProc = ( WNDPROC)callback;
            cbWndExtra  = ( int)8;
          :
            BOOL M_WNDCLASSEX::Register(クラス名){...}
      の様にしておきます。
    2.コールバックは独立した関数です。肝はWM_NCCREATEです。
            LRESULT CALLBACK  callback(
                HWND     hwnd, // Window Handle
                UINT       msge, // WM メッセージ
                WPARAM  wpar, // param 1
                LPARAM  lpar) // param 2
             {
                // HWNDの拡張領域からオブジェクトを取得
                WND_CALLBACK * obj = ( WND_CALLBACK *)::GetWindowLongPtr( hwnd, 0);
                switch( msge){
                case WM_NCCREATE:{
                  // CreateWindowsEx()から渡されたWindowObjectを取得する
                    CREATESTRUCT * cs = ( CREATESTRUCT *)lpar;
                    WND_CALLBACK * obj = ( WND_CALLBACK *)cs->lpCreateParams;
                    // HWNDの拡張領域にオブジェクトを保管
                    ::SetWindowLongPtr( hwnd, 0, ( LONG_PTR )obj);
                    // HWNDをセットしてオブジェクトのcallbackを呼ぶ
                    obj->HWND_Set( hwnd);
                    return obj->callback( msge, wpar, lpar);//インスタンスのコールバック
                    }break;
                default:
                    // 有効なオブジェクトならそのcallbackを呼ぶ
                    if( obj)  return obj->callback( msge, wpar, lpar);
                    // オブジェクトが無効の場合
                    else      return ::DefWindowProc( hwnd, msge, wpar, lpar);
                }
                return 0;
             }
    3.ウインドウオブジェクトは次のようになります。WND_CoreはHWNDのラッパークラスです。
             class WND_CALLBACK : public WND_Core
             {
                 public:
                 // クラスごとのコールバック関数
                 virtual LRESULT CALLBACK callback(
                    UINT    msge,     // WM メッセージ
                    WPARAM  wpar,     // param 1
                    LPARAM  lpar)     // param 2
          {
                    switch( msg){
                        :
                    }
                    return 0;
                 }
             };
    4.アプリケーションクラスはメッセージループと、メインフレームを持ちます。
            WPARAM Run()//メッセージループ
            {
                 MAIN_Frame_Create();
                 while( ::GetMessage( &msg, NULL, 0, 0)){...}
            }
            virtual BOOL MAIN_Frame_Create() = 0; // メインフレームの構築
             (その他省略)
    5.メインフレームは概ね次のようになります。
            class Q_WND_Frame : public WND_CALLBACK
            {
                M_WNDCLASSEX wnd_class; // ウインドウクラス
                // ウインドウクラスの登録
                BOOL Register(){ return wnd_class.Register( _T "MAIN_APP_FRAME")); }
            };
            // ウインドウの作成
           virtual BOOL Win_Create(){
                 // クラスを登録
                 Register();
                 // ウインドウの作成
                 :  
                 WND_Core::Create(  wnd_class.lpszClassName,
                            HWND_DESKTOP,
                            rc,
                            NULL,
                            WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                            NULL, 0, m_Instance,
                           this); // ★これが重要でCreateWindowExの最後の
                   // 引数lpParamになり、WM_NCCREATE時の
                                  // CREATESTRUCT::lpCreateParamsに渡されます。肝です(笑)
                  }
    6.HWNDのラッパーの実装です。
            class WND_Core {
                HWND hwnd; // ウインドウハンドル
                // HWNDのセット
                void HWND_Set( HWND ex_hwnd) { hwnd = ex_hwnd;}
                // ウインドウの作成
                HWND Create( ....) // 省略
            };
    てな感じですね。他にも技をいっぱい使わなければいけませんが
    動き始めると楽しいもんです(^^;)。
    でも、ダイアログは別立てで実装しなければならないので注意しましょう。

    参考になれば幸いです。

    • 回答としてマーク 七空白 2010年4月2日 8:31
    2010年4月2日 1:03
  • 仲澤さんありがとうございます♪

     

    かなり色々と参考になります。

     

    この設計は、概要としては、以下のような解釈で良いでしょうか?

     

    ・ウインドウハンドルとハンドルをセットする関数とウインドウの生成用関数
    をもたせたクラスを継承し、そこにvirtualコールバックを付加し、さらにそれを継承しつつ

    ・別途WNDCLASSEXを継承させて登録関数をもたせたクラスのインスタンス変数を持つ

    という内容のQ_WND_Frameは



    callbackがvirtualでWinCreateもvirtualなので、これをさらに継承して、それぞれのウインドウを作るということでしょうか?


    WM_NCCREATEは「WM_CREATEの前」にクライアント領域外が生成(重要なのはどのみち生成時はここに必ず入るということ)されるときに呼び出されるので、そこでWND_Core::Createの最後の引数でセットしたthisを拾い
    あらかじめ確保しておいたところへセット、このとき

    cbWndExtra  = (int)8;
    とSetWindowLongPtrを使っているのは、32bitと64bitの両対応のため

    ここまでいけばあとは
    WND_CALLBACK * obj = ( WND_CALLBACK *)::GetWindowLongPtr( hwnd, 0);

    return obj->callback( msge, wpar, lpar);

    によって、仮想関数が呼び出されるので、あとはそれぞれのウインドウ用にそれをオーバーライドすればおkということでしょうか?

    仮想関数を使う場合実行時オーバーヘッドを計測してみないとなんとも言えませんが(ただ、この場合「計測する」という行動自体が複雑な手順を要求されそうですがw)検討する価値は十分ありそうです。

    SetWindowLongPtrとcbWndExtraを使う方が意味的に考えればSetPropとGetPropより速そうな気がするのですが、こだわるならその辺も調べたいですね。

    2010年4月2日 8:31
  • その後

    実験しましたので報告します。

     

    仮想関数については、確かにオーバーヘッドは存在しますが(私の環境では少なくとも)ナノ秒単位で考えるようなものという感じがするため、実質的にはほとんど問題ないようですね。

    (ただし、キャッシュミスが発生しない状況での測定だった可能性が高いため、キャッシュミスが発生し、さらによほどのことがあると、無視できなくなる可能性はあるかもしれません)

     

    もしとことん速度を追求するなら、特定の「仮想関数を用いた方が遥かに簡単に記述できる」という場合以外は、今までどおりにするという作戦がさらに理想的かもしれません。

    今のところ

    あるクラスの中にウインドウを複数持たせて、さらにそれを継承するといった場合等に

    WM_KEYDOWNとかWM_KEYUPとか、マウス関係の色々とかを、部分的に仮想関数にするほうが簡単になるという状況が実際にあるため、そういうときはむしろ積極的に使っていこうと思います。

     

    これも結局ケースバイケースが一番でしょうねw

    たくさんの方法を知れば選択肢が増えます。

     

     

    そして、もう一つの点については

    やはりSetPropとGetPropを使うより、(おそらく文字のチェックが不要なので)SetWindowLongPtrとGetWindowLongPtrの方が高速でした。

     

    こちらは大よそ3~4倍程度のようです。

    というより、これは仮想関数のオーバーヘッドなどスズメの涙というくらいの大きな差でした!

    数百倍だったか数千倍だったかくらいで、最適化オプションをオンにしてもオフにしても差は歴然として存在しました。

     

    ※SetWindowLongのGWL_USERDATAは64bitだと出来ないはずなので、インスタンスの受け渡しが必要な場合はSet/GetWindowLongPtrを使いつつ、拡張8バイトを用意することにします。

    (若しくはどっかで見たのですが、SetWindowLongPtrのGWLP_USERDATAは64bit版だとちゃんとサイズ対応してくれるのでしょうか?32bit版では速度は同じくらいのようです)

     

    重要なウインドウにいくらかそれを適用しただけで、目に見えてアプリケーションの動きがより軽快になりました!

    いや~、助かりました!ありがとうございます♪

     

     

     

    あとは、ついでに

    WM_NCCREATE、WM_CREATE、WM_SIZE、WM_MOVE以外はどんなメッセージが発行されるのか気になったので

    メッセージを出力するように処理を作っておいて

    試しにウインドウスタイルを

    WS_OVERLAPPEDWINDOW|WS_VISIBLE

     

    にして生成した場合、出力結果をみると

    CreateWindow(かCreateWindowEx)を実行し、次の文に入るまでに、以下のものが以下の順番で呼び出されていました

     

    1. WM_GETMINMAXINFO
    2. WM_NCCREATE
    3. WM_NCCALCSIZE
    4. WM_CREATE
    5. WM_SHOWWINDOW
    6. WM_WINDOWPOSCHANGING
    7. WM_WINDOWPOSCHANGING
    8. WM_ACTIVATEAPP
    9. WM_NCACTIVATE
    10. WM_GETICON
    11. WM_GETICON
    12. WM_GETICON
    13. WM_ACTIVATE
    14. WM_IME_SETCONTEXT
    15. WM_IME_NOTIFY
    16. WM_SETFOCUS
    17. WM_NCPAINT
    18. WM_ERASEBKGND
    19. WM_WINDOWPOSCHANGED
    20. WM_NCCALCSIZE
    21. WM_NCPAINT
    22. WM_ERASEBKGND
    23. WM_SIZE
    24. WM_MOVE


    こんなに呼び出されてたのかと、驚きましたw

    しかし、一つ一つ丁寧に見ていくと、それほど心配することもなさそうですね。

    それにしてもWM_GETMINMAXINFOが一番最初で、WM_NCCREATEの前とは、そこも驚きました。

     

    まぁこれはリサイズすれば呼べるし、必要ないウインドウもあるし、必要あったとしても生成時にサイズを決めることができる以上、ちゃんと気をつければ問題ないはず。

    • 編集済み 七空白 2010年4月2日 17:19 理解が不正確だったかもしれないので
    2010年4月2日 16:47
  • 補足...

    仮想関数のコストを気にするなら、呼び出し形式コストを気にしたほうがトータルでは速くなると思います。

    ただし、例外があるとすれば、クラスメンバーに無意味に virtual が付きまくってる場合。。。これはNGですね。

    そうじゃなければ、それほど気にしなくてもいいと思いますよ。DLLの呼び出し(APIを含む)も、呼び出し手段的には仮想関数と同じですし(関数テーブルの位置がインスタンスがもってるのか、リンカーが用意したテーブルなのかの違いだけ)。

    この辺りは、アセンブラをはきだしてみればすぐにわかります(もちろん読めるなら。。。ですが、読めないとこのレベルの最適化は議論の意味すらないので)。

    ついでなので、API関係についても...

    SetProp/GetProp ですが、利用は確かに容易ですが、コストが非常に高いです。

    まず実行コスト...文字列については、ATOMを渡すことで多少なりとも軽減できますが、そもそもそのATOMの値がシステムグローバルな値なので、何も考えずに文字列のまま利用すると、一度作ったら最後システムリブートまでずーっと生き残ります(そういうものなので)。

    なので、これについては、よほどのことがない限り利用しないほうがいいです。SetProp/GetPropするなら、個人的には、std::map<std::string,obj*> や、std::map<HWND,obj*> のほうがよっぽど低コストで作成できる気がします(オブジェクトは、スレッドローカルストレージに格納したグローバル変数でよい)。

    最後にメッセージについても...

    ちゃんと調べられたから書いておきます。

    トリガーに使うのは、割り当ては、WM_NCREATEを、解除には WM_NCDESTROY を使うようにしてください。

    これらのメッセージの外側でもメッセージが流れ続けますが、それらはすべてデフォルトプロシージャに流しこむようにしてください。

    あと、メッセージの順番は絶対に期待してはいけません。実際、XPとVista、Win7では違う場合もありますし、VisualStyle の有無、MDIかどうか?ダイアログなのか、子ウィンドウがあるか?などいろいろな部分でメッセージの発生順が変わります。

    ウィンドウのリサイズ一つとっても、OSの設定によってメッセージの流れが変わります。

    チューニングする際にやってしまいがちなことの一つですが、意識しておかないとOSが変わるたびに書きなおしということになりかねないので。。。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月3日 7:33
    2010年4月3日 2:48
  • ありがとうございます。

     

    確かにvirtual は無意味につけたくないですね

    呼び出し形式コストというのは

    __stdcall  __cdecl  __fastcall __thiscall

    などの違いによるコストの差、ということでしょうか?

    VC++では通常では無指定だと__cdecl になって、WindowsAPIだと__stdcallにする、という感じだったと思うのですが、これは自分で自由に作る関数においては、別にそうこだわる必要がないので、特に__cdecl にする必要がない関数は__stdcallすることを検討していいとか

    引数を優先してレジスタに積んでも問題ないし、むしろインラインアセンブラを使わず(多少速度が犠牲になる可能性はあっても互換性を少し上げつつ)それをしてほしい場合は、 __fastcall を検討すべき、とか、そういうことでしょうか?

     

    あるいは、そうではなくて「呼び出し」の「仕方」つまりたとえば

     

    a->b();

    a->c();

    a->d();

     

    より

    a->e():

    void なんとか::e(){ b(); c(); d(); }

    の方が速い可能性が高い(カプセル化という点でも良い)とか

     

    そもそもごく小さな関数は当然最初からinline化を検討すべきとか、そういう感じのことでしょうか?

     

     

    >ATOMの値がシステムグローバルな値なので、何も考えずに文字列のまま利用すると、一度作ったら最後システムリブートまでずーっと生き残ります

     

    なるほど、ウインドウとの関連が保たれてる間にRemovePropを使えば解放できるけども、ひとたびそれをせずにアプリを終了でもしてしまえば、リブートするまで残る、ということでしょうか?

     


    inline void __stdcall f(LPCTSTR c){ 

        static TCHAR tc[80];
        _stprintf_s(tc,80,_T("%u" ), GlobalFindAtom(c));
        MessageBox(0,tc,_T("" ),0);
    }

    /* (この実験とは関係ないけど)inlineと__stdcallを書くと、inline化されない場合は__stdcallってちゃんとなる(?)のが保証されるのかな? */


    #define 確認 _T(" __aaaaabbbbbcccccdddddeeee__" )

     

    void   なんとか::かんとか() {

        //適当に作っておいたウインドウのハンドルwndを使用


        f(確認);
        SetProp(wnd, 確認, (HANDLE)this );
        f(確認);
        RemoveProp(wnd, 確認);
        f(確認);

    }

     

    結果:

    ・新品なら

    0→ATOMを発見→0

     

    ・不始末ゆえ既に再起動しない限り不死身のボディを手に入れてるダメダメなATOMの場合

    ATOMを発見→ATOMを発見→ATOMを発見

     

    どっちにしても速度面であれなので、この点確認出来る全てのウインドウを修正しておきます。

     

    あと気になったことがあるのですが

    cbWndExtraとSetWindowLongPtrを使う場合、拡張したメモリは単に

    DestroyWindowをするだけで同時にしっかり解放されるでしょうか?

     

    std::map VS cbWndExtra&SetWindowLongPtr はまた計測しておきます。

     

     

    アセンブラは見慣れてないので読むのに時間がかかり、たまにしか見ないため

    現在そんなに詳しくないのですが

     

    >リンカーが用意したテーブル

     

    ということは、静的リンク、ということでしょうか?

    ま、いいや、せっかくなので3パターン確認してみます。

    (ちょっと時間がかかるかもしれないのでまたあとで別途報告します)

     

     

    >チューニングする際にやってしまいがち

     

    大丈夫ですw

    そのつもりはありませんのでご安心くださいw

    私もこういうたかだか一つの結果から判断することは危険だと思うので

    (最終的にはWindowsオンリーにすると完全に決めたコード内以外では char c[2]; short *s=(short*)c; というコードも絶対書きたくないくらいですのでw )

     

    「そういうパターンもあった」「自分がメッセージをいじる部分以外は考えなくても大よそ問題なさそう」という判断にしか使わないので大丈夫です。

     

     

    >これらのメッセージの外側でもメッセージが流れ続けますが、それらはすべてデフォルトプロシージャに流しこむようにしてください。

     

    どちらの方法を使ったとしても、わずかな注意で可能ですね♪

    (ちなみにデフォルトプロシージャ直に指定している場合だとその間に送られてきたメッセージの処理はやはり速いですね。)

    2010年4月3日 7:33
  • 呼び出しコストは、トータルで考えないとだめなんですが...

    基本的には、呼び出し形式にどれを選ぶか?(cdeclなのかstdcallなのかなど)と、スタックにどれだけ積むか?(引数の数)の2つです。

    fastcall や thiscall をつけてもレジスタの数が限られているため、それを超える数の引数を与える場合必ず push/pop が入ります。

    ですが、これを構造体にしてそのアドレスを渡すようにすれば、push/pop がなくなる(fastcallなどの場合)とか、絶対的な数を減らすことができるため、高速化が期待できます。

    もちろん期待できるだけで、本当に早くなるのか?はアセンブラを見てみないとわからんですがw

    もちろん関数を呼び出す数も少ないほうが呼び出しコストがなくなる分速くなります。

    ただ、このレベルのチューニングは、今やコンパイラレベルで行ってることなので、ライブラリを作るなどであれば、不用意にvirtual を多用しない、何でもかんでも引数にしない...という程度で十分期待できます。

    むしろ、ソースレベルの可読性を上げることを考えたほうがよっぽど効率よく開発できると思います。

     

    APIの本来の使い道...

    SetWindowLong(Ptr)と、SetProp はそれぞれ前者がコントロール用(ボタンやエディットなどではなくユーザーコントロール)、後者がグローバルフックを行うルーチン用に用意されたものです。

    そのため、前者はウインドウクラスレベルで設定可能なものとなっており、後者は後から誰でも使え、かつシステムグローバルな識別子をもつものとなっています。

    なので、特定のアプリケーション内部で完結するものについては、システムレベルで余計なエリアを確保しないで、アプリケーション内でのみ利用可能なものにしておくほうが余計な管理コストが発生しない分全体効率(自分以外を含むOS全体の利用効率)が上がるといえます。

    もっとも、このあたりの設計思想やその前提条件あるいは知識は、Windows 3.x 以前の時代にまでさかのぼるため、C++ ではなく、C言語ベースで開発を前提としているなどもあるのですが...

     

    >cbWndExtraとSetWindowLongPtrを使う場合、拡張したメモリは単に

    拡張領域は、そこに何か(エリアとして一致していれば何を収めてもいい)を保持しておくエリアを用意するだけで中身が何か?はWindowsは一切考慮しません。

    それがポインタであっても、ポインタ自体は、数値型のデータの一形式とみなせますので、他の数値データと同じように扱います。

    ですので、拡張領域に収めた内容がmallocなどで確保したものなら、別途アプリケーションサイドで解放する必要があります。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月3日 14:43
    2010年4月3日 10:28
  • ありがとうございます。

     

    ライブラリを作るほどのことは今のところはしません。(作ったとしても特定の用途に特化させた、自分で使いまわすための、ごく小規模のものだと思います。)

     

    push, popの辺はどうもよく分からないことがあったのでよく知らなかったのですが(とくに吐き出されるアセンブリコードが、pushよりpopの方が明らかに少ない場合が多いのはなぜ?と思って)

     

    espレジスタがカレントスタック(?)指してて

    add    esp,  数値

    で、結果的に 数値が示すスタックに積んだバイト数

    分だけ積まれてない状態に戻したことになる、ということでしょうか?(予感としてはたぶんこれ、基本的なことっぽい…?w)

     

    そして確かに、仮にあるクラスや構造体を

    fastcallで参照渡し、あるいはポインタ渡しにする場合、わざわざそれぞれを別々に沢山の引数として渡す、とかするようにした場合よりも呼び出し側のコードのpushは少ないとみていい、というのは確認出来ましたが

    なぜか、fastcallにするとかわりに関数の定義側のコードにpush、popが1つずつ加わった場合があり「???」という状態です。

    レジスタの状態の制御やスタックを使うかどうかの判断は、今の私の知識やとれる時間を考えると、ほとんどの場合コンパイラに任せた方が良い可能性が高いですw

    結構良い感じに最適化してくれてるっぽい感じがありますし。

    もちろん、呼び出し規約は必要に応じていじれるという選択肢は念頭に置いておいて損はないでしょう。

     

    なお、仮想関数も解析しようと思ったのですが、asmファイル見ても仮想関数の場合どこをどういう風に見れば良いのかまだ未知のことが多いので、今のところはまだ深入りしすぎるのは厳しい感じがします。vftableとかがキーワードでしょうか?

    (ただ、もともと開発効率と、コードの再利用性という点をとるならば、アセンブリは書くのではなく見る方が主体なのが現実的だと思いますし)

     

     

    少なくとも最適化オプションをつけると、コンパイル時(?)にどれが呼ばれるかはっきりわかる場合は

    仮想関数であっても余計なコストは生じないコードになる可能性大という感じがするのですが

    ローカルオブジェクトとかを実験としてつくってみて、わざとそれをキャストしたり無駄な処理をさせて…というコードを書いたら

    最終的にオブジェクトが作成されなくなって、しかもインライン展開みたいになってて、かつ前後2度手間をまとめてる

    なんて場合もありましたし

    最適化オプションなしだとしても相当精通してないと厳しそうですね

     

    でも実際にはそれによってバグが発生しない限り最適化オプション極力付けたいので これらの細かい問題全てについて今のところはやはり

     

    >不用意にvirtual を多用しない、何でもかんでも引数にしない...

     

    上で、必要が応じた場合も大抵は時間計測くらいのことで判断しつつ、もしアセンブリコードをみる必要が生じたらそれ次第もうちょっとじっくりと調べ込んでいきたいと思います。

    まぁ普通に意味考えて作ってる限り、水面下での軽いアセンブリ命令数個程度の差なら、そんなに心配はないかなとw

     

    std::map と cbWndExtra及びSetWindowLongPtr

    ですが、最適化オプションなしだと後者の方が少しだけ早くて、最適化オプション付けたら前者の方が1.7倍くらいの速度が出ました。

    なんでそうなるのかは気が遠くなるのでまだ調べれていませんが、これはまた検討する価値ありですw

     

    >SetWindowLong(Ptr)と、SetProp

    なるほど、SetPropというのはもともともっと大仰な場面で使うような感じなのですね。

    システムグローバルということさえ分かれば、自動的にその2択ではSetWindowLong(Ptr)サイドが、この場合意味的にも適合している、ということが分かりますね。

     

    ・拡張領域について

    はい、そこはアンマネージなクラスのデストラクタを呼んでも、そのクラスがメンバとして何か動的に確保してたらそれを解放してやらないとリークしてしまう、のと同じことですね?

    そこはOKですが

    拡張領域そのものは、ちゃんと解放されるのでしょうか?

    2010年4月3日 14:42
  • アセンブラ周りについては、わからん!ということであれば、深入りしなくてもいいと思いますし、しない方がいいと思います。

    現在のWindowsプログラミングなどいわゆるデスクトップ系PCなどでの開発であれば、よっぽどひどいコードを書いていない限り、それが原因で体感できる速度差は発生しないと思います。

    むしろ、速度的な最適化は、コンパイラに任せ、可能な限りシンプルな設計と実装を心掛ける。式、文の絶対数を減らすという方向でコーディングしていくスタイルをとればいいと思います。

    そのうえで、むやみに virtual はつけない、本当の意味で一過性のあるもの以外は引数にしないなどを心掛ければそれだけで良質なコードを目指せると思います。

    >まぁ普通に意味考えて作ってる限り、水面下での軽いアセンブリ命令数個程度の差なら、そんなに心配はないかなとw

    Pentimum4シリーズくらいから後のCPUであれば、数個どころか10~20は、パイプラインに飲み込まれてしまう可能性が高いですし、いい感じで投機実行できれば数十どころか、数百ステップ程度は実行速度としての差が出なくなります。いまどきのCPUの性能は、アセンブラコードレベルでは測りきれないので。。。

     

    ・std::map の速度

    最適化の有無で大きく差が出る理由がわかるようになったら、もう一度今までの一連の流れを読み直してみるといいでしょう。それこそ。。。それが最適化というものです。。。というくらいな世界です。

     

    ・拡張領域そのものは、ちゃんと解放されるのでしょうか?

    はい。ことらは確実に解放されています。ウィンドウを作成したときに確保するメモリ(プロシージャやウィンドウの状態保持用のエリア)サイズにプラスして、メモリアロケートしていますので、器そのものは奇麗に解放されています。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月3日 18:32
    2010年4月3日 16:51
  • ありがとうございます♪

    色々と安心しました。

     

    とりあえず、最適化はC、C++のコード面では(意味的に考えて)もうほとんど無駄がない、ってとこまで行ってから先の話ですね。

    (CPU毎でも多少相性の差によって最速パターンは変わってくるでしょうから、こだわりすぎても逆に非現実的になってしまうと思いますが、相当な知識があればある程度色々出来るでしょうね。ただ、それが次のバージョンとか、別のコンパイラでも通用するテクニックかどうかというとこまで考えると、インラインアセンブラしか確実な方法はないでしょうが、やはり互換性の点で…w(略))

     

    しかし、冷静に考えると、現在考えている点については、CとかC++でまだ全然改良できそうな気になってきました。

     

    例えば、もし私が投稿時に使った、単純に自クラスにstaticプロシージャを設定する方法の一部を用いる場合は

    そしてしかもウインドウがもし一つ なら、thisポインタのアドレスを拾うのに

    これだけのことをするだけで

     

    #define null NULL


    class ABC{

         HWND wnd;

          static ABC *abc;  

          static LRESULT CALLBACK Proc(HWND, UINT, WPARAM, LPARAM);
    public :
          static BOOL InitABC();
          ABC(void);
          ~ABC(void);

    };

     

    念のためならソース側でNULLで初期化

    ABC* ABC::abc(null );

     

    //あとはコンストラクタかコンストラクタ周辺の初期化関数等で↓

    abc=this;

     

    これで完成します。

     

    GetとかSetとかstd::mapの必要性もありません。

    最適化オプションあり同士でやると

    このポインタからアドレスを拾うのに必要な時間は

    「std::map」の1500分の1 程度です。

     

    しかも、拾って別のポインタに入れる必要自体がない ので、実質0です。

     

    んじゃ数が増えてももしウインドウの個数が固定ならstaticポインタ配列で良…

     

    と、一瞬思いましたが

    プロシージャの中で変化させるためにはやっぱり引数であるHWND、つまり

    ウインドウに関連付けるのが良いか

    と思いましたので

     

    試しに固定長の連想配列っぽく動作できるものを適当に作ってみました。

     

    template <class T,WORD N=10>
    class TList{


        T* p[N];
        HWND w[N];
        WORD Len;


    public :
        TList() : Len(0) {}
        ~TList(){}


        void Add( HWND a, T* b ){ w[Len]=a; p[Len++]=b; }


        T* operator []( HWND a ){
            for(WORD i=Len;i--;) if (w[i]==a) return p[i];
            return null ;
        }
        T* operator []( WORD a ){ return a<Len ? p[a] : null ; }


    };

     

    ※ウインドウの破棄はアプリケーション終了時に一括で行うものと考え、途中消去用関数は作っていませんが、必要ならちょこっと書き足せば出来るでしょう。

     

    これで

     

    class ABC{

         HWND wnd;

          static TList< ABC> tlist;   //個数指定なしならデフォルトで10個対応

          static LRESULT CALLBACK Proc(HWND, UINT, WPARAM, LPARAM);
    public :
          static BOOL InitABC();
          ABC(void );
          ~ABC(void );

    };

     

    ソース側で

    TList<ABC> ABC::tlist;

     

    あとはつくっておいたウインドウを関連付けます

    tlist.Add(wnd,this );

     

    出来上がりです。

     

    あとはサクサクっと取り出します♪


    LRESULT CALLBACK ABC::Proc(HWND hw, UINT msg, WPARAM wp, LPARAM lp){
             ABC* abc=tlist[hw];


     

    このように「特定の状況に限定」させれば、標準のクラスを上回るパフォーマンスが得られる可能性がいくらでも転がっていそうです。

    このTListでも、std::mapの10倍以上の速度でポインタを取り出すことができるようです。

     

    面白いものですw

     

    また、この作戦(自クラスにstaticプロシージャを持たせる方法)

    をさらに特化させると、特定のメッセージの時だけ変換させるという手も考えられます。

     

    (↓処理内容は適当です)

     

    #define WM_DEFINE (WM_APP + 10)


    LRESULT CALLBACK ABC::Proc(HWND hw, UINT msg, WPARAM wp, LPARAM lp){
        static TCHAR c[20];
        static int i=0;

        switch (msg){
            case WM_DEFINE:    
               if (wp) {
                   ABC* p=tlist[hw];        //ここでしか使わないためここだけで取り出す、という方法
                    _stprintf_s(c,20,_T("%p" ),p);
                    SetWindowText(hw,c);
                }
                else SetTimer(hw,1,100,0);
                break ;
           case WM_TIMER: ++i; break ;
           case WM_KEYUP:
                if (wp=='T' ) {
                    _stprintf_s(c,20,_T("%d" ),i);
                    SetWindowText(hw,c);
                }
                break ;
          case WM_DESTROY: KillTimer(hw,1); break ;

          default : return ::DefWindowProc(hw , msg , wp , lp);

          }
       return 0;
    }

     

    色々と面白くなってきました♪

    default なくして return ::DefWindowProc(hw , msg , wp , lp); 外に出して、break ; をreturn 0; にした方が確実かな?…最適化オプションオンだと最適化されそうだけどw)

    2010年4月3日 18:24
  • 数が十分少ないのなら、汎用のコレクションクラスを使うより特化したクラスを使う方が圧倒的に有利です。汎用性の高いもの vs それ専用ではやはり専用のほうが圧倒的に有利です。

    ま、この辺りは凝り始めるときりがない世界でもあるので、逝っちゃう前にほかのところを詰めておくことをお勧めしますw

     

    おまけ。。。だなw

    適当に書いたコードのやつに突っ込み入れるのはどうよ?とも思うのですが。。。

    WM_TIMER、WM_DESTROY の二つについては、DefWindowProc も呼び出すようにした方がいいです。

    また、WM_KEYUP についても、if文外れた場合はやはりデフォルトを呼び出すことをお勧め(この場合はifの中を通っても呼び出してもいいけどw)します。

    これもまたお作法の一つ。自分自身で作ったウィンドウであっても、自分のプロシージャはシステムデフォルト処理の横取りをしています。

    横取りしないといけないもの(画面再描画(WM_PAINT)など)ももちろんありますが、基本は、自分で処理してもデフォルトは呼び出すという形のほうがいいです。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月4日 5:37
    2010年4月4日 0:55
  • ありがとうございます♪

     

    >横取りしないといけないもの(画面再描画(WM_PAINT)など)ももちろんありますが、基本は、自分で処理してもデフォルトは呼び出すという形のほうがいいです。

     

    これ結構重要だと思うのですが…w

    公式な一覧表とかってありますか?公式じゃなくても良いけど、どの時は呼び出さないといけないのか、どの時はreturn 0でないといけないのかは、ネット上のいろんなサンプル見てもまちまちな感じがあって、ちゃんと把握できていません。

    2010年4月4日 5:37
  • >公式な一覧表とかってありますか?

    たぶんないと思います。

    基本的なところは、

    • デフォルトに任せると都合が悪いところは自分で行う(Ex. WM_PAINT)
    • 知らないものは触らない(Ex.WM_TIMERで未知のIDで通知が来たなど)

    というところですかね。

    むずかしいことを考えるのではなく、つねに自分以外にも同時にプログラムが動いており、お互いがお互いのことを思いやって動作するように心がけるということを考えていればいいと思います。

    実際Windowsアプリケーションの設計思想の根幹は互いのプログラムが性善説に基づいて動作しており、譲り合いの精神がいきわたっていること。。。ですからね。Ver.1.0のころから。。。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月4日 13:19
    2010年4月4日 6:02
  • ありがとうございます。

     

    仮にMDIならば、DefMDIChildProcに回さず処理を止めるとまずい可能性がある、というのは思いついたのですが

     

    >WM_TIMERで未知のIDで通知が来たなど

    自分でprivateに作っている以上、こういった未知のメッセージは起こりうるのだろうか…?

    とじっくり考えてみたのですが

     

    例えばEnumWindows系の関数を使えば、メイン制御用のHWNDがprivateであってもあり得ないとは言い切れないために

    http://msdn.microsoft.com/ja-jp/library/cc410851.aspx

     

    これは

    自分のアプリケーション内で気をつければ通常は大丈夫だけど、他のアプリケーションが何らかの理由でアクセスしてくる可能性 まで考えれば、はっきりデフォルトに任せられないという場所以外、逆に処理後にデフォルトに渡した方が、環境依存のバグが発生する可能性を減らせる、ということでしょうか?

    2010年4月4日 13:19
  • HWND自体はシステムグローバルなオブジェクトです。残念ながらC++のようなアクセス制御を持っていません。プログラム内部で抱えているHWNDはあくまでも参照情報で、その実体は、Windowsそのものが管理しているものです。

    また、EnumWIndowsやGetWindowなど、いくつもの手段でウィンドウハンドルを探し出すことができます。

    この辺りは、Spy++というツールがあるので、一度立ち上げて自分自身のアプリケーションのウィンドウをスパイしてみるといいです。

     

    あと、寄生していない場合でも、マウスの右クリックのメッセージをデフォルトに流しておくと、後で WM_CONTEXTMENU が送られてくるなどという、標準的な拡張なども行われています。

    一番最近の例でいえば、Win7のマルチタッチのメッセージもデフォルトに流すといろいろなもの(おもに拡大縮小や、スクロールですが)に変換して改めてメッセージを送ってくれたりします。

    マルチタッチなどは新しいメッセージなのでブロックされることはないでしょうけど、マウスメッセージなどは、意図せずブロックしてしまうことも多々ありますので、やはり意識的に注意しておく必要があると言えるでしょう。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月4日 17:29
    2010年4月4日 14:01
  • ありがとうございます!

    Spy++というのがあったのですねw

     

    やってみたら、メッセージがだ~~~っと表示されるので、その辺の細かい流れもしっかりやってけば確認できそうです。

    (ただ、現状非常にウインドウが多くて目的のやつを探しづらいので、新規プロジェクトを作って実験してみようと思います。)

     

    ちなみに、10万個表示できるようで、越えたらどうなるのかな?と思ったら、実際10万個以上飛ばしてみて、単にそれ以上の桁は表示されない…?ようですねw(どうでもいい)

     

    ところで、非常に気になったことがあるのですが

    非POD(非C互換型)のポインタはPODという扱いになるでしょうか?

     

    どういうことかというと、C++でアラインメントを求めようとした場合

    ポインタのアラインメントがかならず


    template < class T>
    class alignmentof__{
        struct S {  char c; T t; };
    public :
        enum {  value = offsetof(S, t) };
    };

    #define alignof (T) (alignmentof__<T>::value)

     

    としといた場合のalignof (クラス名*)で求まることが保証されるのかどうかによって

    (求めずとも32bitならオブジェクトのポインタのアラインメントは大抵は4だと思いますが)

    上記TListの可変長または、大規模バージョンを試作(上記方法だと線形探索なのでたぶん速度的には30個くらいまでが、自作TListを使う意義がはっきりある目安)してみようとした場合の高速探索用のアルゴリズムが変わる可能性がありますので…

     

    あと、sizeof がアラインメントの影響を受けるという性質を考えれば、上記TListでは32bit版でも64bit版でもおそらく

    WORD ではなくintunsigned int の方が合理的でしたねw

    (どっちにしても私の環境ではNがデフォルトの10だと4*10*2+4(アラインメントが4のため、WORDが2でも4にアラインされる)で84バイトになりました。そういうことになるなら単に速い方が良いはずです)

     

    実際そのように変えたら、また状況次第で1.25倍程度高速化させることができました。

    2010年4月4日 17:29
  • 読みきっていただいてありがとうございます。

    もちろんウインドウコールバックを単一にするのと
    各ウインドウオブジェクトのインスタンスのコールバックが
    virtualになるのはトレードオフです。ただ、

    ・現在のCPUはキャッシュが数MByteあるため、v-tableごとキャッシュされると予測できる。
    ・本設計上の全てのウインドウは、このコールバックを通過する。

    ため、特に問題ならないと予想できます。
    それよりも、

    ・CWnd* CWnd::GetParent()など、MFCでは「一時オブジェクト」しか戻せない仕様の
     同等関数で、ウインドウオブジェクトの実体を戻せる(★キャストできる(笑))。
    ・クライアント矩形等の取得を、より基本的クラスに実装済みにできる。
     事実上WM_SIZEでクラスメンバの矩形に取得すれば十分。
    ・WM_COMMAND等、引数の上下WORDを分離してから下位に通達できる。
    ・WM_MOUSEWHEEL等、フォーカスウインドウに通達されるメッセージを
     マウスカーソル直下のウインドウに事前にパッチできる。
    ・マウス系メッセージ、キーボード系メッセージなど、一つの物理デバイスに
     複数のメッセージが用意されているのを一つのストラテジ
     (例えばOnMouseMsg()、OnKeybordMsg())にまとめてパッチできる。

    等の利便性を優先させた結果です。また、

    ・MFCと共存できる

    のも良いところです。

     

    • 回答としてマーク 七空白 2010年4月5日 15:02
    2010年4月5日 1:01
  • ・Spy++

    ツールバーにある、双眼鏡マークで、ウィンドウ検索ができます(出てきたダイアログからドラッグして照準を合わせる)。

    それで、自分のアプリのどこかのウィンドウにフォーカスを当てればツリーの該当ウィンドウ上に移動してくれますよ。

     

     

    ・ポインタの扱いとアライメント問題とか

    こっちは、Windows で動いてるかどうかではなく、利用するコンパイラやCPUの特性の問題ですね。どう扱われるか(扱うほうがいいか)は、仕様とかきちんと当たらないとだめだと思います。

    基本的に32bitCPUであれば、通常はアライメント境界は4か8がいいです。が、SSEを使うなら16にしておいてもいいでしょう。

    64bitなら8か16か。。。でしょう。ですが、これらはあくまでもCPUのメモリアクセス特性に合わせた形にしてやるのがよいのであって、言語仕様的にどうか?とかとはまた少し違う次元の話になります。

    あとは。。。パフォーマンスと同時にレイテンシも考慮しておくといいのかなぁ?

     

    ・MFCのCWnd::GetParent()とか。

    えっと。。。もともとそのウィンドウが現在のMFCのスレッド管轄下にない(==CWnd派生クラスにアタッチされていない)場合は、テンポラリオブジェクトが

    そうではない場合(そのスレッドで何らかのCWnd派生クラスにアタッチされている)場合は、そのウィンドウオブジェクトのポインタが返ってきます。

    なので「一時オブジェクト」しか返さないわけではありません。誤解してる人が多いので、ここは突っ込んでおかねばw

    正直な話、ほぼすべての状況において、CWnd::GetParent は必ず CWnd 派生クラスの何かを返すはずです。

    そうじゃない条件は...

    1. 別のスレッドにある子ウィンドウ(作ると効率が劇的に悪くなるため絶対にやってはいけないマルチスレッド化の一例)
    2. OLEインプロセスサーバー(含:ActiveXControl)における、アプリケーション的なメインウィンドウ(実際は子ウィンドウ)
    3. 2の時のツールバー

    おそらくは、この程度です。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月5日 15:02
    2010年4月5日 2:38
  • >仲澤さん

    ありがとうございます♪

     

    確かに、いつも速度と利便性はトレードオフですよね

     

    >現在のCPUはキャッシュが数MByteある

    そんなにあるんですかw

    となると、ほとんどの場合杞憂かもしれませんねw

     

    ////////////////////////////////////


    ・クライアント矩形等の取得を、より基本的クラスに実装済みにできる。
    事実上WM_SIZEでクラスメンバの矩形に取得すれば十分。
    ・WM_COMMAND等、引数の上下WORDを分離してから下位に通達できる。
    ・WM_MOUSEWHEEL等、フォーカスウインドウに通達されるメッセージを
    マウスカーソル直下のウインドウに事前にパッチできる。
    ・マウス系メッセージ、キーボード系メッセージなど、一つの物理デバイスに
    複数のメッセージが用意されているのを一つのストラテジ
    (例えばOnMouseMsg()、OnKeybordMsg())にまとめてパッチできる

     

    ////////////////////////////////////

     

    その辺についても、継承を繰り返して省略、ということでしょうか?

    利便性という点ではどういうふうに実現するのがいちばんいいかというのも気になるので、そのあたりのちょっとしたコード辺を見せていただくことはできますでしょうか?w

     

     

    ただ、本当に仮想関数を使ってひとたび構造を作ると、微量とはいえ、その影響を受ける全体にトレードオフとなりうるのか・・・・・・?

     

    という疑問が生じたので、可読性とか意味があるのかとかはまだ分かりませんが

     

    技術として、一端仮想関数を使ったクラスの性質を継承しながら、早くしたいところは(内部的に)極力シンプルな処理に、という手法も確立できなくはないのでは?

    という予感がしたので

    かなり色々実験していましたが

     

    今まで出たアイデアを色々とミックスしてみて

    一端あるプロシージャを必ず経由させておいてから、部分的に変更 とか

    部分的に別のプロシージャを呼び出すとか

    そういうことだって、やろうと思えばいくらでもできそうなことが確認できました。

     

    ↓ここまで簡単な動作の例だと継承使ってるのに逆に可読性が下がって、自ら進んでやる意味はないでしょうが(w)

    「こういうことも可能性として出来る」という実験なのでw

    (例によって内容は適当ですが、そういう意味じゃなくて突っ込みどころがあったら教えてください)

     

    ///////////////ヘッダ側////////////////



    class SuperClass{ //基底クラス
       
        static LRESULT CALLBACK Proc(HWND, UINT, WPARAM, LPARAM);
    protected :   
        HWND wnd;
        SuperClass(void );
        virtual ~SuperClass(void );

        virtural void Init(){}
        template <WNDPROC p> void Create( HWND hw , LPCTSTR cname, LPCTSTR c=_T("" ) );

    };


    template <WNDPROC p>
    void SuperClass::Create(HWND hw, LPCTSTR cname, LPCTSTR c){


        wnd=CreateWindowEx(0,cname,c,WS_OVERLAPPEDWINDOW|WS_VISIBLE, 0,0,200,150,hw,null ,HINST::me,0);                       // ひとまずDefWindowProcがせっていされたとしても


        SetWindowLong(wnd,GWL_WNDPROC,(LONG)Proc); //プロシージャ変換可
        SendMessage(wnd,WM_DEFINE,(WPARAM)this ,(LPARAM)p);

           //その一時変換を利用してさらにプロシージャ変換も可
    }

     

     

    class SuccessEx : protected SuperClass { //サブクラス1

        static SuccessEx* suc;                     // 単一ウインドウ時のお手軽this格納用

    protected :
        virtual void Init() override { suc = this ; } //その場合の取り出し準備
        static LRESULT CALLBACK Proc(HWND, UINT, WPARAM, LPARAM);
        SuccessEx(void );    //サブクラスがなければこれをpublicにして直にやることも普通に可能

    public :   
        static SuccessEx* New( HWND hw=null ); //この場合はこれでインスタンスを生成することも
        virtual ~SuccessEx(void ){}

        virtual LPCTSTR ClassName(){ return _T("SuccessEx" ); } //ただの確認用関数
        LRESULT CALLBACK InstProc(UINT, WPARAM, LPARAM); //インスタンスプロシージャ

    };



    class Sub_2 : private SuccessEx { //サブクラスのサブクラス

        LPCTSTR ClassName() override { return _T("Sub_2" ); } //ただの確認用関数

        void Init() override { SetWindowLongPtr(wnd,0,(LONG_PTR)this ); }

    //SuccesssExとは別の方法で変換


        static LRESULT CALLBACK Proc(HWND, UINT, WPARAM, LPARAM);

    //基底と別の方法でthisを拾う場合は作っておかないと不便な可能性大


    public :
        Sub_2( HWND hw=null );
        ~Sub_2(void ){}

    };

     

     

    ///////////////ソース側////////////////

     


    SuperClass::SuperClass(void ) : wnd(null ) {}

    SuperClass::~SuperClass(void ){ DestroyWindow(wnd); }

    LRESULT CALLBACK SuperClass::Proc(HWND hw, UINT msg, WPARAM wp, LPARAM lp){
        if (msg==WM_DEFINE)    {
            SetWindowLong(hw,GWL_WNDPROC,(LONG)lp);   //プロシージャをそれぞれ用へ切り替え

           ((SuperClass*)wp)->Init();                     //thisポインタをそれぞれの方法で拾えるようにする

            return 0;

        }
        return DefWindowProc(hw,msg,wp,lp);
    }


    SuccessEx* SuccessEx::suc(null );

    LRESULT CALLBACK SuccessEx::Proc(HWND hw, UINT msg, WPARAM wp, LPARAM lp){
        static TCHAR c[20];
        static RECT rc;
        switch (msg){
             case WM_SIZE:
                GetClientRect(hw,&rc);
                _stprintf_s(c,20,_T("%d %d" ),rc.right,rc.bottom);
                SetWindowText(hw,c);
                break ;
            case WM_LBUTTONUP:
                _stprintf_s(c,20,_T("%s" ),suc->ClassName());
                MessageBox(null ,c,_T("左クリック" ) ,null );
                break ;
            case WM_RBUTTONUP:
                SetWindowText(hw,_T(" SuccessEx::Proc WM_RBUTTONUP" ));
                break ;
            defaultreturn suc->InstProc(msg,wp,lp);
        }
        return DefWindowProc(hw,msg,wp,lp);
    }

    LRESULT CALLBACK SuccessEx::InstProc(UINT msg, WPARAM wp, LPARAM lp){
        switch (msg){
            case WM_RBUTTONUP: SetWindowText(wnd,_T(" マウス右UP" ));   break ;
        }
        return DefWindowProc(wnd,msg,wp,lp);
    }

    SuccessEx::SuccessEx(void ){}

     

    /* WndClassLibrary::WhiteWindow8()はあらかじめ登録しておいたウインドウクラスの名前です. 初期設定はDefWindowProc */

    SuccessEx* SuccessEx::New( HWND hw ){
        SuccessEx* a=new SuccessEx;
        a->Create<Proc>(hw,WndClassLibrary::WhiteWindow8(),_T(" SuccessExクラス" ));
        return a;
    }

    Sub_2::Sub_2(HWND hw){

    Create<Proc>(hw,WndClassLibrary::WhiteWindow8(),_T(" Sub_2クラス" ));

    //thisの拾い方が同じなら Proc ではなく SuccessEx::Procを指定とかも可

    }

    LRESULT CALLBACK Sub_2::Proc(HWND hw, UINT msg, WPARAM wp, LPARAM lp){
        switch (msg){
            case WM_SIZE: return SuccessEx::Proc(hw,msg,wp,lp);
            case WM_RBUTTONUP:
               return ((SuccessEx*)GetWindowLongPtr(hw,0))->InstProc(msg,wp,lp);
            case WM_LBUTTONUP:   

                TCHAR c[20];
                _stprintf_s(c,20,_T("%s" ), ((SuccessEx*)GetWindowLongPtr(hw,0))->ClassName() );
                MessageBox(null ,c,_T("左クリック" ) ,null );
                break ;
        }
        return DefWindowProc(hw,msg,wp,lp);
    }

     

     

     

     

    一時的にあっちこっち行くことになったとしても、プロシージャの切り替えを行うという方法を使えれば、それ以降は直に出来るので、もしかしたら場合によっては両方の設計の良いとこ取りができるかもしれません。

     

    (ただ、最大の問題があるのですがw

     

    確か関数ポインタのサイズって一定じゃなかったですよね?

     

    WPARAM や LPARAM って、

    UINT_PTR
    LONG_PTR

    のtypedefになってますが

    これらはWNDPROCやthisポインタと互換性あるのでしょうか?(特にLLP64 )

    (ない場合は多少面倒な手順が必要になるかも)

    2010年4月5日 15:01
  • >とっちゃんさん

    ありがとうございます♪

     

    Spy++について、確認できましたw

    なんか最初「プロセス」って書いてあるウインドウの方いじってたので分かりませんでしたがw

    これは便利ですね。

     

    レイテンシというと、メモリの場所ごとにメモリに出力する時間が変わる可能性があるとか、ということでしょうか?

    む~

    今のところはさっぱりなのですが

    そういう事まで考える場合だと、どんなふうにどういう要素を熟慮すれば良いのでしょう?

     

    MFCのCWnd::GetParent()

    って、返り値がCWnd*ってことは……

    やっぱりウインドウのラッパークラスのオブジェクトのポインタかな?

     

    定義はどんなのか分かんないですが

    WindowsAPIのGetParentとはどういう風に違うのでしょうか?

     

    WindowsAPIでは一時オブジェクトとかそういう概念というか、気をつける必要はないですよね?

    2010年4月5日 15:15
  • >レイテンシというと...

    ご、ごめんなさい。そこはレスポンスです...なんでレイテンシなんだ?>おれ

    一応。。。チューニングのかなめとしては、パフォーマンス(絶対的な速度)、レスポンス(反応の遅延)、レイテンシ(処理の遅延)の3つをどうするか?というバランスの上に成り立ちます。メモリだけじゃないということは覚えておいてください(英語がカタカナのままの場合ってあやふやな意味でとらえられてることが多いんですよねw)。

    あとは、必要になってから考えればいいと思います。

     

    ・CWnd::GetParent()...

    CWnd というのは、MFCのウィンドウクラスの基本クラスです(HWNDをもつクラス)。その派生クラスとして、ボタンとか、エディットコントロールとかいろんなものがあります。

    ちなみに、ATLは、CWindowです。実装スタイルは全く違うので注意してください。どちらもMSDNライブラリ(オンラインでよい)にリファレンスがありますので、メソッドの一覧(すべてではない)はある程度見れます。

    WindowsAPIのレベルでは一時オブジェクトは、プロシージャのパラメータ(おもにLPARAM)ですかね。様々なコントロールの通知系メッセージのパラメータの大半はその場限りのオブジェクトです。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月6日 8:44
    2010年4月5日 16:09
  • ありがとうございます。

     

    私はもともと音楽関係の色々なので、レイテンシと聞くと、ASIOは高性能だとかそういうことが先に思い浮かんでしまいますw

    カタカナの言葉は定義自体がそもそも曖昧な場合もありますので、問題ありませんw

     

    その定義だと、パフォーマンスを高めることは計測やアセンブリのチェックによって比較的普通にできるとしても

    レスポンス(反応の遅延)やレイテンシ(処理の遅延)とかとはどういうところで分類されるものなのでしょうか?

     

     

    >CWnd::GetParent()等について

     

    はい、こちらについては先に一応調べてみたのですが

    http://msdn.microsoft.com/ja-jp/library/0x2wyab0%28VS.80%29.aspx

     

    >>『Platform SDK』の「GetParent 」の戻り値のセクションを参照してください。

     

    をクリックで、ページが見つかりませんというコンボをくらっていたもので…w

     

     

    ATLのCWindowとは結構違うスタイルなのですか(そっちも細かいところは分からないですが)

    使える状態になったら調べてみようと思います。

     

     

    >WindowsAPIのレベルでは一時オブジェクトは、プロシージャのパラメータ(おもにLPARAM)ですかね。様々なコントロールの通知系メッセージのパラメータの大半はその場限りのオブジェクトです。

     

    むむ?一時オブジェクトって、参照使わない限り戻り値じゃないと役に立たない概念じゃないですか?

    プロシージャのパラメータって言い換えると関数の引数ってことになりますよね?

     

    そうなると、参照渡し以外の引数はインライン展開されない限り一時オブジェクトってことになってしまうのでは?

    (ポインタ渡しも「アドレス」をコピーするので、格納する空間自体のアドレスは違う場所に確保されるので)

     

    プロシージャのHWNDが「非const」参照渡しとかだったら良かったかもしれないんですけどねww

    (一時オブジェクトが作成されない非const参照渡しになってくれれば、HWNDがメンバならば、HWNDそのもののアドレスを調べることでthisの位置を逆算できる可能性があるため、面倒な手順が一掃される可能性が…w)

    ・・・まぁWindowsAPIはC用に作られたと思うので仕方がないでしょうがw

     

     

    とりあえずそういうことはできないので

    上記コードのアイデアをさらにに詰めて

     

    「thisの拾い方が同じ」クラスをいくつか拾い方ごとに別々に基底に作っておいて、それを継承させる、という作戦も思いつきました。

    ただ、キャッシュミスまで考えるなら、大よその話でいうと実行ファイルのサイズは不用意に膨れ上がらない方が良いはずです。

    そこで、プロシージャはインスタンス疑似プロシージャでも、staticプロシージャでも(クラスメンバにするなら)、積極的にpublicにした方が良いかもしれませんね。

     

    なお、ウインドウが一つであっても複数であっても、結果それがstaticメンバ変数を用いて拾うものなら

    基底クラスにそれをまとめる際、そのstatic変数が型ごとに作られるべきだと思うのですが、templateを使えばそこは何とかなるとして、継承方向を逆にしてはどうか?など色々検討してみて、やはりそこは汎用性、無駄なコードを省くという点で、逆にしない方が良いか、とか考えて

    結果、たとえばウインドウが一つで、staticプロシージャを継承先に独自に持たせるとした場合

    以下のクラスを継承してはどうか?

    と思うのですが、2つほど問題があります。

     

    template <class T>
    class Single{

    protected :
        static Single* me;
        HWND wnd;

        Single(void );
        virtual ~Single(void );
        virtual void Create( HWND hw, LPCTSTR cname, WNDPROC Proc, LPCTSTR c=_T("" ) );

    };

     

    /* 特殊な用途がない限り初期化処理は一回しか呼ばれないという前提のため、念のためインライン化は抑えめにすべく、外で定義。 */

     

    template <class T> Single<T>* Single<T>::me(null );
    template <class T> Single<T>::Single(void ) : wnd(null ) {}
    template <class T> Single<T>::~Single(void ){ DestroyWindow(wnd); me=null ; }
       
    template <class T>
    void Single<T>::Create( HWND hw, LPCTSTR cname, WNDPROC Proc, LPCTSTR c ){
        if (me) return ;
        me = this ;
        wnd=CreateWindowEx(0, cname, c, WS_OVERLAPPEDWINDOW|WS_VISIBLE, 0,0,200,150,hw,null ,HINST::me,0);

         /*  なお、cnameに関連付けられたウインドウクラスに設定したプロシージャを別途特別に用意すれば、WM_NCCREATEでプロシージャを切り替えることも可能なはずです  */

        SetWindowLongPtr(wnd,GWLP_WNDPROC,(LONG_PTR)Proc);
    }

     

     

    継承例としては例えばこういうことになります。

     

    class Success : private Single<Success*> {

    public :

        //プロシージャを積極的に公開
        static LRESULT CALLBACK Proc(HWND, UINT, WPARAM, LPARAM);   
        Success(HWND hw=null );


    };

     

    (最低限コンストラクタとプロシージャを書くだけでOK)

    定義はコンパイル時解決の必要がないように、またインライン化を抑制するため(特に短く見えて裏で色々やってるであろうコンストラクタ)

    ソース側に・・・

     

    //関係ないですがWndClassLibrary::なんちゃら は関数から変数に変えましたw

     

    Success::Success(HWND hw){
        Create(  hw, WndClassLibrary::WhiteWindow,  Proc,  _T("Success!" )  );
    }

       
    LRESULT CALLBACK Success::Proc(HWND hw, UINT msg, WPARAM wp, LPARAM lp){
        switch (msg){
            case WM_MOUSEMOVE:{
                TCHAR c[30];
                _stprintf_s(c,30,_T("%p MOUSEMOVE " ),me);
                SetWindowText(hw,c);
                }

                break ;
            case WM_EXITSIZEMOVE:
                SetWindowText(hw,_T("EXITSIZEMOVE" ));
                break ;

           case WM_CLOSE:
                ShowWindow(hw,SW_HIDE);
                return 0;

        }
        return DefWindowProc(hw,msg,wp,lp);
    }

     

     

     

     

     

    問題その1は

    ・templateは型ごとに作成されるので、Create関数はクラス内でなくグローバルに宣言した方がいいのではないか?

    ・同じ理屈でコンストラクタやデストラクタは…どうにもできないか

     

     

    問題その2は、継承時の奇妙さですw

    class Success : private Single<Success* >

     

    型ごとに作成するためだけの理由のtemplateなので、整数型でも良いかもしれませんが、把握が面倒なのでクラスポインタにしています。ここ、もうちょっとどうにかなるでしょうか?w

    2010年4月6日 8:44
  • >レスポンス(反応の遅延)やレイテンシ(処理の遅延)とかとはどういうところで分類されるものなのでしょうか?

    こちらも、基本は計測です。

    おおざっぱにいえば、

    パフォーマンス=一つのメッセージを処理する時間(プロシージャに入ってきてそれを抜けるまでの時間など)。

    レスポンス=処理を要求してから実際に処理されるまでにかかる時間(再描画を行う必要が発生してから実際に再描画され始めるまでの時間など)。

    レイテンシ=処理を要求したときに発生する待機時間など(自分でどうにかできるものではない時間)

    という感じです。

     

    クラスオブジェクト...

    のほうは、なんか無理やりシングルトンオブジェクトを作ろうとしてるからなのか、かなり歪みがでてる気がします。

    と。。。それとは別に...

    テンプレートの実装を inline スタイルにしていなくてもコンパイル時点でインライン化されると思いますよ。多少なりとも最適化をすれば...

    あとは、オブジェクト(C++のクラスも、HWNDなどなども)の個々の寿命とその関係性をどう管理していくか?なので、これがベストという解はおそらくないでしょう。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月6日 13:16
    2010年4月6日 11:32
  • ありがとうございます♪

    ということは

    「パフォーマンス」ならアルゴリズムの話にもなり得るけど

    レスポンスの改善となると、どちらかというとOSやCPU毎のチューニングっていう感じになるでしょうかね?

     

    >クラスオブジェクトについて

     

    はい、確かに歪みはありますw

    が、やはりそこはC++のクラスとWindowsAPIのプロシージャの相性自体がそんなにいいと思えないので

    パフォーマンスと利便性の両方を得たければ、なかなか避けがたいところではないでしょうか?


     

    よくよく考えると、派生クラスのコンストラクタ・デストラクタでしか呼ばれないような関数ならば、型ごとに生成されたとしても逆に積極的にインライン化させる ことによって、結局それぞれのクラスを作ってそれぞれのクラスごとに書いたのとほとんど大差なくなることに気付きました。

    あとはシングルトン的に動かすなら基本的に何度も不要に派生クラスのインスタンスを作るようなコードを書かなければいい、というだけです。

     

     

    私が実用面で「歪み」だと感じた最大の点は

    meを継承先のプロシージャで使い

    継承先のメンバを使うときはダウンキャストしないといけないことです。「コード的に面倒」とかなら一度キャストして保存しておけばいいのでしょうが、それは4バイトとか8バイトとかとはいえ、無駄な領域を使うことになります。また、別途保存するように書くためには結局何らかのメッセージか仮想関数を書くことを強要することになりかねません。

     

    しかし、テンプレートに渡すTが、最高の意味を持つ方法があったことに気付きました。

    さらに注意深く考えて、注意深く実験してみたら以下の方法が可能だと判明しました。(継承する場合は上記コードと同じように「継承先のクラスのポインタをテンプレート引数にしてやれば」できます。)

     

    template <class T>
    class Single{

    protected :
        HWND wnd;
        static T me;
         //Tの意味がある!(注意深く意味を考えるとコンパイラ的にはぎりぎりかもしれませんw)

        Single(void ) : wnd(null ){}
        virtual ~Single(void ){ if (wnd) DestroyWindow(wnd); me=null ; }

        virtual void Create( const HWND hw, LPCTSTR cls, WNDPROC p, LPCTSTR c=_T("" ) ){
            if (me) return;
            me = (T)this ;       //ただしここが重要です。これやらないと通りません。
            wnd=CreateWindowEx(0, cls, c, WS_OVERLAPPEDWINDOW|WS_VISIBLE,
                0,0,200,150,hw,null ,HINST::me,0);
            SetWindowLongPtr(wnd,GWLP_WNDPROC,(LONG_PTR)p);
        } 

    };

    template <class T> T Single<T>::me(null );

     

     

    「ただのポインタ」なので可能

    class Success : private Single<Success*> {...};

     

    これで使い勝手は良くなったと思います。

    依然として見た目変態的技法ではあるかもしれませんがw

    2010年4月6日 13:16
  • >レスポンスの改善となると...

    OSやCPUはもちろんですが、時代の変化もありますよ。

    CPUはマルチコア化が急速に進んでいます。つい先日、1ダイ(ニコイチではない)で8コアなCPUが出てきました。後5年もすれば物理コア二桁という時代が来るのでは?と思います。

    それに、今までのように32bitOnlyではなく64bitも同時に考慮(バイナリが異なるのでソース互換)していかなければという時代になっています。

    難しいですけどねw

    >なかなか避けがたいところではないでしょうか

    です。だからこそ設計に時間をかけるし、ライブラリを創れるだけの技量のある人は重宝される(このご時世でも転職先がある)のです。

    それだけ難しい世界だし、一朝一夕でどうにかなるというものでもないのですが...

    >テンプレートに渡すT

    ここは重要ですよ。今までTが何の役割も与えられていなかったのが、明確に役割をもつようになっています。

    この辺りは、もう少しさまざまなテンプレートライブラリの実装に触れてみることをお勧めしたいところですが...STLのソースを眺めるだけでも大分違う気がします。

    あと。。。ここでTは「必ず」ポインタとして使われるんですよね?

    であれば、T に Hoge* を指定するのではなく、たんに Hoge とする形でもいいと思います。

    template<class T>
    class Single
    {
     T* me;
    //... 以下省略...
    };
    

    こんな感じ。こうすると、p として、渡しているプロシージャを T::WndProc という形で直接指定することができるようになります。名前が固定されますが、仮想関数だと思えば同じですし、

    引数が減る分タイプ量も減りますので、バグ発生率が減ります(タイプ量から測定される定量的な値)。

    また、引数が減るため、呼び出しコストが下がりその分速度向上が期待できます。

    今の実装だと、クラスであるということの利点が半減してませんか?わざとかもしれませんけど...

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月7日 6:45
    2010年4月7日 2:37
  • ありがとうございます。

     

    たしか継承と包含で全く同じことが実現できるなら包含を使うべし、と言う法則ですね?

    (class Success : private Single<Success> {...}; というのは解決不可能でエラーになるのでw 別途作って置いたクラスを使って単にどこかで Single<Success>とするということですね? )

     

    私も包含で済むところは継承を使わずに実装したいと思っています。

     

    >クラスであるということの利点

    この場合、理由は、テンプレートを使ってコード量を最大限に減らしつつ、再利用性を高めつつ、名前制御とアクセス制御を行いつつ、一度初期化がすんでしまえば仮想関数は使わなくて済む、というのを自動的に行うためです。

     

    一瞬いけるか!と思ったのですが

    上記継承によってSingleクラスを作る最大の目的の一つは

    「型ごとにそのクラスにstaticポインタ が、継承しただけで何の苦もなく手に入る」

    ということです。しかもそれに対して「wndを持っているクラスの」「this」を自動的に代入してくれれば、コードが最大限に減る、と考えました。なおかつそれは、テンプレート引数と関係があるクラスそのものでなければ、前述したとおりキャストが必要になるか、作る意味自体がなくなってしまいます。

     

    私も継承を考える前に包含も考えたのですが

    その点を毎回書かずに行う方法が、テンプレート継承を使う以外に思いつきませんでした。

    思いついてないだけかもしれませんがw

     

    包含を使って実現可能でしょうか?

    2010年4月7日 6:45
  • >というのは解決不可能でエラー

    あれ?エラー?あ。。。private 派生だからかな?

    えっと。。。従来のOOPにこだわる必要がありますか?

    ちょっと見なおして思ったのですが、OOPにこだわるよりも、テンプレートメタプログラミング(genericsプログラミング)と呼ばれる技法を使うほうが、いい気がします。

    従来のOOPの利点であるカプセル化をある部分で否定してますよね(仮想関数とか)。であればそもそもそれを必要としない技法を使ったほうがいいのでは?と思います。

    となると、あまり書き換えずに。。。とはいきませんが、使えるようになるとコンパイル時間を犠牲にして、実行速度を改善することができます。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月7日 8:27
    2010年4月7日 7:48
  • ありがとうございますw

     

    >あれ?エラー?あ。。。private 派生だからかな?

    あ...

    すみませんw

    たぶんこれ、thisのキャストを忘れてたから出来ないと思いこんでしまっただけで、実際は出来るようですね。

    思ってたより若干templateの展開が早いか、この場合は少なくとも継承の判断より先にテンプレートが展開されるので、大丈夫っぽいですね。

     

    仮想関数についてですが、Create関数は例えばthisやmeをvoidポインタで渡すように書き足した関数をグローバルに作ってしまえば、なくすことは可能です。(且つ、インラインか抑止用プリプロセッサ命令とか書けばおそらく余計なコードは生成されない)

    元々これは仮想関数である必要もなく、もし全く別の処理を書くのなら派生先で独自に書けばいいです。

     

    テンプレートメタプログラミングは難しいですw

    特に何が難しいかというと、意図しない余計なコードが可能な限り生成されないようにすることが、ですw

     

    が、もし継承なしでテンプレートのみで、表記の省略ができるなら、この場合でも十分考えるべきだと思います。

    ここ、別のことをやってた時も何度か気になったことがあるのですが、継承なしでメンバを書くのを省略することって出来るのでしょうか?

     

    上記のことを実践すれば、現時点で残った仮想関数は「デストラクタ」のみになりますからねw

    継承を使わずに実現できれば、仮想関数なしで可能なはずです。

    2010年4月7日 8:27
  • >特に何が難しいかというと、意図しない余計なコードが可能な限り生成されないようにすることが、ですw

     意図しない余計なコードが何を指しているのかわからんのですが...書かれてあるもの以外には生成されないはずですが?

    私がわかってないだけかもしれませんけど。

     

    ・デストラクタに virtual を付ける必要がある場合

    オブジェクトを親クラスの型ポインタで保持しており、なおかつ delete する場合だけですよ?

    今回の場合は、親のクラスの型で保持する箇所がないので、多分不要です。

     

    派生しないで書いてみました。大きな変更点はHWNDをもつオブジェクトがSingleじゃなくなってることくらいです。同じ部分は、... とかやってるので、コンパイルできませんけどw

    template<typename T>
    class Single
    {
    public:
      static T* me;
    
      HWND Create( T* p, HWND hw,  LPCTSTR cls, LPCTSTR c=_T("") )
      {
        if (me) return;
        me = p;
        HWND wnd=CreateWindowEx(0, cls, c, WS_OVERLAPPEDWINDOW|WS_VISIBLE,
            0,0,200,150,hw,null ,HINST::me,0); 
        SetWindowLongPtr(wnd,GWLP_WNDPROC,(LONG_PTR)T::WndProc);
        return wnd;
      }
    };
    class Success 
    {
    private:
    static Single<Success> me;
    HWND wnd;
    static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    public:
      Success(HWND hw=NULL)
      {
        wnd = me.Create( this, hw, ... );
      }
    };
    LRESULT CALLBACK Success::WndProc( HWND hw, ... )
    {
      _ASSERTE( me.me->hwnd == hw );
      ...
      _stprintf_s(c,30,_T("%p MOUSEMOVE " ),me.me );
      ...
    }
    

    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月7日 15:29
    2010年4月7日 9:24
  • ば、バグ見つけたw

    Success クラスの WndProc は、public にしてください。いくらなんでも、private では。。。アクセスできないw

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月7日 15:29
    2010年4月7日 10:21
  •  

    ありがとうございます。

     

    ・意図しない余計なコードについて

     

    クラスと関わった時になる可能性がある?のか、正確なところは良く分かりませんが

    少なくとも私の環境では

     

    ヘッダ側で


    class aaaa{
        void func();
        double d;
    };

     

    そしてソース側で例えば適当に

    (どうでもいいコードなので色分けはしていませんw 処理内容全く見なくて良いです。)


    template <class T>
    struct abcdefg{
        T value;
        abcdefg(T a=0) : value(a){}
    };

    struct abcdefgdouble{
        double value;
        abcdefgdouble(double a=0) : value(a){}
    };

    template <class T>
    T func2( T a, T b ){
        abcdefg<T> c(a+b+size_t(&a)+size_t(&b));
        abcdefg<T> d(c.value+a+size_t(&c)+size_t(&b));
        abcdefg<T> e(c.value+d.value+size_t(&d));
        for (int i=10;i--;) c.value+=(d.value+e.value);
        return c.value*8+5;
    }

    double funcdouble2( double a, double b ){
        abcdefgdouble c(a+b+size_t(&a)+size_t(&b));
        abcdefgdouble d(c.value+a+size_t(&c)+size_t(&b));
        abcdefgdouble e(c.value+d.value+size_t(&d));
        for (int i=10;i--;) c.value+=(d.value+e.value);
        return c.value*8+5;
    }

    void aaaa::func(){
        char c[40];
        d=func2(98980.90,12345.); //ここが重要
        sprintf_s(c,40,"%f",d);
        MessageBoxA(0,"",c,0);
    }

     

    としたとき、意味的には全く同じに見えますが、ここでfunc2を選ぶと、funcdouble2を選んだ時と比べ

    呼び出し側のアセンブリコードで

     

    push    esi

     

     

    pop esi

     

    の二行が余分に加わります。あくまでこれは最適化オンの時なので、最適化オフだとかなり単純な関数でも実測値ではっきり区別できるほど違う場合があります。

    なんでこうなるのかは分かりませんが、少なくともコンパイラはかなり優秀な働きをしてくれるけれども、神ではない、ということです。

    その点では、利便性とパフォーマンスは(コンパイル時間をかわりに増幅させる代償を払ったとしても、それでも)やはりある程度のトレードオフは避けられない場合があるかもしれません。

     

    ・派生しない場合について

     

    う~~~む

    やはり、まったく継承なしではメンバの表記の省略はできないっぽいですかね?w

     

    「me.」が気になるっていうのはこだわりすぎかなぁ・・・w

     

    ところでCreate関数は戻り値がHWNDになれば、最初は

    if (me) return NULL;

    ですよね?というのはたぶん野暮な突っ込みなのでおいといてw

     

    Create関数をテンプレートクラス内に作って、インライン要請を出すんなら

    インライン要請を出してるんだから引数にプロシージャをくっつけて指定してしまっていいと思います。

     

    このときの秘儀は「プロシージャのデフォルト引数も設定する」 でしょう。(必要があれば設定できるので)

     

    具体的には

     

    HWND Create( T* p, HWND hw, LPCTSTR cls, LPCTSTR c=_T("" ), WNDPROC proc=T::WndProc ....その他

    という宣言にすれば良いと思います。

    こうすれば、TがWndProcを持ってなくても、どっかにあるプロシージャを指定してやればOKになるのです。(テンプレートは必要が生じるまで展開を遅延する、という性質と関係あると思います)

    しかも省略すればWndProcになります。

     

    >オブジェクトを親クラスの型ポインタで保持しており、なおかつ delete する場合だけ

     

    そうですねw

    あんなへんてこりんな継承をするコードは、たいてい使うとしたら自分だけと仮定すれば(w

    デストラクタまで含め、仮想関数を消しつくしても問題ないかもしれません。(ついでに言うと自分だけが派生クラス使うと仮定すれば、シングルトンのチェックすら余裕で不要(w))

    2010年4月7日 15:28
  • どうもw

     

    >Success クラスの WndProc は、public にしてください。いくらなんでも、private では。。。アクセスできないw

     

    この場合はそういうことでしょうがw

    とりあえず、継承を使う場合でも使わない場合でも

    プロシージャをpublicにする、という内容について、上の方に書いた私の考えが、特殊な時しか使えないならそんなにやるほどでもないか、と思いましたので、その他の点も含めもう少しじっくり考え込んでみます。

    2010年4月7日 15:32
  • えっと。。。なんとなく危険信号を感じました。

    継承しないとか、メタプログラミングとか手段が目的になりかけてる気がする!

    一応は、それなりに形になっていますのでこれまでのやり取りが無駄だったとは思いません。

    ですが

    後で一部変更するとき他のところがなるべく影響受けないようにと思って、今までほぼウインドウ毎にウインドウクラスを登録、としてきたのですが

    大規模アプリケーションを作るなら、プロシージャのアドレス以外が一致するウインドウクラスは、わざわざそれぞれを別々に登録するよりも、あらかじめ汎用的なウインドウクラスを作って、同じものを指定してウインドウを生成した直後に、プロシージャの変更をするのが一般的なのでしょうか?

    という質問に至った経緯も含めてもう一度考えてみることをお勧めします。

    Windows プログラミングは、C++とは異なるオブジェクト指向プログラミングを要求しているということも加味して。。。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月7日 17:52
    2010年4月7日 16:42
  • ありがとうございます♪

     

    >継承しないとか、メタプログラミングとか手段が目的になりかけてる気がする!

     

    大丈夫ですw

    あくまで「こういうことも考えられる」という考え方がたくさん集まれば、それは全てに波及する可能性があります。

    しかも幸いここがMSDNフォーラムという場なので、結果的に色んな知識を交換できるというのは素晴らしいことです。

    とても感謝していますw

    (てか、本音としてはこういう事を適度にずっこけながら考えるのは面白いから良い、のですがw)

     

    特に、継承元のクラスのテンプレートに、派生クラスを渡すという継承方法が可能だということが分かったのは、結構大きいかもしれません。

    (人に薦めるのは躊躇しますがw 実はああいうの結構気に入ってたりしますw)

     

    また、継承しなければメンバの省略は難しそうか、若しくはできない、というのが確認できたっぽいのも、大きそうです。

     

    実際今、この間までのコードをみると「手ぬるい」と思う箇所が色々あったりして、自分のアプリケーションが改善される見込みがある、という前向きな心理状態になります。これもまた、大事なことです。

    (実際、様々な個所を改善しました。)

     

    そして、あくまでいずれも手段の一つで「それもあり」ということで

    パフォーマンスが必要なければ利便性をとることもできるし、そうしておいてからあるところだけ後で特化しなおすのもやりようによっては普通にできる、と、具体例を伴って実感できたのは大きいですね。

     

     

    で、また面白半分真面目半分に、今度はかなりウインドウが多い場合(数百個とか千個以上とか)

    の取り出し用ハッシュ(?)的なものを作ってみました。

     

    今回の作戦としては

    アドレスで区分けするのですが

    バランスよく来てくれれば、例えば総格納数が1000個くらいにいった状態で、まだ

    最適化オプション付きのstd::mapの3倍くらいまで早いっぽいです

     

    typedef size_t uptr ;

    /* unsigned ポインタ互換整数、のつもりですが、規格上の保証はないっぽいらしいので将来万が一の事があった場合(ないとは思いますが)のためのおまじない  */


    template <class T>
    class Hash32{

        struct Data__{
            Data__* next;
            HWND w;
            T p;
            Data__( HWND a, T b ) : next(null ), w(a), p(b) {}
        };   
        Data__* data[32];

    public :
        Hash32();
        ~Hash32();
        void Add( HWND a, T b );
        T operator []( HWND a ){
            Data__* now( data[((uptr )a>>2)&31] );
            for ( ; now ; now=now->next ) if (now->w==a) return now->p;
            return (T)null ;
        }

    };

     

    /*  ↓たぶんこれだけじゃ問題ありでしょうが、 こういうもの(try catch & bad_alloc)は書いた方が良いかもしれません。 ちなみにtryブロックなくせばもちろんより速くはなります。今回インライン化については深く考えないことにします。外に出してるのは「適当」w */


    template <class T> Hash32<T>::Hash32() try {
       for ( int i=32; i ; ) data[--i]=null ;
    catch (std::bad_alloc) {
       /* 何かする → */    throw ;
    }

    template <class T> Hash32<T>::~Hash32(){
        Data__ *a,*b;
        for ( int i=32; i--; ){  

               for ( a=data[i]; a; a=b ) { b=a->next; delete a; }
        }
    }

     


    template <class T> void Hash32<T>::Add( HWND a, T b ){

        Data__ *now=data[((uptr )a>>2)&31], *c(null );
        while (now){ c=now; now=now->next; }

        if (!c) data[((uptr )a>>2)&31]=new Data__(a,b);
        else c->next=new Data__(a,b);

    /* ↑コンストラクタはまだいいとしてもここら辺にtrycatch仕込むとなるとちょっと泣けるかもしれませんw */

    }

     

     

    //////使い方//////

    Hash32<int > a;  //例えばintを指定するなら

    int i=257275;        //適当にテンプレートに渡す型のものを作ります
    a.Add(wnd,i);     //要素に追加

    int j=a[wnd];     //取り出し


     

    例によって、改善案とか面白いアイデアとか、「あんちゃん、そいつはバグってるぜ。実はここをこうすると落ちる」とか突っ込みどころあったら教えてくださいw

     

    これは現在ポインタ配列のそれぞれの要素の後ろにくっついてくものは、それぞれは単なる連結リストになっていますが

    探索速度を追求するなら、その連結リストは特定の関数によってnewで作る配列に置き換え可能にして、しかもその時は同時にソートしておいて

    そのうえで探索アルゴリズムを作るともっとウインドウ多くてもかなり早くなりそうです。(その場合、ソートするときに多少コストかかりそうなのでそこをどうするか、ですが)

     

    その他、現状でも一番後ろの(次に来たとき確保する、NULL状態の空き)やつをやはりこれも32個ポインタ配列として持っておいて、追加するときにいちいちnext→nextとたどっていかなくても良いようにも出来るでしょう。

    2010年4月7日 17:52
  • >あくまで「こういうことも考えられる」という考え方がたくさん集まれば、それは全てに波及する可能性があります。

    よかった。

    手段が目的化してしまうと、気が付いたら使えないものに。。。なんてこともあるのでw

     

    >取り出し用ハッシュ(?)的なものを作ってみました。

    そうか!順番は特に必要ないんだから、ハッシュでもいいのか。なら、対抗馬として std::hash_map(std::tr1::unordered_map) を提示しましょう。こうなったらとことんソース書かない方向でがんばってやる!<おい!

    unordered_mapは、TR1(C++0xの先行公開版と思ってよい)でhash_mapから名前の変わったものです。内容はほとんど同じらしいので、使い方も一緒ですが、ネームスペースが違います。

    VS2010でどうなるかは未確認です。こちらは 4/13 を待って。。。だな。あと一週間どういう発表があるのか楽しみです。<脱線しまくり!

    リストは、std::list がありますね。こちらも単純な線系リストとしてなら性能差はあまりないと思いますが、アロケータとかも絡んでくるので、それなりに微妙。。。かな?

    あと、ポインタを数値型としたいのなら、Win系なら INT_PTR(UINT_PTR)がいいと思います。SDK定義の互換型なので、VC以外でも利用できると思います。ただしほかの環境(Linux/BSDなど)で定義されているのかはわかりません。

     

    あと。。。HWNDが1アプリで1000個とかのオーダーになったとしたら、こっちがんばる前にアプリの設計を見直してください(実験レベルなら問題ありませんがw)。

    目安としては、膨大なウィンドウを使うという場合でも3ケタにならない程度しか同時に存在しないくらいにしておくことをお勧めします。

    理由は、HWNDなどのシステムリソースは有限で、システムグローバルに管理されているからです(XPまでは確か全体で8192とかその程度だったはず)。

    このアプリが動いてると動作が不安定になるなどという状況は自分専用でも、あってはならないことですのでw

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月8日 9:16
    2010年4月8日 2:24
  • ありがとうございますw

     

    unordered_mapについて調べてみたのですが、なぜかネット上にあった色々なサンプルをコピペしてみても、コンパイルエラーになったり、通ったら通ったでなぜかメモリがガシガシ増えまくって行ったと思ったらそのまま落ちたりと、「あれ~?」って感じです。

    たぶん正しい表記が分かってないせいでおかしいことが起きてるからだと思うのですが

     

    よろしければ正しい使い方を教えていただけませんでしょうか?w

     

    >Win系なら INT_PTR(UINT_PTR)がいいと思います。

    確かに、いかにもそれっぽい名前ですねw

    この場合

    typedef UINT_PTR uptr; //あくまで2重チキンw

    とかしてると、単に短くなったっぽいだけw

    確かにそのまんまの名前っぽい感じでw

     

     

    そんで、本題です。

    ウインドウについてなのですが

     

    ボタンとかもウインドウなので、流石に大規模アプリケーションとなると3ケタ突入しないのは厳しすぎませんか?w

    Spy++を使ってVisual C++2008 EEのウインドウ数を数えてみたら、ざっと700個以上はありました。

    また、色がうすくない表示になっているものも、100個以上はかる~くありました。

    まぁ確かにイメージ的にはこんなもんぐらいかなと納得できる数値ですが

    やはり常駐でない本格アプリなら総量ではまぁ1000個以内くらい、同時に有効・表示状態で存在するものも100~200個くらい、を目安にしていれば十分ではないかな?と思います。

     

    ただしこれ、考え方という点では、非常に重要な点です。

     

    画像ソフトとか音楽関係のソフトとかで、なんかドラッグ操作とかできる点とかがある場合あるじゃないですか?

    ああいうのについては、CreateWindow系でウインドウとして作って使うんじゃなくて、自分でクラス作って座標とかで制御するのが一般的なのでしょうか?

    2010年4月8日 9:16
  • ・unordered_map

    使い方は、std::map<K,V> や、std::hash_map<K,V> と同じです。VS2008ProSP1では、<unordered_map> をインクルードするだけで使えてます。

    Hash32<T>の置き換え的に書くと...

    #include <unordered_map>
    
    
    
    std::tr1::unordered_map<HWND,int> a;
    int i=257275;        //適当にテンプレートに渡す型のものを作ります 
    
    a[wnd] = i;
    
    //取り出し
    std::tr1::unordered_map<HWND,int>::iterator itr = a.find( wnd );
    if( itr != a.end() ){
      int j=itr.second;
    }
    //値のセットが保証できるのなら...
    int j=a[wnd];
    

    と。。。こんな感じです。std::map と同じように使えますよ。VS2008ProSP1なら、これで動問題なく動きます。コンパイルエラーがなければw

    ・INT_PTR

    typedef された値なので、これをそのまま使ってもいいと思いますよ。セットされていない環境では、これに相当するものを別途定義すればいいだけですし。

     

    ・ウィンドウの数

    同時に存在できるウィンドウの数には制限があります。これは空きメモリ量ではなく、システムで固定された値(Vistaで変更されてるとは思いますが、資料がないので不明)なので、それを超える数のウィンドウを作成することができません。少なくともXPの初期までは、8192個とされているといわれていました(数えられるわけじゃないので詳細は分かりませんが)。

    8000程度しかウィンドウが作れないところで1000近くとなると、1割以上食いつぶされることになります。実際は、OSやらいろいろな常駐ソフトやらがウィンドウを作ってるので、利用するときに起動して使うアプリで1000というのはほぼ限界に近い値と思ったほうがいい値です。

    これから先少しずつあるいは、大きく制限は緩和されるとは思います。が、それでも限界数が8192じゃなく、10240とか16384とかそういう数字になるというだけで、上限撤廃とはならないと思います。

    それに、WPFなんかは内部的にはウィンドウ(OSでいうウィンドウ)を作らず、そのような感じ。。。で個別に領域処理してますし。きっといろいろ限界が見えているんだと思います。

     

    ・ドラッグドロップとか。。。

    マウスオペレーションで画像を動かしたり。。。だと思いますが。。。こちらはやり方はいろいろあります。

    ですが、移動するためのウィンドウを作ったりはしません。

    アプリないだけであれば、マウスメッセージをキャプチャーしてその操作が終わったところで、データを適切な位置に貼り付けるなどを行います。

    ほかに、OLEのDrag&Dropの仕組みを使う場合もあります。通常はアプリケーション間でのやり取りで使いますが(エクスプローラからファイルを落とすなど)、それ以外でも使えるので、うまく活用すればある程度はお任せ。。。とできます。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月8日 22:00
    2010年4月8日 17:11
  • ありがとうございます。

     

    うう~む

    #include <unordered_map>

    として

    std::tr1::unordered_map<HWND,int> a;

    の行を書いたら、その時点で60個くらいコンパイルエラーが出るので

    ExpressEditionではこれもまた使えないのかもしれません。

     

    そこで、かわりにboostのと勝負してみました。

     

    #include <boost/unordered_map.hpp>

    boost::unordered_map<HWND,int> a;

     

    これはできました。後の流れは全く同じですが、これらって実際は全く同じものだったりしますか?

    てか

    a[wnd] = i;

     

    この書き方は魅力的ですねw

    なかった場合は作って返却するようになってるのかな?(boostは読むのがめちゃめちゃ大変なので確認していませんw)

    まぁ、こうじゃないと困るわけじゃないのでとりあえずそのままでw

     

    で、どちらも最適化オプションありで計測した結果ですが

    (なお、HWNDは0~999までの整数を(HWND)変数とキャストしてやりました)

     

    1000要素の追加に費やした時間 → 平均5対6程度でHash32の勝利

    1000要素の探索に費やした時間 → 平均5対4程度でboost::unordered_mapの勝利

     

    流石boostに入ってるのはやっぱり結構高性能w

    用途的には追加に費やす時間より探索に費やす時間の方が重要。そして適当にやっていては勝てない。

    そして、「専用ルーチン」ならばここで上回らないと意味がないw

    boostはコンパイル時間がかなりかかる、という点では自作のを使うメリットはあるけど、せっかくなら専用なんだから探索時間で勝負したいw

    しかしそうなるとガチでいかないとアレっぽいですねw

     

    そこで・・・w

     

     

    new配列としてある程度の予約領域を確保し、一定の周期ごとに拡張

    気分的に性能比較用も兼ね、カスタマイズ性を求めてテンプレート引数を増やします。

    Nはハッシュの区分けするキーの種類数

    ALLは新しく追加する要素が埋まりきっている時に拡張する要素数です。

     

    なお

    >typedef された値なので、これをそのまま使ってもいいと思いますよ。

    大丈夫ですw

    「字数が短くなるという」というメリットもありますw 載せる場合は特にw

    (実際はどちらかというと ~_PTR の仕様は自分で決めれないけど、さらにそれを使ってtypedefしてれば、自分がコントロール出来てる感が若干強いので、より安心できるという心理面の問題ですがw)

     

    usertype.datで好きな単語の色変化作戦使えばどんな名前にしたって結局見分けやすいですしね

     

    (私は今のところ、自分で定義したのも含め

       DWORD ,   LPCTSTR ,   UINT ,   VOID ,  WORD ,   BOOL ,   BYTE ,   TCHAR ,  uptr ,  alignof ,  null

    を青色にしています。)

     

    おおっと、話がそれましたねw

     

    というわけで、改良してみました。これでどうだっ♪

     

    template < class T, int N=16, class U=HWND, int ALL=4>
    class HashEx{

        struct DATA{  //コンストラクタはなくしたけども・・・
            U w;
            T p;
            void operator ()(U a, T b){ w=a; p=b; }
        };   
        DATA* data[N];
        uptr Len[N];

    public :
        HashEx();
        ~HashEx();
        void Add( U a, T b );


        T operator []( U a ){                   //2分探索
            uptr i=(uptr )a/sizeof (U)%N;    //本当はalignofを使いたいけどC++0xまで我慢w
            const DATA* p( data[i] );
            uptr j=0, k=Len[i]-1;
            while ((int )j<=(int )k) {
                if ( p[i=(j+k)/2].w==a ) return p[i].p;
                else if ( (uptr )p[i].w < (uptr )a ) j=i+1;  //ポインタの不等号はsignedでしたっけ?
                else k=i-1;
            }

            return (T)null ;
        }

    };

    template < class T, int N, class U, int ALL>
    HashEx<T,N,U,ALL>::HashEx() try {
        NULL_PTAR(data,N);     //ポインタ配列を全部NULLにするマクロ
        CLR(Len);                    //整数配列0埋めマクロ
    } catch (std::bad_alloc) {
      /* ... */  throw ;
    }

    template < class T, int N, class U, int ALL>
    HashEx<T,N,U,ALL>::~HashEx(){
        for( int i=N; i--; ) delete []data[i];    //なんじゃこりゃw
    }

    /* ↓ tryブロック仕込んだほうが良いのかもしれないけど 眠いので放置 */


    template < class T, int N, class U, int ALL>
    void HashEx<T,N,U,ALL>::Add( U a, T b ){

        const uptr d=(uptr )a/sizeof (U)%N, i=Len[d];

        if (!i) {
            data[d] = new DATA[ALL];
            data[d][0](a,b);
            ++Len[d]; return ;
        }
       
        uptr j=0;
        for (;j<i;++j) if ( (uptr )data[d][j].w > (uptr )a ) break ;

        if ( !(i%ALL) ) {
            DATA *temp = new DATA[i+ALL];
            memcpy(temp,data[d],j*sizeof (DATA)); //本当にPODの保証あるのかな?そこは心配
            temp[j](a,b);
            memcpy(temp+j+1,data[d]+j,(i-j)*sizeof (DATA));
            delete data[d];
            data[d]=temp;
            ++Len[d]; return ;
        }

            memmove(data[d]+j+1,data[d]+j,(i-j)*sizeof (DATA));
            data[d][j](a,b);
            ++Len[d];

    }

     

    うはww

    改めてみてみると、一気にまた仰々しくなりましたww

    あんまりこっちに有利だと気の毒なので(え?w)

    テンプレートのデフォルト引数の、二つの整数はちょっと抑えめの数値にしてます。

     

    そしてこれによる結果はなんと・・・!

    要素の追加にかかった時間 → 5対2くらいでHashExの勝利

     

    探索にかかった時間

     

    仮に

    boost::unordered_map<HWND,int>

    0.000000032551548

    くらいとすると

     

    HashEx<int>

    0.000000028964936

     

     

    び、微妙ww

     

     

    でも安定して差はあるみたいだから、少なくとも一応ループとして回してるという状況における速度面だけ見れば、この状況では超えること自体はできました。(でもどうでも良いレベルw)

    やはり少なくともboostは優秀でした。

     

     

    ちなみに

    HashEx<int,32,HWND,8>

    くらいにすると

    0.000000024814199

    位の比率になります。メモリを多少豪快に使うようにすれば、それだけ速度が期待できる可能性がある、という事ですね。

     

    っていうか、でもやっぱりこの程度の差だと実用上全く違いが分からないかもしれませんw

     

    ・ウィンドウのことについて

    やはりそうですか!

    その辺気がかりだったのでそういう関係になりそうなところはあんま作りこめてないのですが、そうと決まれば「いかにウインドウを減らしつつパフォーマンス、利便性を高めるか」

    というところも、追及の余地ありということになりますか・・・w

     

    とりあえず、気づいたら朝なので今はもう眠ることにしますw

    たぶん上のコード規格上保証されてない操作がありそうですが、それを含め突っ込みどころあったら教えてくださいw

     

    [15:19]

    良く考えると未追加の場合に-1(==0xFFFFFFFF==unsignedで「最大」)と0の比較になって終わらない・・・→いや不正アクセスでは?と思ったので

    while ((int)j<=(int)k) {

    とキャストするように訂正。(元々intとして宣言するとなぜか遅くなりましたが、これだと速度は変わりませんでした。)

    2010年4月8日 22:00
  • ・unordered_map

    エラーが出てしまうんだとすると...ファイルが壊れてるか、なんか別の要因のような気がしますがエラー内容がわからないとなんとも言えませんね。

    んと。。。挿入速度で負けてる。。。と...

    operator[] はすごくわかりやすいけど、速度面でいまいちなので、insert にすれば追加速度はちょっと期待できるかな?

    それでも、専用のほうが多分早いですけどねw

     

    >なかった場合は作って返却するようになってるのかな?

    std::map, std::hash_map も同様ですが、左辺に [] が来る場合は、受け入れる側なので、器がなければ用意します。右辺に来る場合は、const が優先されると思うので、作らない(なかったら例外)はずです。

     

     

     

    >追及の余地ありということになりますか

    おおいにあります。

    なぜか。。。というと、HWNDはその存在自体がかなりのオーバーヘッドをもちます。領域管理も多いほうが煩雑になりますし、さまざまな状態管理も数が多いより少ないほうがやりやすいのは、それなりに経験を積んだプログラマなら容易に想像できるのではないか?と思います。

    もちろんむやみに減らせばいい(なくせばいい)とはなりませんが、少ないほうがオーバーヘッドになる分が減るので確実に速度向上は期待できます(おもにレスポンスタイムの向上や、レイテンシの低減)。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月9日 8:28
    2010年4月9日 8:06
  • ありがとうございます。

    エラー内容は訳の分かんないところ(unordered_map中のテンプレートの内部)を指してるので、ファイルの破損かもしれません。

     

    とりあえず、コード云々ではありませんが、重大なことが分かったので。

     

    HashEx版だと

    例えば挿入時のポインタ移動中にWM_PAINTが飛んできて、その中でインスタンスメンバにアクセスしてて落ちるという場合があったことに気付いたので(そういえば前std::map使ってた時もそういう事がありました。挿入時の時間のかかり方をみると、やはりunordered_mapもそうかもしれません。)

    おそらく探索速度を高める為にソートするHWNDとの関連づけに使う のはタイミング次第では危険なので結局戻り値チェックが必要

     

    boost::unordered_mapも調べてみようと思いましたが、なぜかクラスメンバにするとコンパイル不能に・・・(なんでw)

     

    ということで、この用途の場合はおとなしく(?)連結リスト版の上記Hash32を同様に多少カスタマイズ可能にしておいたものを使っておきますw

    これならチェックしなくても安全です。

    ソートなしでも1000以内でunordered_mapの80%程度の探索速度は出ていますからね。

     

    ただし、みてのとおりHashExはもう「専用と呼ばなくてもいいかも!」という位の汎用性になっていますので(class Uに何かを指定すれば)

     

    >HWNDはその存在自体がかなりのオーバーヘッドをもちます

     

    やはりそうですかw

    そういう予感がしました。

     

    ということなので、座標と大きさ「RECT構造体でも持たせるかな・・・?」

    を使ったHWNDを使わない、オブジェクトに対して使えるかもしれないので、そこを熟考してみます。(残念ながらそろそろバイトの時間ですので帰ってきてからですがw)

    2010年4月9日 8:27
  • >挿入時のポインタ移動中にWM_PAINTが飛んできて

    ここがすごく気になります。実装がわからないので、なんとも言えませんが、普通にやってたらそのようなことはまずもって発生しないはず。

    あるとすれば、コレクションに突っ込む処理を別スレッドでやっていて排他制御していないとか、ソート処理の中で、メッセージポンプ回すとか。。。

    今その作業中にあってはならない事例をやっているというパターン以外にはありえないはずなんですが。。。

     

    ちなみに、エラーメッセージは、テンプレートクラスを使ってるので、ほとんどすべての状況においてわけのわからん内容のことが多いです。なので、エラーメッセージ見ないと助言できないです。

     

     

    HWNDをどうするか?はそこで何を表現するかに依存するので、なんとも言えません。ただ一つ言えることは

    その部分は、汎用的に表現するのは非常に難しいことが多い。

    というところかな。

    汎用的に扱える部分もあると思いますが、多くはそれ専用か専用に近いものになることが多いです。

     

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月9日 14:17
    2010年4月9日 10:18
  • ありがとうございます。


    >挿入時の~

    実はプロシージャの切り替えタイミングに問題があったようですw

    直したらいずれでも戻り値チェックなしでうまく動きました。ただしこの場合「手順の厳格さが要求されるという点」では当然速度を求めすぎると安全度が下がる可能性はあるという事はいえると思います。

     

    スレッドというのは、ウインドウをいくつ作っても自分で作らない限り必ず、増えることはないのですか?

    基本的にはないと思っていたのですが、完全に「いかなる場合でも」別スレッドを作らない限りほかの事やってるときは処理を止めるのでしょうか?

     

     

    >エラーメッセージ

    正確にはどこに書いても「必ず58個」でます。ただしいずれもたくさん重複しており

    種類は

     

    error C4430: 型指定子がありません - int と仮定しました。メモ: C++ は int を既定値としてサポートしていません
    error C3203: '_Umap_traits' : 非特殊クラス テンプレート は、テンプレート 引数として テンプレート パラメータ '_Traits' に使用できません。実際の型を指定してください
    error C2977: 'stdext::_Hash' : テンプレート 引数の数が多すぎます
    error C2975: 'std::tr1::_Umap_traits' : '_Mfl' の無効なテンプレート引数です。コンパイル時の定数式が必要です
    error C2974: 'std::tr1::_Umap_traits' : テンプレート 引数が '_Tr' に対して無効です。型が必要です
    error C2955: 'stdext::_Hash' : クラス テンプレート を使用するには テンプレート 引数リストが必要です
    error C2275: '_Kty' : この型は演算子として使用できません
    error C2238: ';' の前に無効なトークンがあります。
    error C2208: '_Hash<_Traits>::_Traits::key_compare' : メンバのない列挙型、構造体、共用体が定義されました。
    error C2146: 構文エラー : ';' が、識別子 'key_compare' の前に必要です。
    error C2143: 構文エラー : ';' が '<' の前にありません。
    error C2065: '_Mytraits' : 定義されていない識別子です。
    error C2039: '_Hash_compare' : 'stdext' のメンバではありません。

     

    これで全部です。どちらかというとどうも必要なものが不足しているような感じがしますが・・・

     

     

    [追記]

    いやいや大うそでしたw

    プロシージャの切り替えタイミングではありませんでした。

    少なくともHashExで落ちた理由は、寝る直前に何となくwhileの方が若干速くなるかな?とか適当に思って、深く考えずに


    for (;j<i;++j) if ( (uptr )data[d][j].w > (uptr )a ) break ;

    while (j<i) if ( (uptr )data[d][j++].w > (uptr )a ) break ;

     

    としてしまっていたのが原因で(これだとbreakが通ってもインクリメントされてしまう)

    特定のデータが取り出せなくなってしまうという単純なバグだったようですw

    いわゆる、while文のトラウマと呼ばれるやつです。(←そんなの聞いたことありませんが)

    ちゃんとそこを直したら、いまのところ今までどおりの切り替えタイミングでうまくいっています。

     

    以前std::mapで落ちたというのは、どこでどうやったのか正確に思い出せませんが、単に書き方が間違ってたのかもしれません。(?)

    2010年4月9日 14:17
  • >特定のデータが取り出せなくなってしまうという単純なバグだったようですw

    なるほど。。。なら、納得です。単一のスレッドからアクセスしているのなら再帰しない限り重複アクセスは発生しないのになぜ???

    と思ったので。

    ・エラー

    うーん。。。テンプレートパラメータに渡してる型に問題がある気がする。。。けどこれだけじゃよくわからん。

    ちゃんとエラーを突き詰めるなら、新規にコンソールプロジェクトを作成し、

    ソースの stdafx.h の次の行に

    #include "stdafx.h"
    #include <unordered_map>
    
    std::tr1::unordered_map<int,int> hoge;
    
    int _tmain( ... )
    {
    ...
    }
    
    

    となるようにしてみてください。

    これでエラーが出るなら、ソースが壊れてます。そうじゃなければ、もうエラー詳細とソースを見ないとちょっと無理かも。。。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月11日 10:00
    2010年4月10日 2:10
  • ┌****//////////これを投稿3分後に追記//////////*****

    ありがとうございます。

    ってあれ?ww

    なんでとっちゃんさんのレス表示されてなかったんだろう?

     

    >ソースの stdafx.h の次の行に~

    分かりました。とりあえず試してみます。

     

    └****////////////////////*****

     

     

     

    とりあえず、疑似ウインドウ的なものを作ってみました。

    (作る必要があったからではなく実験として作ったので、詰めが甘い箇所がいくつもありますがw)

     

    というより、画像ソフトであるような、移動/サイズ変更可能な四角形みたいなものです。

    ただし、マウスが離れている状態で黒に、マウスが四角形内にあるときは白に、マウスでドラッグ中は青に、サイズ変更中は黄色になります。

    選択中の四角形は四隅に変なのがつきますw

    また、選択中の四角形はBackSpaceで消せます。

    Enterキーで、ウインドウを増やせます(ただし10個までです!実験用という事で面倒なのでチェックとか拡張可とかにしておらず、このコードではそれ以上作ろうとすると落ちますw

    (また、ウインドウを増やすときはそこ再描画ちゃんと作ってないので最初表示されませんw)

     

    それでもやはりウインドウなしでウインドウもどきを作るとなると工数がかなり多くなりました。

    本格的にこういう疑似ウインドウをどうこうしたことはなかったので、やはり完全に専用的な感じになっていますが(w)

    分解できるところを適切に分解すれば、ある程度汎用的にできるかもしれません。

    ただ、無駄が完全にないように分解するにはかなり考えないと難しいかもしれません。

    (なお、インライン化は意図的ではなく、単に載せるときにコード量が増えるのが面倒なので結果的に要請することになっていますw)

     

    //////////////ヘッダ/////////////

     

    class KeySwitch{ //キーボードからの入力制御
        char key[256];
        HWND wnd;
        UINT id, msec;
        TIMERPROC Proc;
    public:   
        char Get(char a) const { return key[a]; }
        KeySwitch(void) : wnd(null), Proc(null) { CLR(key); }
        VOID SetWnd( HWND hw, UINT TimerID, UINT uElapse, TIMERPROC proc=null){
            wnd=hw; id=TimerID; msec=uElapse; Proc=proc;
        }
        VOID KeyUp(WPARAM wp){
            key[wp]=0;    if ( !wnd ) return;
            for (int i=256;i--;) if(key[i])  return;
            KillTimer(wnd,id);
        }
        VOID KeyDown(WPARAM wp){
            key[wp]=1;    if ( !wnd ) return;
            for (int i=256;i--;) if(key[i]) { SetTimer(wnd,id,msec,Proc); return; }       
        }
    };

    class WhiteBlackRect;
    class WBList;

    class EXWCLASS : private Single<EXWCLASS> { //本物のウインドウ
        WBList* aaaa;
        static KeySwitch keys;
        VOID Pos();
    public:
        static LRESULT CALLBACK Proc(HWND, UINT, WPARAM, LPARAM);
        EXWCLASS(HWND hw=null);
        ~EXWCLASS();
    };


    class MyMouseMovement{ //四角形(疑似ウインドウ)の境界付近でのカーソルの形制御

        enum{ ←=1, ↑=2, →=4, ↓=8, 幅=10 };
        RECT rec;
        POINT po;
        DWORD cur;

    public:
        MyMouseMovement() : cur(0) { CLR(po); }
        VOID down(LPARAM lp, const RECT& rc){
            memcpy(&rec,&rc,sizeof rc);
            po.x=LOWORD(lp);
            po.y=HIWORD(lp);
        }

        VOID SizeMove(LPARAM lp, RECT& rc) const{
            cur ? Size(lp,rc) : Move(lp,rc);
        }

        VOID Move(LPARAM lp, RECT& rc) const {
            const LONG x=(SHORT)LOWORD(lp)-po.x+rec.left;
            const LONG y=(SHORT)HIWORD(lp)-po.y+rec.top;
            rc.right=rc.right-rc.left+x;  rc.left=x;
            rc.bottom=rc.bottom-rc.top+y;  rc.top=y;
        }

        VOID Size(LPARAM lp, RECT& rc) const;
        VOID SizeLeft(LPARAM lp, RECT& rc) const{
                if ((SHORT)LOWORD(lp)<rec.right) { rc.left=rec.left+(SHORT)LOWORD(lp)-po.x;  rc.right=rec.right; }
                else  { rc.left=rec.right;  rc.right=rec.left+(SHORT)LOWORD(lp)-po.x; }
        }
        VOID SizeRight(LPARAM lp, RECT& rc) const{
                if (rec.left<(SHORT)LOWORD(lp)) { rc.left=rec.left;  rc.right=rec.right+(SHORT)LOWORD(lp)-po.x; }
                else  { rc.left=rec.right+(SHORT)LOWORD(lp)-po.x;  rc.right=rec.left; }
        }
        VOID SizeTop(LPARAM lp, RECT& rc) const{
                if ((SHORT)HIWORD(lp)<rec.bottom) { rc.top=rec.top+(SHORT)HIWORD(lp)-po.y;  rc.bottom=rec.bottom; }
                else  { rc.top=rec.bottom;  rc.bottom=rec.top+(SHORT)HIWORD(lp)-po.y; }
        }
        VOID SizeBottom(LPARAM lp, RECT& rc) const{
                if (rec.top<(SHORT)HIWORD(lp)) { rc.top=rec.top;  rc.bottom=rec.bottom+(SHORT)HIWORD(lp)-po.y; }
                else  { rc.top=rec.bottom+(SHORT)HIWORD(lp)-po.y;  rc.bottom=rec.top; }
        }

       
        VOID Framing(HWND hw, LPARAM lp, const RECT& rc){
            cur=0;

            if ( LOWORD(lp) < rc.left + 幅 ) cur|=←;
            else if ( rc.right-幅 < LOWORD(lp) ) cur|=→;

            if ( HIWORD(lp) < rc.top+幅 ) cur|=↑;
            else if ( rc.bottom-幅 < HIWORD(lp) ) cur|=↓;

            SetClassLong( hw,GCL_HCURSOR,(LONG)LoadCursor(0, CurName()) );

        }

        LPCTSTR CurName(){
            switch(cur){
                case ←|↑:    case →|↓: return IDC_SIZENWSE;
                case →|↑: case ←|↓: return IDC_SIZENESW;
                case ←: case →: return IDC_SIZEWE;
                case ↑: case ↓: return IDC_SIZENS;
            }
            return IDC_ARROW;
        }
        DWORD CursorType(){ return cur; }
    };


    class WBList{  //疑似ウインドウリスト(?)
        WhiteBlackRect* p[10];
        WhiteBlackRect *Selected, *Moving, *Hover;
        DWORD Len;
        HWND wnd;
        Byte in;
        VOID Remove( DWORD d );
        VOID Zorder(WhiteBlackRect*);
    public:
        VOID Remove();
        WBList(HWND hw);
        ~WBList();
        void Add( WhiteBlackRect* b );
        WhiteBlackRect* Get( POINT );
        WhiteBlackRect* Get( LPARAM );
        VOID Mes(UINT msg, LPARAM lp);
        VOID Draw(HDC hdc);
        VOID Pos(LONG x, LONG y);
    };


    class WhiteBlackRect{ //疑似ウインドウ
        static MyMouseMovement mmm;
        static HBRUSH br, br2;

        RECT rc;
        HWND wnd;
        BOOL OnMouse;
        enum { SELECT_=3, ChWid_ = 5 };

        const RECT* ExpandRect(const RECT& t){
            static RECT rec;
            SetRect(&rec,t.left-8,t.top-8,t.right+8,t.bottom+8);
            return &rec;
        }

        VOID DrawSel(HDC hdc){
            HBRUSH t=(HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH));
            RectFromLong(hdc,rc.left,rc.top);
            RectFromLong(hdc,rc.left,rc.bottom);
            RectFromLong(hdc,rc.right,rc.top);
            RectFromLong(hdc,rc.right,rc.bottom);
            SelectObject(hdc,t);
        }

        VOID RectFromLong(HDC hdc, LONG x, LONG y){
            ::Rectangle(hdc,x-SELECT_,y-SELECT_,x+SELECT_,y+SELECT_);
        }

    public:   
        enum { OUT_ , ON_  , DOWN_ , CAPTURE_ };

        VOID SetWnd( HWND hw ){wnd=hw;}
        WhiteBlackRect() : OnMouse(FALSE) {
            SetRect(&rc,0,0,50,50);
            if (!br) {
                br=CreateSolidBrush( RGB(0,0,255) );
                br2=CreateSolidBrush( RGB(255,200,0) );
            }
        }

        VOID Invalidate(const RECT& t){    InvalidateRect(wnd,ExpandRect(t),1); }
        VOID Invalidate(){ InvalidateRect(wnd,ExpandRect(rc),1); }

        WhiteBlackRect( const RECT& rect ) : rc(rect), OnMouse(0) {
            if (!br) {
                br=CreateSolidBrush( RGB(0,0,255) );
                br2=CreateSolidBrush( RGB(255,200,0) );
            }
        }
        ~WhiteBlackRect() {
            if (br) { DeleteObject(br); DeleteObject(br2); br=null; }
        }

        BOOL In(LPARAM lp){
            POINT po={LOWORD(lp), HIWORD(lp)};
            RECT rec(rc);
            rec.left-=ChWid_; rec.right+=ChWid_;
            rec.top-=ChWid_; rec.bottom+=ChWid_;
            return PtInRect(&rec,po);
        }
        BOOL In(const POINT& po){
            RECT rec(rc);
            rec.left-=ChWid_; rec.right+=ChWid_;
            rec.top-=ChWid_; rec.bottom+=ChWid_;
            return PtInRect(&rec,po);
        }

        VOID Pos(LONG x, LONG y){
            RECT t(rc);
            if (x<0) t.left+=x; else t.right+=x;
            if (y<0) t.top+=y; else t.bottom+=y;
            rc.left+=x; rc.right+=x;
            rc.top+=y; rc.bottom+=y;
            Invalidate(t);
        }

        VOID SizeMove(LPARAM lp){
            Invalidate();
            mmm.SizeMove(lp,rc);
            Invalidate();
        }

        VOID MouseMove(LPARAM lp){
            char c = OnMouse;
            OnMouse = In(lp) ? ON_ : OUT_;
            if ( c != OnMouse ) Invalidate();
            if ( !OnMouse ) { ResetCursor(); return; }
            mmm.Framing(wnd,lp,rc);
        }

        VOID ResetCursor(){
            if (mmm.CursorType())
                SetClassLong(wnd,GCL_HCURSOR,(LONG)LoadCursor(0, IDC_ARROW));
        }

        VOID LButtonDown(LPARAM lp){
            mmm.down(lp,rc);
            OnMouse=DOWN_;
            Invalidate();
        }

        VOID LButtonUp(LPARAM lp){
            if(OnMouse==CAPTURE_) ReleaseCapture();
            OnMouse=ON_;
            Invalidate();
        }
        VOID LUp(LPARAM lp){
            OnMouse= (In(lp)) ? ON_ : OUT_;
            Invalidate();
        }
        VOID Leave(){
            if ( OnMouse==DOWN_ ) { OnMouse=CAPTURE_; SetCapture(wnd); return; }
            OnMouse= OUT_;
            Invalidate();
        }


        VOID Draw(HDC hdc, WhiteBlackRect* a){
            if (OnMouse==ON_) { ::Rectangle(hdc,rc.left,rc.top,rc.right,rc.bottom); }
            else {
                HBRUSH t=(HBRUSH)SelectObject(hdc, (!OnMouse ? GetStockObject(BLACK_BRUSH) :
                    ( mmm.CursorType() ? br2 : br)) );
                ::Rectangle(hdc,rc.left,rc.top,rc.right,rc.bottom);
                SelectObject(hdc,t);
            }
            if (a==this) DrawSel(hdc);
        }
       
    };

     

    ///////////////ソース//////////////


    MyMouseMovement WhiteBlackRect::mmm;
    KeySwitch EXWCLASS::keys;
    HBRUSH WhiteBlackRect::br(null), WhiteBlackRect::br2(null);

    EXWCLASS::EXWCLASS(HWND hw) : aaaa(null) {
        Create(hw,WndClassLibrary::WhiteWindow,_T("exwclass"));
        SetWindowLong(wnd,GWL_EXSTYLE,(LONG)WS_EX_COMPOSITED);
        keys.SetWnd(wnd,1,10);
        RECT rc={120,120,150,150};
        aaaa=new WBList(wnd);
        aaaa->Add( new WhiteBlackRect );
        aaaa->Add( new WhiteBlackRect(rc) );
    }
    EXWCLASS::~EXWCLASS(){
        delete aaaa;
    }


    LRESULT CALLBACK EXWCLASS::Proc(HWND hw, UINT msg, WPARAM wp, LPARAM lp){
        static HDC hdc;
        static PAINTSTRUCT ps;
        static int i=0;

        switch(msg){
            case WM_PAINT:
                hdc=BeginPaint(hw,&ps);
                me->aaaa->Draw(hdc);
                EndPaint(hw,&ps);
                return 0;
            case WM_TIMER:
                if (wp!=1) break;
                me->Pos();
                return 0;
            case WM_MOUSELEAVE:
            case WM_MOUSEMOVE:
            case WM_RBUTTONUP:
            case WM_LBUTTONUP:
            case WM_LBUTTONDOWN:  me->aaaa->Mes(msg,lp);     break;
            case WM_KEYUP: keys.KeyUp(wp); break;
            case WM_KEYDOWN:
                if (wp==VK_BACK) { me->aaaa->Remove();break; }
                else if (wp==VK_RETURN){ me->aaaa->Add( new WhiteBlackRect );break; }
                keys.KeyDown(wp); break;
        }
        return ::DefWindowProc(hw , msg , wp , lp);
    }

    VOID EXWCLASS::Pos(){
        aaaa->Pos( (keys.Get('C')-keys.Get('Z'))*4, (keys.Get('X')-keys.Get('S'))*4 );
    }


    VOID MyMouseMovement::Size(LPARAM lp, RECT& rc) const {
        if (cur&←) SizeLeft(lp,rc);
        else if (cur&→) SizeRight(lp,rc);
        if (cur&↑) SizeTop(lp,rc);
        else if (cur&↓)  SizeBottom(lp,rc);
    }


    WBList::WBList(HWND hw) : Selected(null), Moving(null), Hover(null), Len(0), wnd(hw),in(0)  {}
    WBList::~WBList(){ for (int i=Len;i--;) delete p[i]; }
    void WBList::Add( WhiteBlackRect* b ){ b->SetWnd(wnd); Selected=p[Len++]=b; }
    VOID WBList::Remove( DWORD d ){
        for (DWORD i=d;i<Len-1;++i) p[i]=p[i+1];
        --Len;
    }

    VOID WBList::Remove(){
        if (!Selected )return;
        for (DWORD i=Len;i--;)
            if (Selected==p[i]) {
                Remove( i );
                InvalidateRect(wnd,0,1);
                Selected=null;
                return;
            }    
    }

    WhiteBlackRect* WBList::Get( POINT a ){
            for(DWORD i=0;i<Len;++i) if ( p[i]->In(a) ) return p[i];
            return null;
    }
    WhiteBlackRect* WBList::Get( LPARAM a ){
        for(DWORD i=0;i<Len;++i) if ( p[i]->In(a) ) return p[i];
        return null;
    }

    VOID WBList::Mes(UINT msg, LPARAM lp){
        static TRACKMOUSEEVENT tme={sizeof tme,TME_LEAVE, 0, 100};

        switch(msg){
        case WM_MOUSELEAVE:
            if ( Moving ) Moving->Leave();
            else if ( Hover ) Hover->Leave();
            in=0;
            return;
        case WM_MOUSEMOVE:
            if (!in) { tme.hwndTrack=wnd; _TrackMouseEvent(&tme); in=1; }
            if ( Moving ) { Moving->SizeMove( lp ); return; }
            if ( Hover ) Hover->MouseMove(lp);
            if ( Hover=Get(lp) ) Hover->MouseMove(lp);
            return;
        case WM_RBUTTONUP:
            if (Selected) Zorder(Selected);
            return;
        case WM_LBUTTONUP:
            if (!Moving) return;
            Moving->LButtonUp(lp); Moving=null; return;
        case WM_LBUTTONDOWN:
            if (Selected) Selected->Invalidate();
            Selected=Get(lp);
            if ( Moving || !(Moving=Get(lp)) ) return;
            Moving->LButtonDown(lp); return;
        }
    }

    VOID WBList::Draw(HDC hdc){
        for (int i=Len;i--;) p[i]->Draw(hdc,Selected);
    }

    VOID WBList::Pos(LONG x, LONG y){
        if ( Selected ) Selected->Pos(x,y);
    }

    VOID WBList::Zorder(WhiteBlackRect* a){
        for (int i=Len;i--;){
            if ( a == p[i] ){ Add( a );    Remove( i ); return; }
        }
    }

     

     

    その他も未完成な感じの部分はいくつかありますがw

    (new で例外が発生した場合どうするかとか、複数の疑似ウインドウが重なった時に、特定の操作でZオーダーが後ろにあたるウインドウをマウスカーソルが離れても黒くならないとか、マルチバッファにしてWM_PAINT内ではBitBltのみにする、そうして拡張ウインドウスタイルWS_EX_COMPOSITEDを使わないようにするとか)

    ただ、ほとんどの未完成部分は、現在の知識内で、後はコードを書けば対処できるのですが

    それ以外の点で気になるのは

     

    DirectXとかだと特定の状況で自動的にやっているらしい、

    「前方に何かがあるとその後ろにあるものについてそこは描画しない」

    という処理です。


    CreateWindowとかで子ウインドウを作るときはWS_CLIPCHILDRENであっさり実現できる、あれです

     

    マルチバッファにして必要が生じたときだけバックバッファを描きかえる、というようにする上で、さらにそれが出来れば、より一層軽量化が可能と思うのですが、自分で疑似的に子ウインドウの一種っぽく見えるものを作る場合、通常それはどのように実現するのでしょうか?

    • 編集済み 七空白 2010年4月11日 12:05
    2010年4月11日 9:56
  • ソースは。。。結構な量があるので見てません。

    全部のウィンドウをなくしてしまうという方向は、環境依存でいろいろ出るので、あんまり現実性はないと思います。ただ、仕組みそのものはドロー系ツールで役に立つと思うので、研究対象としては悪くないと思いますけどね。

     

     

    WS_CLIPCHILDRENであっさり実現できる

    仕組みそのものは、SelectCilpRgnでできます。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月11日 15:57
    2010年4月11日 11:05
  • ありがとうございます。

    >ソースは。。。結構な量があるので見てません。

    大丈夫ですw

    自分自身でも全体把握するのが大変なので、「とりあえず載せておけばいつでも引用できる」という意味が強いですw

     

    新規にコンソールプロジェクトを作成し、そのまま

     

    stdafx.hに

     

    #include <tchar.h>

     

    だけ追加しておいて

     メイン プロジェクト ファイルが

     

    #include "stdafx.h"

    int main(array <System::String ^> ^args){
        System::Console::WriteLine(_T("Hello World" ));
        return 0;
    }

     

     

    のままならOKですが

     

    #include "stdafx.h"

    #include <unordered_map>

    std::tr1::unordered_map< int
    ,int > hoge;


    int
    main(array <System::String ^> ^args){
        System::Console::WriteLine(_T("Hello World" ));
        return 0;
    }


    と2行追加すると依然として58個のエラーが出ました。

    「やはり」ファイルの破損かあるいは、「やはり」EEでは使えないかのいずれかもしれませんw

    う~~む、もし「EEだから」だとするとEEはやっぱり基本ネイティブ向けじゃないってことかなぁ・・・

    (リソースエディタも使えないからコード手打ちか別のソフト使うかだしw

     

    あと、私は今までmainとWinMain以外使ったことありませんでしたが(つまり_tmainは使ったことありませんでした。)

    _tmainでは引数を空にすれば通ったのですが、array<System::String ^> ^argsのままだと以下のエラーと警告が出ました。

     

    ・エラー  fatal error LNK1561: エントリー ポイントを定義しなければなりません。

    ・警告    warning C4829: 関数 main への正しくないパラメータである可能性があります。'int main(array<System::String^>^ argv)' を使用してください 

     

    これは.NETのSystem::StringはもしANSIだとまずいとか「でもwmainで引数がarray<System::String^>^ argvだとエントリーポイントを定義したことにならない」とかそういうことでしょうか?

     

     

     

    なお、リージョンについてですが、こちらも今までほとんど使ったことがなかったのですが、色々適当にいじってたら

    http://www.geocities.jp/shank_long7_02/ForMSDNForums/fff.gif

    ↑こんなことも比較的簡単にできました。結構便利ですね。(HTMLタグ編集画面で2回更新するとなぜか画像が見えなくなるので画像のアドレスをテキストとしてw)

    これはコードとしてはプロシージャの一部を

     

        static HRGN hRgn1(null ) , hRgn2, hRgn3;
        static HBRUSH hbr,old;
        static RECT rect;

        switch (msg){
            case WM_DEFINE: //独自定義整数

                if (hRgn1) return 0;
                hRgn1 = CreateEllipticRgn(10 , 10 , 110 , 110);
                hRgn2 = CreateEllipticRgn(50 , 50 , 150 , 150);
                hRgn3 = CreateEllipticRgn(30 , 30 , 130 , 130);
                hbr = CreateSolidBrush(RGB(0,0,0xFF));
                return 0;
            case WM_DESTROY:
                DeleteObject(hRgn1);

                DeleteObject(hRgn2);

    DeleteObject(hRgn3);
                DeleteObject(hbr);
                break ;
            case WM_PAINT:

                 //この中で色々計算するのは良くない傾向でしょうが、今は実験ようなので適当にw
                hdc=BeginPaint(hw,&ps);
                me->aaaa->Draw(hdc);
                SelectClipRgn(hdc , hRgn1);
                GetClipBox(hdc , &rect);
                Rectangle(hdc , rect.left , rect.top , rect.right , rect.bottom);
                FillRgn(hdc , hRgn2 , (HBRUSH)GetStockObject(BLACK_BRUSH));
                ExtSelectClipRgn(hdc, hRgn2 ,RGN_OR ); //3つ目の引数で多彩に変化可能
                ExtSelectClipRgn(hdc, hRgn3 ,RGN_XOR );
                GetClientRect(hw , &rect);
                old=(HBRUSH)SelectObject(hdc,hbr);
                Rectangle(hdc , rect.left , rect.top , rect.right , rect.bottom);
                SelectObject(hdc,old);
                EndPaint(hw,&ps);
                return 0;

     

    こんな感じにしてあらかじめWM_DEFINEを飛ばしておいた、という感じです。処理内容全般も適当ですw

     

    リージョンはうまく使うと色々できておもしろそうですが

    もうひとつ、リージョンを使わずに

    これをやる前に

    「長方形の描画済み領域への描画を省略」という意味では

    単にWM_PAINT内の me->aaaa->Draw(hdc); で、手前の方から順番に長方形を描くたびに、描画した範囲にValidateRectを使っては どうか?と思ったのですが、それでは描画済みに出来たことになりますか?

     

    また、リージョンとValidateRectの両方で仮に可能だとして、また結果も同じものが得られる場合は

    複雑な形をしている可能性があるリージョンより、通常ValidateRectの方が軽い可能性が高い、と考えて良いでしょうか?

     

    それと、今まで円を書いたことはほとんどなかったのですが、少なくともこのコードだと見てのとおり円がスムーズじゃなくて汚い感じがするのですがw

    画像ソフトとかだとかなり綺麗な円です。

    綺麗に描くには自分で何かスムージングするとかもっと言うとピクセルレベルで自分で計算して描画する関数を作るべきなのでしょうか?

     

    また、上記緑色のMyMouseMovementにて

     

        VOID Framing(HWND hw, LPARAM lp, const RECT& rc){
            cur=0;

            if ( LOWORD(lp) < rc.left + 幅 ) cur|=←;
            else if ( rc.right-幅 < LOWORD(lp) ) cur|=→;

            if ( HIWORD(lp) < rc.top+幅 ) cur|=↑;
            else if ( rc.bottom-幅 < HIWORD(lp) ) cur|=↓;

            SetClassLong( hw,GCL_HCURSOR,(LONG)LoadCursor(0, CurName()) );

        }

     

    とやっていますが、ひとまずこの部分についてはSetClassLongを使ってカーソルを変えるのと、SetCursorではどちらが普通なのでしょうか?

    あるいはSetCursorでは一時的だから、通常はそっちで、サイズ変更中とサイズ変更終了時のみはSetClassLongとかでしょうか?

    • 編集済み 七空白 2010年4月12日 16:27
    2010年4月11日 15:57
  • >とりあえず載せておけば...

    そういってもらえると助かりますw<ただの手抜きじゃん!


    int main(array <System::String ^> ^args)

    こいつは...C++/CLIですね。てっきり全部Nativeオンリーだと思ってた。

    だからと言って、エラーになるというわけじゃないはずなので、とりあえず C++/CLI でも新規にプロジェクトを起こして(TestCppCli)追記してみました。

    #include "stdafx.h"
    #include <unordered_map> //※
    
    std::tr1::unordered_map<int,int>	hoge; //※
    
    using namespace System;
    
    int main(array<System::String ^> ^args)
    {
    	hoge[0] = 1; //※
    
        Console::WriteLine(L"Hello World");
        return 0;
    }
    

    エラー出ませんでした(記述箇所はコメントでマーク入れたところ)。なにかがなにかが壊れているのかもしれません。

     

    ・ValidateRect

    これは、無効化していない領域ですと通知するもので、WM_PAINT の中(BeginPaintを呼び出した時点)で利用しているHDCには影響を与えません。

    やってみるとわかりますが、、、がっつり描画してくれますよw

    WM_PAINT は、頻繁に呼ばれるとちらつきが発生するのと、描画の最適化は、他のリソースをたくさん使ってしまうため、限界があることから、頻繁に呼び出されないようにする工夫がされています。

    その一つが、複雑になりうる更新範囲。これについては、長くなるので割愛。

    ValidateRect は再描画を待たずに描画したので、ここは更新しなくていいです!というための通知用です。

    最終的には、次回WM_PAINTで更新すべき範囲として、常にリージョンに追加削除しながら範囲を管理しています。

     

    ・円が汚い

    これは、いわゆるジャギーがでるのだが。。。というものですね。

    アンチエイリアスをどうするか?ということに収束するのですが、まぁいろいろあるのでまずは調査してみてください。

    ただ、クリップ任せではどうすることもできない部分です。クリップはピクセル単位で描画するエリアを限定させるものですので、描画の途中のエリアをぶった切るもので境界部分ではありませんので。

    円がきれいに描画される...については、これ以上はとりあえず意見を言うのは差し控えます。この先はプロの仕事に影響しますのでwww

    マウス処理は。。。

    1.WNDCLASS で指定されているマウスカーソル

    2.SetCursor で指定したマウスカーソル

    の2つではなく...

    1.WM_SETCURSOR メッセージでデフォルト処理される1を含む何らかのマウスカーソル(場所による)

    2.WM_SETCURSOR メッセージで自分でSetCursor したマウスカーソル

    3.どこかのメッセージでSetCursorしたマウスカーソル

    のいずれかになります。

    マウスカーソルの寿命は、WM_SETCURSOR が呼ばれるか、SetCursorするかのどちらかとなっています。

    通常、ウィンドウのどこかにいるかどうかでマウスカーソルを変更する場合は、

    WM_NCHITTEST でHTCLIENT ならその座標から、カーソルを何にするかをチェックしておき、次に来る WM_SETCURSOR でマウスカーソルをセットするという形で設定します。

    基本的にSetClassLong はやってはいけません。SetWindowLong はまだ外部変数を書き換える程度の意味と同じだからいいですが、SetClassLong は、class 定義を実行時に書き換えてしまうので、そうそう行っていいものではありません。

    たとえ、そのクラスを使うウィンドウが一つしかないという場合でも、OS内部の整合性維持などの都合で莫大な変更コストが伴いますので。

    なので、ある一定条件の場合だけ背景色を変えたいという場合は、WM_ERASEBKGND で背景描画を行うようにすることで、そのウィンドウのその時の状態だけ背景を変えるという形をとります。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月12日 15:37
    2010年4月12日 7:06
  • ありがとうございます♪

     

    >てっきり全部Nativeオンリーだと

     

    プログラミング始めたころは最初C++/CLIのマネージオンリーで

    段々混在型になってきて

    最近はネイティブオンリーです。(ネイティブだけだとコンパイル時間も激減w)

     

    ただし、EEで新規に「コンソールプロジェクト」を作成するとマネージコード込みのプロジェクトになってたものでw

     

    意味的にはtchar.hを入れようが入れまいが、using namespace つかおうがつかわまいが

    結果的に同じになるはずです。

     

    つまり、やはり58個のエラーにより使う事ができませんw

     

    原因はやはり不明、ということで、とりあえず私は今のところ自作のを使っておくことにしますw

    (使える状況でかつそれを超えるのが現れたときに、改良か書き換えを考えます。)

     

    ・ValidateRectについて

    そうでしたかw

    ではとりあえずリージョンと「DDBの」BitBltが切り札ということでw

    重ならない場合も含め、リージョンとInvalidateRect(~,&領域,TRUE); のいずれがより良さそうかは計測する意義がありそうです。

     

     

     

    ・マウスカーソルについて

     

    C++は現実性を求めるための言語、とかいったことをどこかで聞いたことがあるような気がするのでw

     

    例によって計測してみました。

     

    10万回ほどSetCursorあるいはSetClassLongを行った場合の、1回あたりの平均(秒)です。

     

    SetCursor

    0.000004440214073


    SetClassLong

    0.000001625531992

     

    少なくとも、私の環境では単なるこれらの関数呼び出しではSetClassLongの方が「速度的には」勝るようです。

    最適化ありでもさほど変化はありませんでした。

    0.000004497591339
    0.000001569806068

     


    ただし注意点として

    SetClassLongは

    WM_SETCURSOR の (LParam&0xFFFF) が HTCLIENTのときのデフォルトカーソルをそれに変えるというだけ の性質しか、おそらく持っていないようで、この瞬間はカーソル変更を行っていません

    対してSetCursorは呼び出した時にカーソル変更を行います

    そう考えれば、「別の意味で」当然SetCursorの方が時間がかかっている、とも考えられます。

    また、SetClassLong+α(SetCursorを使わずに)のみで、SetCursorとまったく同じような動作が出来る方法を確認できなかったので、そういう意味ではしっかり計測できていないとも取れます。

     

    しかし最大の問題は、 SetCaptureを用いて一時的にウインドウ外でもマウスの動きをキャプチャする場合です。

     

    このとき

    WM_NCHITTEST WM_SETCURSOR 等は発生せず WM_MOUSEMOVE等のマウスメッセージのみ発生する

    ため、制御が難しくなるか、あるいは毎回MOUSEMOVE内でSetCursorをしないといけない可能性があります。

     

    なお、今のところ私が提示した上記長いコード(w)では、ウインドウ外へのサイズ変更・疑似ウインドウ移動も対応しています。(ウインドウ左または上に出るとおかしいときがありますが)

    というより、それが難しかったので若干工数増えたわけですがw

    SetClassLongを一切使わずにSetCapture中のカーソル制御をおこなう方法はあるのでしょうか?

     

    また、これらの結果から考えられることは

     

    WM_SETCURSORを、処理するウインドウメッセージのswitch文の中に書かなかったとき、にDefWindowProcによって「SetCursorに相当する処理」が、クライアント内をマウスが移動する間、毎回行われているかどうか が非常に重要だということです。

    毎回行われているならば確かにSetCaptureが絡まないときはそれを自分で制御する方が、より効率が良くなる可能性があります。

    そこらへんはどうなっているのでしょうか?

     

    なお、これらの結果から今度は対処法を判断すると、実際「マウスカーソルの位置によってマウスカーソルが変化する」ウインドウなど、大抵のアプリなら全体からみてごく少数のウインドウに限られると考えられるので

    専用ウインドウクラスを作って、適切に、かつ最小限に変更回数を制御すれば、おそらくSetClassLongを使ってもさほど問題にはならないのではないかと思います。(カーソルの形状を整数値で保存しているので、それと比較すれば必要な時しか呼ばれません)

     

     

    ・ジャギーとかそれに対する アンチエイリアスとか

     

    やはり、ある意味グラデーションの応用みたいに考えて自分で描画する、ということですね?

    その時の補間の方法がバイリニアだったりバイキュービックだったり自作だったり、という感じで…

    その時に計算範囲を絞るなら複雑な形ならFFTでエッジ部分をだいたい絞っておいて使うか、特定の形状なら自分で絞り込んだりするのでしょうかね?

    (FFTは画像ではまだ使ったことないのでよく知りませんがw)

     

    いずれにしても、それらのことは必要になったらやるかもしれませんが(単に疑似ウインドウ作るってことならよほどのことがない限り不要そうですがw)とりあえずその前にもっと大きな「方向性」をつかみたいです。

     

    そういう計算を行って自分で描画する場合は、WindowsApiだとDIBを使わざるを得ないのでしょうか?

    DDBのみだとかなり速い(BitBltのみなら少なくともDirectX9のDirect3Dよりずっと速いです)

    のですが、他の方法で自分で描画可能でしょうか?

     

     

    ちらつきはWS_EX_COMPOSITEDを使うか(場合によってはバグの巣窟)

    あるいはやはり、自分でマルチバッファやればかなり、あるいは完全に抑えられると思います(XP 32bit SP2での経験上は、です)が

    WM_PAINT外でバックバッファの更新を行うようにマルチバッファをするとして、さらに

    WM_PAINTを抑える必要はあるのでしょうか?

    WM_PAINTは、基本的には自分で無効化領域を作らない限りデフォルトではWM_SIZEで拡大されたときと、別のウインドウに隠れていた等で表示されていなかったところが現れたばあい、くらいしか発生しないはずなので、デフォルト以外では必要時しか呼ばないとなると

    やはりそのWM_ERASEBKGNDというメッセージが絡んでいるという事だと思いますが、これはどういう風に使うという事なのでしょうか?

     

    また

    >WM_PAINT は、頻繁に呼ばれるとちらつきが発生するのと、描画の最適化は、他のリソースをたくさん使ってしまう

    というのは(意味を持たせるとして)最小限のBeginPaintとEndPaintとBitBlt1回(及びそのためのバックバッファ)のみでも、既に裏で想像以上のリソースを使ってしまうことになるのでしょうか?

    2010年4月12日 15:37
  • ・エラー

    Expressだからなのかな?いまはExpressをインストールしている環境がないのでちょっとわかりかねます。。。でもこの辺り変わらないと思うんだよなぁ。。。とりあえず、こちらについてはちゃんと調査するなら改めてスレッドを立ててください。

    ・マウス関係...

    まず、計測対象が間違っています。SetClassLongは、内部的にはWNDCLASS::???? = param; という処理を行うものです。

    当然ですが、それを利用して何かをする(カーソルをセットする、背景を塗りつぶす)という部分は呼び出しタイミングでは行われません。したがって計測データとしては全く役に立ちません。

    マウスカーソルの設定を計測するのであれば、HTCLIENT でWM_SETCURSORが来たときのデフォルトプロシージャの処理時間VS独自にセットした場合の処理時間(こちらもWM_SETCURSORレベル)で比較するのが一番公平です。

    SetCaptureしている場合のマウスメッセージですが。。。SetCaptureすることで、マウスメッセージは常にそのウィンドウの「クライアント領域にある」マウスメッセージとして送られてきます。

    したがって、マウスカーソルが今どこを指しているか?というWM_NCHITESTや、場所に合わせたマウスカーソル設定を行う WM_SETCURSOR などが要求されることはありません。詳しくは、WM_SETCURSOR メッセージのリファレンスなどを読んでもらいたいところです。

    ちなみに、さっきリファレンスを見ていて気が付きましたが、WndClass の部分でのクラスマウスの部分でWM_MOUSEMOVEとなっている個所がありました。これはWM_SETCURSORの間違いです。WM_MOUSEMOVEは要求ではなく通知メッセージですので。

    ・ウィンドウの更新

    ValidateRect については、記述の通り。。。で、切り札が「DDB」によるBitBltか?といわれるとこちらは疑問。そのDDBを生成するコストも考慮してなおであれば、最速の部類にはなります。が今でもそれが本当かどうかははっきり言ってドライバ次第なので、どこにチューニングしてあるかはわかりません。

    一応。。。WindowsGDIが扱うビットマップ形式は、DDB(Device-Dependent Bitmaps)と、DIB(Device-Independent Bitmaps)の2つと、CreateDIBSection API でのみ作成可能な 通称SectionDIBと呼ばれる特別なDDBの3つがあります。

    ビットマップとしてどのように扱うか?にも影響があるので一概にどれがいいとは言えません。また、ハードウェアによって最適化可能なポイントが異なるため、DDBが最速であるというわけでもありません。

    また、Vista ではGDIはソフトウェアですべて処理する形になっているため、DDBという名前でも内部的にはDIBと同義だったりしますし、Win7ではDirect2Dが入るため、再びDDBが意味をなしてはいても最速の座はDirectX11対応の場合はDirect2Dに譲る形になると思います。

    このように、画面描画系は特定環境で早ければ最速とは言えないため、トータルコストでみてどれが「一番無難なのか?」が意味をなしてきます。

    というところをまずは踏まえてもらったうえで...

    描画処理は、非常に単純であり、常に上書きが行われます。

    この上書きの条件が

    1. 背景で塗りつぶして(WM_ERASEBKGND)から、前景描画(WM_PAINT)を行う
    2. WM_ERASEBKGNDは行わないようにして、WM_PAINTだけを行う

    のどちらかになります。

    一応注釈だけ。あくまでもイメージで、現行のOSではちょっとだけ異なる部分がありますが、通常は意識しなくてもいいようになっているのと動作そのものが変わるわけではないので割愛しています。

    具体的には、2のパターンは、InvalidateRect( ..., FALSE )で無効化した(=再描画を要求した)エリアかどうかという違いになります。

    どのくらい違うか?は、背景をどうやって処理してるか次第なので何とも言えません(ちらつきも同様)。この辺りは再描画テクニックの一つなのでじっくりと研究してください。

    あと。。。WM_SIZE はサイズが変わったよーという通知です。サイズ変更は、ユーザーの処理なのでメッセージじゃありません(この考え方は非常に重要なものです。ウィンドウは常に何らかの操作結果を受け取って動く受動的な仕組みの上に成り立っています)。

    ま、それはともかくとして。。。どうしてちらちらして見えるのか?をまず把握してからですね。ちらつき低減を考慮するのは。

    すでに書いていますが、Windowsの画面描画は非常に単純で、常に上書きです。どんなAPIをどのように呼び出しても必ずです。例外は一切ありません(強いて挙げればAlphaは少しだけフィードバックを得ますけど...実質的には上書き)。

    ですので、背景で塗りつぶしてからほかのものを描画というだけで大きくイメージが変わることになり、結果ちらちらする格好に見えます。

    また、WM_PAINTはシステムレベルで最適化されていますので、強制呼び出し(UpdateWindow)をしない限りは適度に呼び出しそのものが抑え込まれています。

     

    ・WM_PAINT

    WM_PAINT自体は、システムレベルで最適化されるので、UpdateWindow API を頻繁に呼び出さない限り、何度もセットされてくることはありません(もちろん再描画の必要と判断された領域がない場合はUpdateWindowを呼び出しても再描画されません)。

    なので、WM_PAINT自体が発生される可能性を減らす努力が必要か?という点では考慮しなくてもいいと思います。

    ただし、描画を行う必要があるという現象が発生した時点で、即座に描画するのか(WM_PAINTだけが描画ではない、その場でGetDCすればいくらでも描画可能)、描画範囲を指定して InvalidateRectをし、しかるべきタイミングを待つのか?では当然ながら、ユーザーの目に変化が表れるまでの時間は変わります(前に書いたレスポンスタイム)。

     

    ・リソース

    OSからみれば、そこでプログラムが動いていることそのものが想像以上のリソースなので、少しくらい増えた減ったは案あり意味はないですよ。

    ただし、最適化というのは単に速くすることではありません。ここは非常に重要なことなのできちんと書いておきます。

    速度を向上する(パフォーマンス、レスポンス、レイテンシ)は確かに最適化としてはよくある事例の一つですが

    もうひとつ重要な最適化の一つにサイズの低減があります。

    実行ファイルサイズ、データサイズ(圧縮するなど)、メモリの使用量そのものの低減などなどこれもまた最適化の一つです。

    最適化とは、何らかの犠牲により、何らかの向上を図ることを言います。

    犠牲とするものが何か?それによって何が向上できるのか?は一意ではありませんので、なんとも言えません。もしかしたらやらなくてもいいことかもしれません。

    ですので、何を犠牲にして何を向上したいのか?を明確にしない限り、

    >BitBlt1回(及びそのためのバックバッファ)のみでも、既に裏で想像以上のリソースを使ってしまうことになるのでしょうか?

    これは想像以上に無駄なリソースを用意しているともいえるし、それによって劇的な速度向上が行えるので想像の範囲内でのリソース利用であるともいえます。

    これだけでは何をやっているか(1回にしたことでどんなメリットが出るのか)がわからないので無駄なのか効率的なのかはわかりません。

    速ければいいのが、無駄に10メガのメモリを消費しても、計測結果でわずかに速くなればいいのなら、必要なメモリでしょう。

    ですが、10メガもメモリを使って、違いがわからない程度の速度向上では意味がないといえば、無駄なメモリです。

    それはこれだけでは測ることはできませんので何とも言えません。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月13日 10:13
    2010年4月13日 7:39
  • ありがとうございます。

    >SetClassLongは、内部的にはWNDCLASS::???? = param; という処理を行うものです。

    やはりそうですよね?

     

    >したがって計測データとしては全く役に立ちません。

    「これだけの」比較では「まだ以下の問題が未解決なので」、意味がありませんが、だからといって計測データが無意味という事ではありません。

    重要なのはトータルでのコストなので、SetClassLongによって「実際に余計に時間がかかっていた」としても1回につき1msにも満たない環境は、確実に存在した、ということです。つまり必要時しか変更しない場合は「SetClassLongを使うという方法のが実質的に最も遅かったとしても」その程度のコストの差しか存在しないという事です。

     

    >デフォルトプロシージャの処理時間VS独自にセットした場合の処理時間

    自分で整理して書いた後、全く同じことを考えましたが、「その処理だけ」についてのデフォルトプロシージャの処理時間を拾う完全な方法が思いつきませんでした。

    そこまでこだわる必要がないような気がしてきましたが、もし簡単に計測できるならば少し気になる感じもします。

    何かいい方法はないでしょうか?

     

    >SetCaptureすることで、マウスメッセージは常にそのウィンドウの「クライアント領域にある」マウスメッセージとして送られてきます。

    それが確実であればより安心できます。

     

    >WM_NCHITESTや、場所に合わせたマウスカーソル設定を行う WM_SETCURSOR などが要求されることはありません。

    つまり結果的には、WM_MOUSEMOVEなどでSetCursorし続けるか、やはりSetClassLongのGCL_HCURSORで変更するかだと思いますが

     

    逆に、クライアント領域を移動中ってことになってる場合の「デフォルトプロシージャの処理時間VS独自にセットした場合の処理時間」がほぼ互角ならば、どっちにしても比較さえ作れば極些細な差ではないかと。(SetCapture中はマウスカーソルの変更は起きないように組むので、その比較はほんのちょっとのコストで可能です。)

     

     

    >DDB(Device-Dependent Bitmaps)とDIB(Device-Independent Bitmaps)の2つと、CreateDIBSection API でのみ作成可能な 通称SectionDIBと呼ばれる特別なDDBの3つがあります。

     

    なるほど、その3つですね♪

     

    >Vista ではGDIはソフトウェアですべて処理する形になっているため、DDBという名前でも内部的にはDIBと同義

    なるほど、Vistaの描画が重く感じるのはハードウェア側で制御してた部分をソフトウェアで・・・というイメージまでは聞いていましたが、具体的にはDDBもソフトウェア側で処理することになってたのですか!

    Direct2Dが速いというのは聞いていましたが、DDBはXPと比較して遅いのでしょうか?あれより速いとなるとすごいですがw DirectXの描画はメモリ使用量が気になりますがw (そこが改善されてればXPのDDBより多少遅くても個人的にはOKw)

    その辺は使えるようになったら即調べてみようと思います。

     

    >どうしてちらちらして見えるのか

    >その場でGetDCすればいくらでも描画可能

     

    これらがわからなかったらマルチバッファという単語は出てこないかとw

    CPU・メモリ・速度を見ながらだいぶ前から色々と研究していました。

    問題があるとすればXP以外では試せていないという事です。(その点は結構重要)

     

    ただ、概念的にWS_EX_COMPOSITEDをつかわないとしても

    とりあえず背景再描画を何らかの方法でオフにしてやれば「必ず」ちらつき自体は消えるはずです。

    また、それによって以前の描画内容の残像が残らないように組んでさえいれば、パフォーマンスの面でも(WM_PAINTが主体な場合だとしたらレスポンスということになるかもしれませんが)常に背景を消さないようにする方がより良いと思います。

     

    WM_PAINTでの負担を最小限にするために別のHDCを用意してそこに描画してから

    InvalidateRect( ..., FALSE )というのは良く使っていますが、WM_ERASEBKGNDをいじるというのは、つまりそれと同じ違いが出るということですね?


    自分で背景を塗りつぶすならウインドウクラスの背景指定はNULLにすればいいですし、逆に自分でHDCに対して塗りつぶし命令を直に書くなり、背景再描画命令を出すなりを完全にせず、背景を全く塗りつぶさなければ、以前の描画内容が残るのを防ぐことはできないはずです。

     

    >想像以上に無駄なリソース

    曖昧でしたねw

    ではかなり具体的にします。

    どこかで使うHDCとHBITMAP、のハンドルの指す中身

    あるいはWM_PAINT内で使うPAINTSTRUCTのなどのリソース(?)に限定して言うと「Windows タスク マネージャの、プロセスのメモリ使用量」では分からない「何か」を消耗している可能性はあるのでしょうか?

    これはおおよその数値でしょうがCPU使用量も分かるのでかなり頻繁にチェックしています。

    これを見てるだけだとそれらは気にするほどのことではないという感じがするのですが…

    むしろこれ見てる限り、少なくともXPにおいて

    ウインドウを非表示にするときはウインドウのクライアント領域のサイズを0*0にする、とかやってた方が俄然メモリ使用量が減る可能性が高いように思います。

    (Windows7ではそこのところ改良されたんでしたっけ?)

     

    あと、実際プログラミングするうえで、WM_~の「メッセージか要求」が、どういう性質を持ってて、どういう場合その処理のなかに入るかは知っているとして、そのうえでそれが「メッセージ」であるか「要求」であるかは、正確に分けて考える必要はあるのでしょうか?

    あらゆるものはプログラマーが「何も」書かない限りは発生しないので(デフォルトの「要求」(?)も、ウインドウがなければそもそも存在しない)ある意味全部受動的だと思うのですが

    何かまだ裏があるのでしょうか?

    2010年4月13日 10:13
  • とりあえず。。。一度仕切り直しが必要かな。。。

     

    ちょっと話題がばらけ過ぎましたが、ここまでのやり取りで率直に感じたのは、「Windows のメッセージシステムをちゃんと理解できていない?」です。

    まずは、正しい Windows の動く仕組み(特にメッセージのもつ意味とその作用)を学ぶ必要があると感じました。

    今持っている知識は全般的なプログラミング技術が大半で、Windows特有の技術にはそれほど詳しくない?と感じました。

    私の単なる勘違いかもしれません。そうであれば私の理解力が足りないだけなので申し訳ないです。

    時間が許すのであれば、大学の図書館とかで借りてで構わないので、まずは Programming Windows(C#やVB用のではない) あたりを読破してみることをお勧めします。

    ちなみに。。。C#やVB用のものも出ていますが、こちらは .NET Framework の場合用で書かれてあり、論点から異なるため、今のタイミングで必要な情報源としては意味をもちませんので注意してください。

    Programming Windowsの第5版あるいはそれ以前(5版より後のものは、.NET 向けしかないため意味をなさない)のものを読むようにしてください。

    もしすでに読んだことがあるというのであれば、今一度読み直してみる(コードは読み飛ばしてもかまわない)ことをお勧めします。

    この本ならWindowsの本質をつかさどるメッセージの流れもそれとなく気を使って書いていますし、どういうタイミングでどういうメッセージが来てどういう処理を行うべきなのか?ということも基礎的な部分は一通り網羅しています。

    おそらくは大半が知ってることだと思います。ですが、気が付かないでいたところや本当はそうなのか!という部分は少なからず絶対にありますので、まずは読破してみてください。

     

    ・HDC

    数字として表れる程度まで豪勢には使ってないと思います。ですが、HDC を一つ構築するだけでUSERやGDIのハンドル数が変わります。ただし、短命(メッセージ処理を終えるタイミングで解放される)な場合はタスクマネージャに数字として出てこないと思います(1秒程度の間隔で自己更新するため)。

    デバイスコンテキストとしてユニークに保持する情報としては...SelectObject で渡すユニークなGDIリソース種別ごとのエリア、SetBkModeなど、HDCを引数として受け取るSet/GetできるAPIごとのデータとなります。

    どれくらいあるのかは数えたことがないのでわかりませんが、100バイト以上は余裕であると思いますよ(100バイトを微々たるものと見るか、貴重なシステム共有リソースとみなすかは人それぞれなので言及しません)。

    ・HBITMAPなど

    GDIリソースは、GetObject で取得できる情報+データとしてそれを実現するための情報で構成されています。具体的に何バイト使ってるのかわかりません。ビットマップであればBITMAP構造体+ビットイメージを格納するだけの連続したメモリイメージ+その他制御に必要な情報程度は保持しているでしょう。それがタスクマネージャに出てくるかどうかはわかりませんが。

    ほかにも、HWND なら、WNDCLASSへの参照(ATOM値)+ウィンドウ状態管理情報(情報量不明)はもっているでしょう。さらに、更新領域情報(HRGNとしてもっていると思われる)も随時変化しながら保持していると考えられます。

     

    >ウインドウを非表示にするときはウインドウのクライアント領域のサイズを0*0にする、とかやってた方が俄然メモリ使用量が減る可能性が高いように思います。

    えーっと。。。思いっきり勘違いしてます。ウィンドウを最小化したりした場合に、メモリをスワップアウトする仕組みが発動します。

    詳しくは、NyaRuRuさんの日記(http://d.hatena.ne.jp/NyaRuRu/)にあるのですが、あまりにも古すぎて探せなかったw

    ま、他にも色々と有用な情報が載ってるので、暇を見てチェックしてみることをお勧めします。

    >あらゆるものはプログラマーが「何も」書かない限りは発生しないので(デフォルトの「要求」(?)も、ウインドウがなければそもそも存在しない)ある意味全部受動的だと思うのですが

    違います。

    ウィンドウを作成した場合は、それがどういうウィンドウであれ、要求はOSサイドからやってきます。プログラマが何もしない場合はデフォルトプロシージャがそれを肩代わりしてくれることで、何もしていないように見せかけているだけです。

    裏ばかり見ようとせず、まずは正しく表を見てください。そこにあるべきものあるはずのものが見えていない気がします。

    識者に頼るという勉強のスタイルもあります。がそれはあくまでも対面で質問ができるような場合であり、そうではない場合ある程度以上のレベルで自己研鑽が必須となります。扱う内容が複雑になればなるほど、そこで要求される知識レベルも高くなります。

    が、それ以上にしっかりとした基礎知識が必要となります。この部分はこういったやり取りの場では、全部すっ飛ばしていることが多く、それゆえにたがいに通じない(現にいくつかは通じていないと私は感じました)ということが起こります。

    たがいに通じない部分があるのは仕方ないとは思いますが、もう一歩突っ込んだ世界で花開いてほしいと思えばこその苦言でもあるので、まぁ親父のたわごとと思って、もう少し勉強してみてください。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月14日 15:19
    2010年4月13日 13:33
  • ちょっと良く分からないのでこれらだけ明確にしておきたいのですが……

     

    スワップアウトするとタスクマネージャに「表示される」メモリ使用量は、「スワップアウトするのならばメモリそのものでは一時的になくなるために」実際に潜在的にメモリを使用する量が減ったわけではないのに減るのですか?(変化しないときもありましたが、変化したときもありますし、確かにアプリを非表示状態にして置いてしばらくしてからタスクマネージャをみると「表示上」減ってはいますが…)

    メモリマップドファイルだと作っただけでは変わらず、そこから読み込んだ範囲分までは、読み込んだときに表示上のメモリ使用量が「増え」ました。それは勝手に減るという事はなかったと思ったのですが

    つまり、ここから考えるとこれはスワップアウトとは少し挙動が違うのでしょうか?

     

    ウインドウの持つ情報量はウインドウの大きさには依存しないのでしょうか?(内部でビットマップとして扱われている部分はそうでもないと思うのですが…)

     

    OSの「通常気にする必要のない」挙動は基礎知識とは思えません

    となると問題はメッセージの流れでしょうが、メッセージの流れも一定ではない(環境により異なる場合がある)のですよね?

    では具体的にはどういう部分のことをおっしゃっているのでしょうか?

     

    >100バイト以上

    大規模アプリ、ということなので、1ウインドウに1つなら数キロバイトでも微々たるものと見ます。その程度の差だと音声ファイルの読み込み(音声再生時のマルチバッファの時のバッファのブロックサイズ)をちょっといじるとかでもすぐに覆ります。

     

    DirectX9の3Dを使って描画すると、数MBとか普通に行ったり、マネージのウインドウ込みだとそれだけで10MB以上くらいだったか食うのとかと比べれば、些細な問題だと思います。

     

    >・HBITMAPなど

    大よそイメージ通りです。やはりハンドルの指す中身は知る必要はないし、だからと言ってタスクマネージャのメモリ使用量では判断できない点はあるかもしれない、ということですね

     

    >ウィンドウを作成した場合は、それがどういうウィンドウであれ、要求はOSサイドからやってきます。

     

    どの部分が違うのでしょうか?

    やはり私が書いたことは間違っていないと思うのですが…

    私はあくまで 「何も書かなければ」 「ウインドウがなければそもそも存在しない」 と書いたのであって

    その「ウインドウを作成する」のはプログラマの行動です。

    ウインドウを作成するということは「何かを書いた」ことになるので、「何かを書く」場合にOSがそれに対して何か要求してくる、ということがあればそれに対応できる準備はしておかねばなりません。

     

    その、必要な処理(それがOSサイドから来る要求というのならそう分類することにして)が生じる為に、それが自分が関与しないことはデフォルトプロシージャに「委託するのはプログラマの行動」です。

    それは、そうしないとおかしなことになるから「プログラマが委託している」のです。

    ゆえに、プログラマが主体であるのでデフォルトに任せず自分が要求であれメッセージであれコントロールするならば、どういう状況でそれが起き、その中で何をすべきか、その後の流れをせき止めるとまずいのかどうかを分かっていれば要求とメッセージという分類はしなくても問題ないのではないかというのが私の考えです。

     

    [追記(4/14 AM02:11)]

    Programming Windowsで検索かけてみておそらく日本語版に当たるのを、PDFで少しだけ見れることが分かったので少し見てみましたが

     

    http://ascii.asciimw.jp/books/books/detail/4-7561-3601-X.shtml

     

    2010年4月4日 0:55 のとっちゃんさんのレスに

    WM_TIMER、WM_DESTROY の二つについては、DefWindowProc も呼び出すようにした方がいいです。

    とありますが、PDFをみると、これは見たところMDIではあるものの

    WM_DESTROYでは必ずreturn 0;としているように見えますし、WM_TIMERでもデフォルトに流す分岐は見当たりませんでした。

    あの時点までで私が見たサンプルはやはりこれと同じように単にreturn 0;になっていたのですが

    これはあくまで「例外的な状況を深読みしていない、あるいは冗長になるのを防ぐためにそうしている」ということですか?

    2010年4月13日 14:24
  • 私はあくまで 「何も書かなければ」 「ウインドウがなければそもそも存在しない」 と書いたのであって
    その「ウインドウを作成する」のはプログラマの行動です。

    そうですか。。。私はウィンドウがなければなんてことは一言も触れられていないと思っていましたが、ここの時点で齟齬があったようです。まさか、ウィンドウがない状態のことを記述しているとは思いもしませんでした。思い込みが過ぎたようです。申し訳ありません。

     

    「プログラマの行動」で「ウィンドウを作成する」ことができるウィンドウのお話をしていたのですか。大変申し訳ないのですが私が想定しているウィンドウとは別のもののようです。

    勉強不足で申し訳ありませんが、これ以上なにかを伝えることは私には無理です。識者の登場をお待ちいただくか、新たにスレッドを起こすなどして、話題にしていただければと思います。

     

     

     

     

     

     

     

     

     

     

    どこかにカチンとくるものがあったともいます。ひとえに私の文才のなさが原因だと思います。それについては本当に申し訳ないと思います。

    質問されっぱなしで、答えないことについても申し訳ないと思いますが、気力がそがれました。暇人じゃないのでこれ以上付き合うことは私にはできません。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク 七空白 2010年4月14日 15:21
    2010年4月14日 8:53
  • いえ、私も気力をそぐだけの書き方はしたと思います。

    申し訳ありません。

    …と言うのが普通だと思いますし、実際そういう気分でもあるのですが

    ここは敢えて「とっちゃんさんがそれほど卑屈になるようなことはないです」と私は書きます。

     

    なぜならば、ある文章は「その内容が通用する状況について汎用性を求めた」場合は、当然その文章は抽象性が増すか、逆に状況をかなり限定する必要が生じるために読みにくくなる場合があり、私の文章はそもそも読みづらい場合が多々あるからです。

     

    C++的に言うとテンプレート使いまくりだと読みづらいし、カリカリに最適化しすぎるとやはり読みづらいというのと同じことでしょう。

     

    しかしこの場であえてそういうスタイルをとるのは、重要なところではなるべく誤解が生じる余地が少ない方が良いからです。

    しかしそれは逆に、技術的なそういう話の中に、突如として精神論的な話が入ると、一瞬「?」となり、あるいはぱっと見だと簡単に誤解が生じる可能性が生じます。

    そんな中、暇じゃない忙しい日常、色んなコードを書くような日々を過ごしていて疲れていたら

    全ての文章を意味を吟味しながら精読…というのはそもそも酷な話です。

     

    それどころか

    暇人じゃないのにこれだけ辛抱強く問答にお付き合いいただいたことは、多大なる感謝をしています。

    少なくとも、今の私は自分だけでも大変なので、もし仮に「自分だけが」知っていたことがあったとしても、そういう事はなかなかできません。その点、尊敬に値します。

     

    そして実際2週間前と比べてどうよ?と思ったとき、かなり知識のレベルが上がっているのは紛れもない事実です。

     

    続けて知りたいこともたくさんあるのですが

     

    自力で調べなければならない(か、その方がずっと速い)ようなことも一辺に増えましたので、ここで再び自分自身の単独の精錬の時間に戻りたいと思いますw

    それから、その調査によって調べたら、おそらく自作アプリの多数の 改善点が明らかになるので、それを改善する時間とか、新しい部分を作りこんでみる時間も必要ですw

    さらに、今日は時間的に本に触れるチャンスはありませんでしたが、Programming Windowsという指標も与えていただきましたので、明日あたり探しに行ってみて、見つかったらついでに読破してみましょうw

    なんもやってなかったブランクを除けば、私はプログラミング歴はまだ総計1年程度しかないので、特定の分野にマニアック、というわけではない本であっても、なにも吸収できることがないなど、まずあり得ないはずです。

    知らないことが沢山あるという事はそれだけ分「思ってたよりもはるかに楽にいろんなことができる可能性が沢山眠っているかもしれない」という事です。

     

     

    あ、そういえばUSERオブジェクトやGDIオブジェクトについて、ですが、あの時点までで私はそんな概念もあったのか

    ということ自体を知りませんでしたので、非常に助かりましたw

     

    これがまたタイムリーになかなか適した感じのマーク・ルシノビッチ氏の記事を見つけまして…w

    http://technet.microsoft.com/ja-jp/windows/ff606443.aspx

     

    調べてみたところ、私のアプリは今のところ心配する事はない数値のようですが、もしこれを知らずにどんどん作りこんでいってたら、もしかしたら原因が分からずにじわじわと死亡していた、かもしれませんw

     

    (なお、とっちゃんさんはご存じの可能性が高いと思いますが、誰かが見て役に立つかもしれないので調べてみたことを書いておくと、Windows タスクマネージャーでも、表示→列の選択で、USERオブジェクトやGDIオブジェクトを選択し、プロセスタブを選択すれば確認出来るようです(少なくともXPの場合))

     

    危険を回避できたというだけではなく、むしろこれはこれで、知ることによって改善できるアイデアが次々と出てきます。

     

    第2部はどんなもんだろう・・・周期見ると2週間後くらいかな?

    ということで予習(?)

    http://support.microsoft.com/kb/838283/ja

     

    うむ、うまい事やれば

    マルチバッファを速度的にもメモリ的にもCPU的にも軽~いコストでしながら、かつUSERオブジェクトやGDIオブジェクトの数を低く低く抑える・・・かといって可読性はやはり十分ほしい。

    そんな要求もかなり高いレベルで満たせるかな。

    これはおもしろそうですw

     

     

    と、いうわけで

    都合があるでしょうから答えてくださいとは言えませんが、もし今後質問を立てたときに回答いただければ、いつでも歓迎ですw

    特に、思わず「なるほど!」と思うようなしびれる突っ込みならばむしろ感激するでしょう。

    ただ、全く強制力はありません。とりあえず、自由な時間は自分が最もやりたいことをやってくださいw

    それが究極。

     

    未解決問題については知ることができればベストですが、いずれも今すぐ分からないと「今はっきり困る」、というものではないので、では一端今回はこの辺を意味上の区切りとしましょう。

     

    それにしても、今回はどうもありがとうございました♪ 今後もし気が向きましたらよろしくお願い致しますw

    とりあえず、感謝と敬意の意味で、回答マークと投票をコンプリート状態(?)にしておきますw

    2010年4月14日 15:18