#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のトップページをご覧ください。