CWinThreadからダイアログを操作 – プログラミング – Home

CWinThreadからダイアログを操...
 
通知
すべてクリア

[解決済] CWinThreadからダイアログを操作

固定ページ 1 / 2

いしだ
 いしだ
(@いしだ)
ゲスト
結合: 17年前
投稿: 53
Topic starter  

MSDNのサイトで「MUTEXES サンプル」というものをダウンロードし、
ソースを読んでみたのですが、

int CCounterThread::Run()
{
CSingleLock sLock(&(m_pOwner->m_mutex));
while (!m_bDone)
{
(中略)
m_pOwner->AddToListBox(_T(Cntr: Increment));
}
}

int CDisplayThread::Run()
{
CSingleLock sLock(&(m_pOwner->m_mutex));
while (!m_bDone)
{
(中略)
m_pOwner->AddToListBox(strBuffer);
}
}

となっていて、AddToListBoxは、以下のように
ダイアログ内のリストボックスに文字列を直接追加していました。

void CMutexesDlg::AddToListBox(LPCTSTR szBuffer)
{
CListBox* pBox = (CListBox*) GetDlgItem(IDC_DATABOX);
ASSERT(pBox != NULL);

if (pBox != NULL)
{
int x = pBox->AddString(szBuffer);
pBox->SetCurSel(x);

if (pBox->GetCount() > 100)
pBox->DeleteString(0);
}
}

この流れを見ると、ワーカスレッドから
メインスレッドのウィンドウを直接操作していると思われるのですが、
これは「ダメな例」だと思ったほうがよいのでしょうか。

http://rarara.cafe.coocan.jp/cgi-bin/lng/vc/vclng.cgi?print+201002/10020017.txt
これと同じなのではと思われるのですが。


引用未解決
トピックタグ
tetrapod
 tetrapod
(@tetrapod)
ゲスト
結合: 21年前
投稿: 830
 

うんにゃ。
CWnd* CWnd::GetDlgItem(int) const; が返すのは
(そのスレッド内だけで有効な新しい) 一時的 CWnd インスタンスへのポインタ。
http://msdn.microsoft.com/ja-jp/library/77d16yhw(v=vs.80).aspx

UI スレッド上で作成した CWnd のインスタンス を
Worker スレッドからアクセスしてはならない (スレッドまたぎはだめ)
のであって

Worker スレッド上で作成した CWnd のインスタンスを
Worker スレッドからアクセスするのは問題ない (同一スレッドなら良い)
# Worker スレッドが複数あるとき、スレッドまたぎはだめ

AddToListBox は別スレッドから起動されうるが、
・pBox に一時的 CWnd インスタンスを得て
・pBox に操作を加える
一連の処理は同一スレッド内で行われる (=またがない) 。

ある1つの HWND に対して複数スレッドから操作を行ってよい。
# 排他しないと望みどおりの結果にならないかもしれんが。

ある1つの HWND に対して別の CWnd インスタンスを作ってよい。
片方の CWnd インスタンスは UI スレッドに属し
片方の CWnd インスタンスは Worker スレッドに属し、て問題ない。


返信引用
いしだ
 いしだ
(@いしだ)
ゲスト
結合: 17年前
投稿: 53
Topic starter  

情報ありがとうございます。

> AddToListBox は別スレッドから起動されうるが、
> ・pBox に一時的 CWnd インスタンスを得て
> ・pBox に操作を加える
> 一連の処理は同一スレッド内で行われる (=またがない) 。

AddStringなどのCListBoxのメンバ関数はSendMessageが使われているので、
最初に挙げた過去ログのように、UIスレッドがWaitForSingleObjectで待つと
デッドロックになり得るから危険と解釈していたのですが、
そのような待ちかたさえしなければ、
ワーカースレッドからUIスレッドのウィンドウに対して
SendMessageが使われるのは、問題ないということでしょうか。

また、もし問題ないとすると、もう一つ質問なのですが、
ワーカースレッドの内部で、
UIスレッドの作成したダイアログやその下のコントロールに対して、
HWNDを経由してCClientDCを使って直接描画したりするのも、
一応は問題ないということになりますでしょうか?


返信引用
tetrapod
 tetrapod
