PostMessage での引数の受け渡しについて

[戻る]
かれー 2010/05/10(月) 15:23:37
お世話になっております。
PostMessage での引数受け渡しについて教えてください。

引数受け渡しについて学習中で、試しに
ツリービュー変更時のイベントで文字列を抜き出して取得する
と言うプログラムをPostMessageを使って作成しています。

------------------
// ツリービューのクラス
void CDirTree::OnTvnSelchanged(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
    CString sText = GetItemText( pNMTreeView -> itemNew.hItem );
    CString *pStr = new CString( sText );

    this->GetParent()->PostMessageW(WM_CHANGETREE, 0, (LPARAM)pStr);

    *pResult = 0;
}
------------------
// 親
LRESULT CxxxxView::ChangedTree(WPARAM wParam, LPARAM lParam)
{
    CString* pTemp = (CString*)lParam;
    CString sGetText(*pTemp);

    return 0;
}

上記のコードで、sGetText に文字列を正常に渡せています。

ただ、次のコードのように構造体を引数として渡してから
文字列を取得しようとすると sGetText に文字列が取得できません。

------------------
// ツリービューのクラス
void CDirTree::OnTvnSelchanged(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
    LPNMTREEVIEW* pTemp = new LPNMTREEVIEW(pNMTreeView);

    this->GetParent()->PostMessageW(WM_CHANGETREE, 0, (LPARAM)pTemp);

    *pResult = 0;
}
----------
// 親
LRESULT CxxxxView::ChangedTree(WPARAM wParam, LPARAM lParam)
{
    LPNMTREEVIEW* pTemp = (LPNMTREEVIEW*)lParam;
    LPNMTREEVIEW lpTree(*pTemp);

    CString sGetText = m_Tree.GetItemText( lpTree -> itemNew.hItem );

    return 0;
}


引数の渡し方に問題があると思うのですが、CString と同じように渡すのでは駄目なので
しょうか?

maru 2010/05/10(月) 16:10:47
LPNMTREEVIEWはNMTREEVIEW構造体へのポインタです。
>     LPNMTREEVIEW* pTemp = new LPNMTREEVIEW(pNMTreeView);
は構造体へのポインタを複製していて、構造体そのものを複製してはいません。
元の構造体そのものはOnTvnSelchanged()が終了した後は有効ではないので、
メッセージを受け取った側では使用することは出来ません。

ここまで書けば、どうすべきかは分かると思うので、答えは書きません。

仲澤@失業者 2010/05/10(月) 16:30:28
maruさんの発言を尊重して、その件は置いておくとして、
別の問題。

newしたオブジェクトはdeleteしなきゃいけないのですが、
その機会はnewを実行しているスコープの寿命内でやらなきゃなりません。
となるとreturnの前となりますが、PostMessage()はメッセージキューに
メッセージを置いてくるだけ(受信はされない)なので

1.pTempをnewした(pTempは有効)
2.pTempをPostMessage()した(pTempは有効)
3.WM_CHANGETREEメッセージがメインのメッセージキューの最後に
  追加された(pTempは有効)
4.pTempをdeleteした(pTempの中身は無効)
5.returnして関数が終了した(pTempの中身は無効)
6.メインに制御が移った(pTempの中身は無効)
7.メッセージキューからWM_CHANGETREEを受信した(pTempの中身は無効)

になっちゃいますよね。

つまり、こういったケースではそもそもPostMessage()は使えないのです。
このような場合はSendMessage()を使います。

かれー 2010/05/10(月) 16:48:29
お二方、ありがとうございます。

>maruさん
なるほど、LPNMTREEVIEW は構造体へのポインタだったんですね
以下のようにして、無事解決することが出来ました。

