お世話になっております。
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 と同じように渡すのでは駄目なので
しょうか?
LPNMTREEVIEWはNMTREEVIEW構造体へのポインタです。
> LPNMTREEVIEW* pTemp = new LPNMTREEVIEW(pNMTreeView);
は構造体へのポインタを複製していて、構造体そのものを複製してはいません。
元の構造体そのものはOnTvnSelchanged()が終了した後は有効ではないので、
メッセージを受け取った側では使用することは出来ません。
ここまで書けば、どうすべきかは分かると思うので、答えは書きません。
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()を使います。
お二方、ありがとうございます。
>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を使ってみることにします。
解決を入れ忘れていました
ごめんなさい、追記です。
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
this->GetParent()->PostMessageW(WM_CHANGETREE, 0, (LPARAM)pNMTreeView );
LPNMTREEVIEW がポインタとのことなので、上記の形でもいけました。
これなら、deleteする必要がないのでPostMessageでも問題なさそうです。
> 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さん
何かしら、値の受け渡しをしている場合はSendMessageのほうが安全ということですね。
今回はSendMessageを使う事にします。
ご説明ありがとうございます。勉強になりました。
釈迦に説法かもしれませんが、
SendMessageに関しては気をつけないとスパゲッティ状態に
なってしまう事がありえるので注意が必要です。
SendMessageはメッセージを受けた時の処理関数が終わらないと
帰ってきません。
で、この関数の中でさらにSendMessageを呼んでいてと言う風に
入れ子になった挙句、最初のSendMessageを読んだ部分がまた呼ばれて
しまうとループになってしまいます。
SendMessageを多用しすぎると意識しない所でそうなりかねないので
PostMessageで済む場合はPostMessageを使った方が良いです。
> 何かしら、値の受け渡しをしている場合はSendMessageのほうが安全ということです
ね。
そんなことは言っていませんよ。
OnTvnSelchanged関数で受け取ったポインタをそのまま Postしているのが問題だ、
といっているんです。もう一度、以下を良く読んでください。
> LPNMTREEVIEWが指す構造体はOnTvnSelchanged関数が実行している間は有効ですが、
> 関数が終了してしまうと有効な構造体を示していないかもしれません。
> SendMessageは完了復帰なので、ChangedTreeを呼び出した後にOnTvnSelchanged関数
> を終了しますが、PostMessageはOnTvnSelchanged関数を終了した後にChangedTreeが
> 呼び出されます。メッセージキューの状態によっては、OnTvnSelchangedが呼び出さ
> れてから、ChangedTreeを呼び出すまでに他の処理が実行されて、ポインタが有効で
> なくなる可能性があります。
また、SendMessageを多用した場合の問題点はPATIOさんが指摘している通り
です。メッセージハンドラはそこで完結するように作成する方が無難です。
まぁ全てそのように作成するのは困難ですけど。
昨日のかれーさんのコードをよく見たら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;
}
SendMessageの方が安全とは言いませんが、
メッセージが入れ子になったらループというのは
PostMessageも同様で安全というわけではありません。
標準コントロールに用意されているメッセージはたいていSendMessageで送るものが
多いはずですし、SendMessageを使うかPostMessageを使うかはケースバイケースです。
今回の場合は親側の処理がメッセージ投げっぱなしで問題ないように作ってあれば
PostMessageでもいいですが、親側からの応答を受け取りたいとか
ツリービュー側と同期的に動いてほしいようなものであれば
むしろSendMessageの方が適切ということになり得ます。
>PATIOさん
> 釈迦に説法かもしれませんが、
とんでもないです。ご助言頂けるだけで非常にありがたいです。
今回のはSendMessageを二重で呼び出すような処理はしていませんし、
値の受け渡しも行わずイベントを起こすだけのような箇所では
PostMessageを使うようにしてみましたので、問題はないかなと思っています。
>maruさん
> OnTvnSelchanged関数で受け取ったポインタをそのまま Postしているのが問題だ、
・ポインタのアドレスをそのままPostしてしまうと元の関数が処理終了した時点で
アドレスが消えてしまうため、ポインタの中に入れてそのアドレスを渡すべき
・Sendなら完了復帰なので、アドレスが消えないのでnewせずそのまま渡して問題ない
・Postの場合、delete は値を使い終わってから行うべきなので、渡し先に記述すべき
と、言うことですよね。
今回は、SendMessageを使用しているので、newせずに記述しています。
ご教授頂いたおかげで、理解して動かしていたつもりですが、勘違いがあったらすみませ
ん。
>subaruさん
今回はメッセージ投げっぱなしでも問題ない仕様ですので、
PostでもSendでも問題ないようでした。
良く使う関数だと思うので、今後使う機会もあるでしょうし良い勉強になったと思いま
す。