(@tetrapod)
ゲスト
結合: 21年前
投稿: 830
 

> ワーカースレッドからUIスレッドのウィンドウに対して
> SendMessageが使われるのは、問題ないということでしょうか。
問題ないっす。
(もちろん UI スレッドが待つのはだめ)

> HWNDを経由してCClientDCを使って直接描画したりするのも、
HWND を扱う限りにおいては複数スレッドから操作して問題ないよ。
排他しないと意図通りになるとは限らないけど。


返信引用
いしだ
 いしだ
(@いしだ)
ゲスト
結合: 17年前
投稿: 53
Topic starter  

情報ありがとうごございます。

何度もすみません。
セーフ・アウトの境界を自分なりに理解しようとしているのですが、
もう一つ疑問点が出てきてしまいましたので、
追記させていただきます。

> UI スレッド上で作成した CWnd のインスタンス を
> Worker スレッドからアクセスしてはならない (スレッドまたぎはだめ)

m_pOwnerというのはCMutexesDlgというメインダイアログのポインタで、
UIスレッド側でダイアログ自身がthisを渡しているのですが、
これはなぜ問題ないのでしょうか。
thisはCWnd(CDialog)のインスタンスなのですが。


返信引用
tetrapod
 tetrapod
(@tetrapod)
ゲスト
結合: 21年前
投稿: 830
 

CWnd やその派生クラスに関してスレッドまたぎが一切ダメ、ってわけではなくて、
CWnd の TLS (Thread Local Storage) 依存部がスレッドまたぎ禁止なだけ。

ここでの m_pOwner ってのは CWnd 派生クラスで追加したメンバー変数であって
m_pOwner そのものは TLS 依存しない (m_pOwner の先の HWND 関連部は依存あり)
m_pOwner の先の m_mutex も TLS 依存しない
ので問題ない

具体的に何ができて何が禁止なのかは MFC ソースを読んでみるべし。


返信引用
PATIO
(@patio)
Famed Member
結合: 3年前
投稿: 2660
 

スレッド跨ぎの件もさることながら、モジュールの独立性とか
モジュール間の結合度を出来るだけ弱くすると言う考え方からすると
HWNDを使ってPostMessageするとかの方がより疎結合になるかなと
思います。個人的には出来る限り疎結合にしてウインドウの組み換えを
しやすくしておきたいので、基本的にHWNDでPostMessageと言うのが
私のスタイルです。

まあ、この辺は人それぞれの部分もあるので何処までやるのかと言う話はあると思います
けれども。


返信引用
いしだ
 いしだ
(@いしだ)
ゲスト
結合: 17年前
投稿: 53
Topic starter  

情報ありがとうごございます。

最初に挙げた過去ログもそうですが、それ以外の多くの過去ログでも、
MFCのインスタンスをスレッドを跨いで渡してはダメと書かれていたので、
ダイアログがthisを渡すこと自体ダメなのだと思っていました。

MSDNのサイトの「MUTEXES サンプル」では、
メインスレッドのダイアログがワーカースレッドにthisを渡し、
ワーカースレッドがそのthisを使ってダイアログ内のリストビューを
直接操作したりしていたので、疑問に思っていたのですが、
それらはすべて問題ない使いかただったと理解しました。

ありがとうございました。


返信引用
PATIO
(@patio)
Famed Member
結合: 3年前
投稿: 2660
 

> ワーカースレッドがそのthisを使ってダイアログ内のリストビューを
> 直接操作したりしていたので、疑問に思っていたのですが、
> それらはすべて問題ない使いかただったと理解しました。

この書き方だとちょっと言いすぎなのではと思いますけれど。
MSDNにはスレッド跨ぎでのCWndオブジェクトの受け渡しは駄目と
はっきり書いてありますので、全て問題がないという話ではないです。

この辺はなぜ駄目なのかと言う部分の理解がないと
どの操作が駄目でどの操作が良いのかと言う部分の切り分けが難しいので
その部分の判断ができないのであれば、基本的には行わないと言う方向に
倒した方が安全だと思います。

