#pragma twice

KAB-studio > プログラミング > #pragma twice > 196 Version 10.18 構造体の構造

#pragma twice 196 Version 10.18 構造体の構造

前のページへ 表紙・目次へ 次のページへ

 Version 10.18
構造体の構造

さて、今回はちょっと脇道にそれて、構造体について見ていきます
前回の LPNMHDR と NM_TREEVIEW の話ね
そう。ただ、構造体というよりはポインタの話かも
どういうこと?
それを説明するのが一番大変かも。まず結論から言うと

            NM_TREEVIEW* pstNmTreeView = (NM_TREEVIEW*)p_lParam;

ってできる場合には

            NMHDR *pNmhdr = (NMHDR*)p_lParam;

ってすることができます
それが分かんないのよねー。なんか全然違う気がするんだけど
そこがポインタの難しさかな。さて、このテストのためにテスト用関数を
用意してください。 Version 4.01 ( No.051 ) を参考にして
テスト関数ねー、ほいほい。でもなんで?
テストは MFC 使った方が簡単だから。とりあえず

void StructTest()
{

}

って感じに用意して、ボタンを押したらこれが呼ばれるようにして
ほい
さて、まずは確認から

void StructTest()
{
    NM_TREEVIEW stNmTreeView;
    stNmTreeView.hdr.hwndFrom = 0;
    stNmTreeView.hdr.idFrom = 100;
    stNmTreeView.hdr.code = TVN_SELCHANGED;
    TRACE
        ( "%d, %d, %X\n"
        , stNmTreeView.hdr.hwndFrom
        , stNmTreeView.hdr.idFrom
        , stNmTreeView.hdr.code
        );
    // 0, 100, FFFFFE6E

    NM_TREEVIEW* pstNmTreeView = &stNmTreeView;
    TRACE
        ( "%d, %d, %X\n"
        , pstNmTreeView->hdr.hwndFrom
        , pstNmTreeView->hdr.idFrom
        , pstNmTreeView->hdr.code
        );
    // 0, 100, FFFFFE6E
}

うお、いきなり長い!
って程でもないと思うよ。まず、ポインタじゃない普通の NM_TREEVIEW 
型変数を作ってその中の hdr の中に適当に値を入れます
 hdr って NMHDR のだよね
そう。 hwndFrom は送り元コントロールのハンドル、 idFrom はその 
ID 、 code は通知メッセージ
これって適当でいいの?
うん、テストだけだからね。で、それを TRACE() で出力して確認
 FFFFFE6E が TVN_SELCHANGED なんだね
次に、これをそのまま NM_TREEVIEW のポインタに入れます
ポインタにはアドレスが入って、本体を指し示す、だよね
そう、 Version 4.10 ( No.060 ) をよく読み返して、イメージを掴んで
おいてね
ってことは、 stNmTreeView がメモリ上に置いてあって、その一番最初の
アドレスが &stNmTreeView で取得できて、それをポインタの 
pstNmTreeView に入れてる、ってことだよね
そういうこと。そうすれば、 pstNmTreeView は stNmTreeView と同じよ
うに使えるから。そのためのテストで、さっきと同じように TRACE() で出
力しています
ポインタの時は . じゃなくて -> ね。そのあとのが . ってのがまだよく
わかんない……
 pstNmTreeView->hdr の型は?
さっき言ったじゃん、 NMHDR でしょ
それはポインタ?
違う……だから . ってことなんだよね
そういうこと
それはなんとなく分かるんだけどね……
さて、では一気に核心を突いてみます

void StructTest()
{
    NM_TREEVIEW stNmTreeView;
    TRACE( "%X\n", &stNmTreeView );
    TRACE( "%X\n", &( stNmTreeView.hdr ) );
    // 12F5F8
    // 12F5F8

    NM_TREEVIEW* pstNmTreeView = &stNmTreeView;
    TRACE( "%X\n", pstNmTreeView );
    TRACE( "%X\n", &( pstNmTreeView->hdr ) );
    // 12F5F8
    // 12F5F8
}

えっと、まず stNmTreeView のアドレス、次に stNmTreeView.hdr のアド
レス……って、同じなの!??
そう、そこがポイント。 stNmTreeView は、メモリ上ではこういうふうに
置いてあります

[ NMHDR ][ UINT ][ TVITEM ][ TVITEM ][ POINT ]

前回出てきたのと同じだね。あ、最初に NMHDR がある……そっか!
わかった?
 &stNmTreeView は stNmTreeView の先頭アドレス。 