// ツリービューのクラス
void CDirTree::OnTvnSelchanged(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
    NMTREEVIEW* pTemp = new NMTREEVIEW(*pNMTreeView);

    this->GetParent()->SendMessageW(WM_CHANGETREE, 0, (LPARAM)pTemp);
    delete pTemp;

    *pResult = 0;
}
----------
// 親
LRESULT CxxxxView::ChangedTree(WPARAM wParam, LPARAM lParam)
{
    NMTREEVIEW* pTemp = (NMTREEVIEW*)lParam;
    NMTREEVIEW nmTree(*pTemp);

    CString sGetText = m_Tree.GetItemText( nmTree.itemNew.hItem );

    return 0;
}


>仲澤@失業者さん
PostMessage はマルチスレッドのようなものなんですね。
呼び出し元の処理をブロックするため、SendMessageを使ってみることにします。

かれー 2010/05/10(月) 16:52:33
[[解決]]
解決を入れ忘れていました

かれー 2010/05/10(月) 16:56:50
ごめんなさい、追記です。

   LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
   this->GetParent()->PostMessageW(WM_CHANGETREE, 0, (LPARAM)pNMTreeView );

LPNMTREEVIEW がポインタとのことなので、上記の形でもいけました。
これなら、deleteする必要がないのでPostMessageでも問題なさそうです。

maru 2010/05/10(月) 17:45:51
> LPNMTREEVIEW がポインタとのことなので、上記の形でもいけました。
> これなら、deleteする必要がないのでPostMessageでも問題なさそうです。
問題があります。

LPNMTREEVIEWが指す構造体はOnTvnSelchanged関数が実行している間は有効ですが、
関数が終了してしまうと有効な構造体を示していないかもしれません。
SendMessageは完了復帰なので、ChangedTreeを呼び出した後にOnTvnSelchanged関数
を終了しますが、PostMessageはOnTvnSelchanged関数を終了した後にChangedTreeが
呼び出されます。メッセージキューの状態によっては、OnTvnSelchangedが呼び出さ
れてから、ChangedTreeを呼び出すまでに他の処理が実行されて、ポインタが有効で
なくなる可能性があります。
現実的にはOnTvnSelchangeとChangedTreeの間に他の処理が走ることはほとんどない
でしょうが。

中澤さん
> 別の問題。
メモリーリークのバグがあることを見落としていました。orz

でも
> newしたオブジェクトはdeleteしなきゃいけないのですが、
> その機会はnewを実行しているスコープの寿命内でやらなきゃなりません。
これには異議があります。そんな規則(スコープの寿命内)はありません。
確かにnewしたオブジェクトをdeleteしないとメモリリークしますが、同一スコープ
でやらなければならないわけではありません。
newとdeletewo別々の関数で実行することに何の問題もありません。
今回の場合、メッセージを受け取った関数の側でdeleteすればよいだけのことです。

ただし、メッセージがスレッドをまたいでいると問題かもしれません。
(ここは自信なし)

maru 2010/05/10(月) 19:29:33
失礼しました。
仲澤さんの名前を間違えてしまいました。

かれー 2010/05/11(火) 09:17:53
>maruさん
何かしら、値の受け渡しをしている場合はSendMessageのほうが安全ということですね。
今回はSendMessageを使う事にします。
ご説明ありがとうございます。勉強になりました。

PATIO 2010/05/11(火) 10:09:22
釈迦に説法かもしれませんが、

SendMessageに関しては気をつけないとスパゲッティ状態に
なってしまう事がありえるので注意が必要です。
SendMessageはメッセージを受けた時の処理関数が終わらないと
帰ってきません。
で、この関数の中でさらにSendMessageを呼んでいてと言う風に
入れ子になった挙句、最初のSendMessageを読んだ部分がまた呼ばれて
しまうとループになってしまいます。
SendMessageを多用しすぎると意識しない所でそうなりかねないので
PostMessageで済む場合はPostMessageを使った方が良いです。