あと、仕様上の縛りでだめと言う話になっているので
絶対に動作しないと言う話ではなく、偶々動いているという状況も
ありえますし、動いていたパターンがあるバージョンで動かなくなったとしても
MSからすれば、仕様上駄目と公式に書いていますよねと言う事になるので
あまりチャレンジャーなやり方はお勧めできないと言うのが本音です。
(今、動いているから良いという話では無いという事ですね。)

基本的にはCWndオブジェクトをスレッド間でやり取りしなくても良いように
設計すべきだと思いますし、そもそもダイアログが抱えているコントロールの
制御はそのダイアログが行なうべきだと思いますので
HWNDとPostMessage(ユーザー定義メッセージ)を使ってやり取りを行い、
コントロールの制御はメッセージを受けたダイアログ(メインスレッド側)で
行った方が良いと思いますよ。


返信引用
いしだ
 いしだ
(@いしだ)
ゲスト
結合: 17年前
投稿: 53
Topic starter  

> MSDNにはスレッド跨ぎでのCWndオブジェクトの受け渡しは駄目と
> はっきり書いてありますので

ご意見ありがとうございます。

ここの掲示板でも、たびたびこのような指摘が書かれていて、
今回のMSDNのサンプルではダイアログがワーカースレッドにthisを渡していたので、
これがまさにその駄目な使いかたなのではと疑問に思ったのですが、
tetrapodさんからは「問題ない」という見解をいただいたため、
今回のような使いかたには当てはまらないのだ解釈しました。

この自分の解釈がまた変に誤解しているということでしょうか?

それとも、PATIOさんの見解としては、
今回のMSDNのサンプルは「駄目な例」だということになりますでしょうか?


返信引用
ITO
 ITO
(@ITO)
ゲスト
結合: 22年前
投稿: 1235
 

うーーん、
> tetrapodさんからは「問題ない」という見解をいただいたため、
> 今回のような使いかたには当てはまらないのだ解釈しました。
 HWNDを使ったCWndオブジェクトの処理はHWND == NULLの例外が発生して
異常終了します。
 
> MSDNのサイトの「MUTEXES サンプル」では、--------

> ワーカースレッドがそのthisを使ってダイアログ内のリストビューを
> 直接操作したりしていたので、疑問に思っていたのですが、

> MSDNにはスレッド跨ぎでのCWndオブジェクトの受け渡しは駄目と
> はっきり書いてありますので
 「駄目な例」となるとどこまでがCWndの処理になるか微妙ですが、
MSDNの仕様をまもるのであれば避けたい処理ですね。


返信引用
ロマ
 ロマ
(@ロマ)
ゲスト
結合: 18年前
投稿: 170
 

どうもよくわからないです。

> CListBox* pBox = (CListBox*) GetDlgItem(IDC_DATABOX);

このキャストで使えるCListBoxのメンバー関数は、
直にWin32APIを呼び出しているようなものに限られているように思います。

もしそうならば、ワーカースレッドはHWNDだけを使うほうがわかりやすいような気が。


返信引用
PATIO
(@patio)
Famed Member
結合: 3年前
投稿: 2660
 

> もしそうならば、ワーカースレッドはHWNDだけを使うほうがわかりやすいような気が。

私も同意見です。
スレッド間でCWndを受け渡してはいけないと言う話の元凶が把握で来ていて
こういう使い方なら大丈夫と逐一判断出来るなら良いのですけれど。
個人的にはこのメソッドなら大丈夫とかこういう使い方なら大丈夫とか
そういう判断は結構煩雑になるので一切駄目とした方が解りやすいかなと思います。
根本の理解は必要だと思うのですが、人数を掛けて作業するときなどは
なかなかうまく回らない事が多いと思いますから。

今回のMSDNのサンプルに関しては私自身としてはこういう使い方はしないと思います。
基本的にはワーカースレッド側からユーザー定義のメッセージを使って通知し、
更新に必要な情報を引き渡してメインスレッド側で表示更新を行なう形にすると思います。
作り的にはワーカースレッドはメッセージを投げる所までで自処理の流れに戻り、
受けたメインスレッド側はワーカースレッドからの呼び出しとは切り離された所で
動作する事になります。
好みの話も入っているかもと思いますが、私個人としては切り離してしまいたいです。


