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 ツールバーを作ってみよう >
「につづく!」
『っつーかなんかまだ認めたくない』
「むむむ?」