maru 2010/05/11(火) 14:50:43
> 何かしら、値の受け渡しをしている場合はSendMessageのほうが安全ということです
ね。
そんなことは言っていませんよ。
OnTvnSelchanged関数で受け取ったポインタをそのまま Postしているのが問題だ、
といっているんです。もう一度、以下を良く読んでください。

> LPNMTREEVIEWが指す構造体はOnTvnSelchanged関数が実行している間は有効ですが、
> 関数が終了してしまうと有効な構造体を示していないかもしれません。
> SendMessageは完了復帰なので、ChangedTreeを呼び出した後にOnTvnSelchanged関数
> を終了しますが、PostMessageはOnTvnSelchanged関数を終了した後にChangedTreeが
> 呼び出されます。メッセージキューの状態によっては、OnTvnSelchangedが呼び出さ
> れてから、ChangedTreeを呼び出すまでに他の処理が実行されて、ポインタが有効で
> なくなる可能性があります。

また、SendMessageを多用した場合の問題点はPATIOさんが指摘している通り
です。メッセージハンドラはそこで完結するように作成する方が無難です。
まぁ全てそのように作成するのは困難ですけど。

maru 2010/05/11(火) 15:08:58
昨日のかれーさんのコードをよく見たらOnTvnSelchanged側でポインタを delete
しちゃってるのね。orz
SendMessageを使用するなら、NMTREEVIEWをnewする必要はないし、
PostMessageを使用するなら、OnTvnSelchanged側でdeleteしてはいけない。

void CDirTree::OnTvnSelchanged(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
    NMTREEVIEW* pTemp = new NMTREEVIEW(*pNMTreeView);

    GetParent()->PostMessage(WM_CHANGETREE, 0, (LPARAM)pTemp);
// ここでdeleteしてはいけない
//    delete pTemp;

    *pResult = 0;
}
----------
// 親
LRESULT CxxxxView::ChangedTree(WPARAM wParam, LPARAM lParam)
{
    NMTREEVIEW* pTemp = (NMTREEVIEW*)lParam;
// このオブジェクトは不要!
//    NMTREEVIEW nmTree(*pTemp);

    CString sGetText = m_Tree.GetItemText(pTemp->itemNew.hItem );
    delete pTemp;// ここでdelete!

    return 0;
}

subaru 2010/05/11(火) 15:50:32
SendMessageの方が安全とは言いませんが、
メッセージが入れ子になったらループというのは
PostMessageも同様で安全というわけではありません。

標準コントロールに用意されているメッセージはたいていSendMessageで送るものが
多いはずですし、SendMessageを使うかPostMessageを使うかはケースバイケースです。

今回の場合は親側の処理がメッセージ投げっぱなしで問題ないように作ってあれば
PostMessageでもいいですが、親側からの応答を受け取りたいとか
ツリービュー側と同期的に動いてほしいようなものであれば
むしろSendMessageの方が適切ということになり得ます。

かれー 2010/05/11(火) 16:17:49
>PATIOさん
> 釈迦に説法かもしれませんが、
とんでもないです。ご助言頂けるだけで非常にありがたいです。

今回のはSendMessageを二重で呼び出すような処理はしていませんし、
値の受け渡しも行わずイベントを起こすだけのような箇所では
PostMessageを使うようにしてみましたので、問題はないかなと思っています。



>maruさん
> OnTvnSelchanged関数で受け取ったポインタをそのまま Postしているのが問題だ、
・ポインタのアドレスをそのままPostしてしまうと元の関数が処理終了した時点で
 アドレスが消えてしまうため、ポインタの中に入れてそのアドレスを渡すべき
・Sendなら完了復帰なので、アドレスが消えないのでnewせずそのまま渡して問題ない
・Postの場合、delete は値を使い終わってから行うべきなので、渡し先に記述すべき
と、言うことですよね。

今回は、SendMessageを使用しているので、newせずに記述しています。
ご教授頂いたおかげで、理解して動かしていたつもりですが、勘違いがあったらすみませ
ん。