返信引用
gak
 gak
(@gak)
ゲスト
結合: 21年前
投稿: 132
 

CWnd、CDC 等の「MFC Windowsオブジェクト」ラップクラスには”永続オブジェクト”と”一
時オブジェクト”の2種類存在する。
「MUTEXES」の「m_pOwner(==CMutexesDlg)」は CMutexesApp::InitInstance() で
> CMutexesDlg dlg;
という感じにインスタンスが明示的に作成されているオブジェクト。こういうのを”永続
オブジェクト”と呼び「dlg」が生存中はインスタンスが存在する事が確約される。

> CListBox* pBox = (CListBox*) GetDlgItem(IDC_DATABOX);
逆に、上記で作成される「pBox」は”一時オブジェクト”。IDC_DATABOX (のHWND)に対応
する MFC オブジェクトは(現在のスレッド内で)明示的に作成されていないので、MFC
が一時的な CWnd オブジェクトを作成している。
で、”一時オブジェクト”は”永続オブジェクト”とは異なり(オブジェクトを作成したス
レッドの)処理が一段落した段階で削除される(ガベージコレクションのような感じ)
# スレッドのTLSでハンドルマップを持って管理しる理由の一つはこの機能かも

> ダイアログ自身がthisを渡しているのですが、これはなぜ問題ないのでしょうか。
”一時オブジェクト”を他スレッドに渡す事は 100% NG。仕様上、ガベージコレクションに
よって意図しないタイミングで削除される事があるから(現状モーダルダイアログ表示中
なら一時オブジェクトの削除は行われない動作なので、結果大丈夫かもしんないが)
でも”永続オブジェクト”はそのオブジェクトが存在する事が保証される期間内ならば他ス
レッドに渡す事は可能。よって m_pOwner はこの点に措いては問題無いハズ。

MFCオブジェクトのもう一つの問題点は仕様上「スレッドセーフでは無い」という点。
例えば CMutexesDlg には m_strNumber という CString のメンバ変数が存在しているが

m_pOwner->m_strNumber = CString(_T(xxxxxx));

なんて処理を複数のスレッドから実行されるとタイミングによっては多分落ちる。上記の
様に直接メンバ変数を操作しないにしても、呼んだメンバ関数内でメンバ変数が操作され
ていた場合等にも同様の危険がある。
つまり、MFCオブジェクト(C++オブジェクトでも同様だけど)を複数スレッドから扱うな
らプログラムを組む側で同期してやらないと何時破綻しても文句は言えない。
# 「MUTEXES」では m_strNumber を同期無しで複数スレッドから操作している。
# が、m_strNumber のバッファの再確保が起こらない様に実装(LockBuffer() 等で)し
# て落ちる事は防いでいる(俺にはその場凌ぎの対応としか思えなかった…)

まぁ要すると「MFCオブジェクトをスレッド跨いで安全に使おうと思うと色々メンドイ」

> m_pOwner->m_strNumber.SetAt(0,L'\0');
> m_pOwner->m_strNumber.SetAt(nCurrentDigitPos,(TCHAR) ('0' + nNumber%10));
正直「MUTEXES」は「スレッド間の同期をミューテックスで取る」という点だけに絞って
参考にした方が良いかも。サンプルに含まれていた↑コード見ても「結果的には一緒に
なってるんだけど…う~ん」とか思ってしまう。


返信引用
ロマ
 ロマ
(@ロマ)
ゲスト
結合: 18年前
投稿: 170
 

> ダイアログ自身がthisを渡しているのですが、これはなぜ問題ないのでしょうか。
どこかで、ASSERT_VALID(m_pOwner)を呼び出していれば失敗します。

あと、
VC2003のコードを追っていくと、
一時オブジェクトは、OnIdleで削除されるようです。
モーダルダイアログはOnIdleを呼ばないので、
gakさんの記述どおりの様です(未検証)。
メッセージループの無い、ワーカースレッドの場合は、
スレッドの寿命と等しいようです(実験してみました)。


返信引用
固定ページ 1 / 2

返信する

投稿者名

投稿者メールアドレス

タイトル *

プレビュー 0リビジョン 保存しました
共有:
タイトルとURLをコピーしました