&( stNmTreeView.hdr ) は stNmTreeView.hdr の先頭アドレス。でも 
stNmTreeView.hdr は stNmTreeView の最初にあるから……同じ!
そういうこと。構造体の中に入っている変数は構造体の頭から入っている
んです
最初の所に余分なスペースはないってことね
最初の所にはね

パディングって言って後ろの方に入っていることはあるんだけど、それは
また今度の機会に置いておいて。で、後半のポインタの方を見てもらうと分
かるんだけど、それはポインタの時も同じ
 &stNmTreeView と pstNmTreeView が同じなのは当然よね、 = で渡して
るんだから。でも &( stNmTreeView.hdr ) と &( pstNmTreeView->hdr ) 
って難しいかも
これもさっきと同じ話。 pstNmTreeView->hdr の時点でもう
 stNmTreeView.hdr と同じってこと?
そういうこと。 pstNmTreeView がポインタ、ってことよりも、 
pstNmTreeView->hdr がなんなのか、っていう方が大事だから
なんとなくわかったかも……なんとなくだけど
さて、では最終段階

void StructTest()
{
    NM_TREEVIEW stNmTreeView;
    stNmTreeView.hdr.hwndFrom = 0;
    stNmTreeView.hdr.idFrom = 100;
    stNmTreeView.hdr.code = TVN_SELCHANGED;
    TRACE
        ( "%d, %d, %X\n"
        , stNmTreeView.hdr.hwndFrom
        , stNmTreeView.hdr.idFrom
        , stNmTreeView.hdr.code
        );
    // 0, 100, FFFFFE6E

    NMHDR* pstNmHdr = (NMHDR*)&stNmTreeView;
    TRACE
        ( "%d, %d, %X\n"
        , pstNmHdr->hwndFrom
        , pstNmHdr->idFrom
        , pstNmHdr->code
        );
    // 0, 100, FFFFFE6E
}

ってことができる理由よね
そう、それは次を見ればわかるかな

void StructTest()
{
    NM_TREEVIEW stNmTreeView;
    TRACE( "%X\n", &stNmTreeView );
    TRACE( "%X\n", &( stNmTreeView.hdr ) );
    // 12F5F8
    // 12F5F8

    NMHDR* pstNmHdr = (NMHDR*)&stNmTreeView;
    TRACE( "%X\n", pstNmHdr );
    // 12F5F8
}

……いや、なんてゆーか、ただアドレスを見ただけだし、キャストしたか
ら入ってるだけだし
まぁそうなんだけど。これは

[ NMHDR ][ UINT ][ TVITEM ][ TVITEM ][ POINT ]



[ NMHDR ]

って見てるってこと
あー、つまり後ろは見ないってことね
そういうこと。 NM_TREEVIEW の最初に NMHDR が入ってるんだから、
NM_TREEVIEW のアドレスを NMHDR のアドレスとして使っても問題ないって
こと
だからさっきみたいなことができたのね
重要なのは、これは〈みなしてる〉ってだけだから
どういうこと?
たとえばね、こういうこともできるんです

void StructTest()
{
    NM_TREEVIEW stNmTreeView;
    RECT* pstRect = (RECT*)&stNmTreeView;
}

 RECT ……って、四角の角が入ってる構造体だよね。……それって全然違
くない?
違います。でも、こういうこともできるんです。この場合、 NM_TREEVIEW 
の最初の部分を RECT 構造体と〈みなして〉扱うんです
こ、怖い……
逆に言うと、 NM_TREEVIEW の最初の部分を NMHDR と〈みなして〉操作す
ることも
あ……そっか、今の見ると NM_TREEVIEW の最初に NMHDR が入ってるから
そう〈みなして〉操作しても大丈夫ってわかるけど、違ったらまずいんだ
そういうこと。本当のことを言うと、こういうのは行儀が悪いからやっ
ちゃダメ。 API がそういうことしてるから仕方なくしてるってだけ
そうなんだ
そもそも、 LPARAM を NMHDR とかにキャストすること自体危険だし
そういえばそうだね……

/*
    Preview Next Story!
*/
〈みなす〉ってなんかすごいよね
こういうのはあまり他にはないからね
これはこれで面白いかも。ちょっと複雑だけど
複雑な所も面白い
とは思えない……
う”
というわけで次回
< Version 10.19 ツールバーを作ってみよう >
につづく!
っつーかなんかまだ認めたくない
むむむ?
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。