かれー 2010/05/11(火) 16:40:28
>subaruさん
今回はメッセージ投げっぱなしでも問題ない仕様ですので、
PostでもSendでも問題ないようでした。
良く使う関数だと思うので、今後使う機会もあるでしょうし良い勉強になったと思いま
す。

maru 2010/05/11(火) 18:11:10
> subaru さん
> SendMessageの方が安全とは言いませんが、
> メッセージが入れ子になったらループというのは
> PostMessageも同様で安全というわけではありません。
Send/Postのどちらがより安全か?なんてことに意味は無いですよね。
正しく使用すればいいだけの話です。

> SendMessageを使うかPostMessageを使うかはケースバイケースです。
もちろんその通りです。

> 今回の場合は親側の処理がメッセージ投げっぱなしで問題ないように作ってあれば
> PostMessageでもいいですが、親側からの応答を受け取りたいとか
> ツリービュー側と同期的に動いてほしいようなものであれば
> むしろSendMessageの方が適切ということになり得ます。
今回の場合、どのように動かそうとしているのかが不明ですので、その判断はできな
いですよね。
大元の質問が
> 引数受け渡しについて学習中で、試しに
> ツリービュー変更時のイベントで文字列を抜き出して取得する
> と言うプログラムをPostMessageを使って作成しています。
だったので、PostMessageを使用するんだったら、こうじゃないのかな。
という視点で書いたつもりです。Send/Postのどちらがふさわしいか?はその場面次第
なので、かれーさんの判断で使い分ければいいでしょう。

最も今回の場合、本当にメッセージを使う必要があるのかも疑問ですけどね。
メッセージの代わりにイベントハンドラを直接呼び出したっていいわけだし。

maru 2010/05/11(火) 18:35:41
> ご教授頂いたおかげで、理解して動かしていたつもりですが、勘違いがあったらすみ
> ません。
本当に理解しているのかなぁ。本当に理解していれば、「渡すべき」「記述すべき」
などと「べき」をつかうわけないんだけど。「べき」と書かれると「そうした方が
良い」程度にしか思われていないのかな、と思ってしまう。ここではそうしないの
はバグですよ。

subaru 2010/05/11(火) 22:20:42
>maru さん
親側の処理がわからない状態で両方の方法が示されていたので
maruさんの意図は理解していたつもりですが、
スレの流れでPostMessageの方が推奨動作と勘違いする人も
いるかもしれないので念のための書き込みのつもりでした。
#私がVC++以外の言語では同一スレッドでPostMessage相当の処理を多用することって
#あんまりないような・・・と思っているせいもあるかもしれませんが。

>最も今回の場合、本当にメッセージを使う必要があるのかも疑問ですけどね。
>メッセージの代わりにイベントハンドラを直接呼び出したっていいわけだし。
そうですね。ただそのためには子の方で親側の実装を知っている必要がありますが。
まじめに作りこむならJAVAのイベントリスナーもどきを作ったりとか
.NETのデリゲートもどきを作ったりとかの方法もあるでしょうがけっこう面倒だし、
使い捨てのUIコードなら結局メッセージで処理しちゃうのが楽ってのはあるかも。。。

かれー 2010/05/12(水) 10:36:16
>maruさん
> 最も今回の場合、本当にメッセージを使う必要があるのかも疑問ですけどね。
> メッセージの代わりにイベントハンドラを直接呼び出したっていいわけだし。
今回は、あくまで勉強のために、メッセージを送ってイベントを起こせるかどうか?を
試したかっただけですので、メッセージを使う意味は全くありません。

> 「べき」と書かれると「そうした方が良い」程度にしか思われていないのかな、
どちらかと言うと、「〜するべきですよね?」と言うような疑問系な意味合いで使ってま
した。
理解した、と言っておいてすみません。自信の無さの表れが・・・
一応、「そうしなければならない」と言う意味で認識はしてるつもりです。

古いスレッドには追加発言できません。