CWinThreadからダイアログを操作 – 固定ページ 2 – プログラミング – Home

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

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

固定ページ 2 / 2

bun
 bun
(@bun)
ゲスト
結合: 23年前
投稿: 761
 

このあたり、私も以前からあいまいで、正確なところを知りたいのですが、
そもそもスレッドをまたいでも見えるものと見えないものの差は何なのでしょう?

MFCなんて所詮クラスライブラリです。
C++言語やWindowsAPIの枠からはみ出す物は一つも無いはずです。

C++言語やWindowsAPIの枠内でスレッドをまたぐと見えなくなるものがあるはずだと
思うのですが、それはいったい何なのでしょう?


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

私もわかっていませんが、とりあえず、落書き程度です。

デバッガーで追うと、CHandleMap::FromHandleに行き、
この中に、LookupTemporary,LookupPermanentを見つけ、
これが、gakさんの ”一時オブジェクト”、”永続オブジェクト”かと思い、
実験。
さらに、grepして、CWnd::AssertValidを見つけたということです。

TlsAllocもみつけたのですが、ソースはまだ読んでいません。


返信引用
subaru
 subaru
(@subaru)
ゲスト
結合: 18年前
投稿: 381
 

>C++言語やWindowsAPIの枠内でスレッドをまたぐと見えなくなるものがあるはずだと
>思うのですが、それはいったい何なのでしょう?

すでに回答にも出てきていますが、
スレッドをまたぐと見えなくなるものとは
スレッドローカルストレージ(TLS)に格納された値のことです。
(CWndなどのMFCオブジェクトはTLSを使用しているらしい)

WindowsAPIでは以下のものが使われます。
TlsAlloc
TlsGetValue
TlsSetValue
TlsFree


返信引用
bun
 bun
(@bun)
ゲスト
結合: 23年前
投稿: 761
 

ロマさん、subaruさん ありがとうございます。

お二方のヒントでようやく全貌を理解した気がします。
当方の環境は VC++2005ですが、おそらく最新版も大差ないであろうと予測の
元お話しさせて頂くと、

話題になるのは、
MFCにおける CWnd* <-> HWND間の参照です。

CWnd* -> HWND の方向は、メンバ変数m_hWndの参照ですから何の問題もあり
ません。

HWND -> CWnd* の方向は、マップを用いて変換を行っています。
マップキー:HWND
マップ値:CWnd*
このマップ(CHandleMap)の取得は常に以下の形で行われます。
CHandleMap* pMap = afxMapHWND();
問題は、afxMapHWND()の呼び出しの中で、TLSが使われていることです。
そのため、HWND -> CWnd* の変換マップがスレッド毎に独自のものに
なってしまうようです。

よって、別スレッドでは、HWNDに対応するCWnd*が見つからず、正常処理がで
きないようです。

CWnd*のポインタ自体は別スレッドでも有効でしょうから、
HWND -> CWnd* の変換マップさえ正常動作すればいいように思えます。

それに対する答がMicroSoftの言う、スレッド側で、
「HWND を Attach() で登録しろ」という話なのでしょう。
登録した結果、HWND -> CWnd* の逆引きが出来るようになってめでたし、
なのかな?
(この話だけちょっと自信が無い^^;)


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

多くのご意見ありがとうございます。
ちょっと自分の状態を説明させていただきます。

> CWnd、CDC 等の「MFC Windowsオブジェクト」ラップクラスには”永続オブジェクト”と”一
> 時オブジェクト”の2種類存在する。

このへんの仕組みは、自分なりには理解しているつもりです。

CWnd::FromHandleなどを使ってHWNDから取得したCWndのポインタは、
そのワーカースレッドの中で一時的に作成したCWndを指していて、
UIスレッド側のCMutexesDlgは指していないため、
無理やりダウンキャストしてm_mutexなどのメンバ変数にアクセスすると、
その時点で不正アクセスになってしまいます。

そのため、このサンプルでは、HWNDではなくCMutexesDlg自体のポインタを渡し、
ワーカースレッド内でそれらを使えるようにしているのだと解釈しました。

ただ、その方法を使った時点で、
よく目にする「CWndのポインタを別スレッドに渡すな」に
MS自身が違反していることになるため、
2012/02/20(月) 09:17:31や、
2012/02/26(日) 13:01:50の質問をさせていただきました。

「駄目な例」という結論であれば、それはそれで理解できるのですが、
tetrapodさんからは「問題ない」という見解を頂いたたため、
MSも問題ないという認識で書いたサンプルなのだと解釈しました。

MSが「本当は駄目だけど、ただのサンプルだし」と書いたものなのか、
「別スレッドに渡すな」というのはこういうことではなく、
MS的にはセーフな使いかたなのか、どっちなのでしょうね。


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

> MS的にはセーフな使いかたなのか、どっちなのでしょうね。
http://msdn.microsoft.com/ja-jp/library/h14y172e.aspx
http://msdn.microsoft.com/ja-jp/library/c251x6s1.aspx
・前提として、MFCはスレッドセーフ設計では無い
・スレッド跨いで安全に使いたい場合は同期が必要
・MFCにはスレッドアンセーフな実装をしている機能もある
・ハンドルとMFCオブジェクトのマッピング機能に関してはTLS使ってる。

MSの立ち位置としては「MFCはスレッドセーフ設計では無い。スレッド跨いで使う際の動
作保障はMSはしない。特にハンドル関係はTLS使ってる関係上其れを意識しないコード組
んで予期しない結果が起きてもしらないよ」じゃない?
ぶっちゃけ、スレッドセーフな使い方をプログラマがしないならout、するならoutでは無
い、の範疇ではなかろうか。

サンプルの m_pOwner の使われ方も C++ オブジェクトをスレッド跨いで使うレベルと差
異ない使われ方しかしていない。なのでサンプルの作者は問題無いと判断を下したのかも
しれない。
> http://msdn.microsoft.com/ja-jp/library/ms386471.aspx
若しくは、元々 C + Windows API で書かれていたコードを MFC に置き換えたコードらし
いので、良く考えずに置換しただけなのかもしれない。

> m_pOwner->AddToListBox(_T(Cntr: Increment));
自分の意見を述べてなかったので言っとくと、俺なら HWND 渡して SendMessage する。


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

ご意見ありがとうございます。
急用でPCをさわれませんでした。申し訳ありません。

> サンプルの m_pOwner の使われ方も C++ オブジェクトをスレッド跨いで使うレベルと差
> 異ない使われ方しかしていない。なのでサンプルの作者は問題無いと判断を下したのかも
> しれない。

自分も、ダイアログクラスなどがワーカースレッドを使って
自身のメンバを構築していくような処理では、
HWNDだとダイアログ自身のメンバにはアクセスできないため、
thisを渡してしまうことがよくあります。
これがすでにアウトだという意見もあるかもしれませんが。

ただ、リストボックスへの追加など、ダイアログ内のコントロールの操作まで
ワーカースレッド側ですることまではしないようにしていたので、
今回のMSのサンプルは、ある意味予想外でした。

> > m_pOwner->AddToListBox(_T(Cntr: Increment));
> 自分の意見を述べてなかったので言っとくと、俺なら HWND 渡して SendMessage する。

これは、「AddToListBoxを呼んでください」といった趣旨の独自のメッセージで、
_T(Cntr: Increment)のポインタをLPARAMなどに入れて送るわけですよね。


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

> これは、「AddToListBoxを呼んでください」といった趣旨の独自のメッセージで、
> _T(Cntr: Increment)のポインタをLPARAMなどに入れて送るわけですよね。
そう。今回のサンプルは「mainスレッド + subスレッドx2」という構成で、AddToListBox
がsubスレッドから同時にコールされる可能性が在る実装が気分的に引っ掛かった。なの
で俺なら(念の為) SendMessage で同期取るかなぁ、という考え。

以下蛇足。
例えば「進捗表示するダイアログをモーダルで出して裏スレッドで作業する」という実装
をする場合以下の様なコードを組んだりする(というか過去コード見ると組んでた)

class dlgProgress : public CDialog {
void OnInitDialog() { // WM_INITDIALOG handler
AfxBeginThread(&threadProc、this);
}
static UINT __cdecl threadProc(void *param) {
dlgProgress *dlg = reinterpret_cast<dlgProgress*>(param);
dlg->Do();
}
void Do() { // 作業実行
HWND progressBar = ::GetDlgItem(m_hWnd, プログレスバーid);
::SendMessage(progressBar, PBM_SETPOS, 0, 0); // 0%

::SendMessage(progressBar, PBM_SETPOS, 50, 0); // 50%

::SendMessage(progressBar, PBM_SETPOS, 100, 0); // 100%
PostMessage(WM_APP, IDOK);
}
LRESULT OnWmApp(WPARAM w, LPARAM) { // WM_APP handler
EndDialog(w);
}
}

俺的には特に問題無いつもりでいたのだけど実際はどうなんだろうね。
・変数へ同時アクセスする類の処理
・TLS 依存部分
・FromHandle() で返される一時オブジェクトの生存期間
辺りにさえ注意すれば、画一的に「CWnd 派生を別スレッドに渡すな」では無いと思って
たけど…これ以上に深く考えた事はなかった。


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

> void Do() { // 作業実行
> HWND progressBar = ::GetDlgItem(m_hWnd, プログレスバーid);
> ::SendMessage(progressBar, PBM_SETPOS, 0, 0); // 0%
> :
> ::SendMessage(progressBar, PBM_SETPOS, 50, 0); // 50%
> :
> ::SendMessage(progressBar, PBM_SETPOS, 100, 0); // 100%
> PostMessage(WM_APP, IDOK);
> }

この「:」の部分で、dlgProgressクラスが自身のメンバを
参照したり更新したりしているわけですね?

自分もこのようにAfxBeginThreadにthisを渡すことが多いのですが、
thisはCWnd派生クラスのポインタなので、
MSの見解上は「CWndのポインタを別スレッドに渡すな」に
引っかかっているのではと疑問に思っています。

なお、自分の方法としては、進捗状況を数値型のメンバ変数で持っておき、
プログレスバーの更新はメインスレッド側でタイマーを使って表示したり、
進捗状況の更新を伝えるメッセージを自身に送ったりするようにしています。

> 俺的には特に問題無いつもりでいたのだけど実際はどうなんだろうね。
> ・変数へ同時アクセスする類の処理
> ・TLS 依存部分
> ・FromHandle() で返される一時オブジェクトの生存期間
> 辺りにさえ注意すれば、画一的に「CWnd 派生を別スレッドに渡すな」では無いと思って
> たけど…これ以上に深く考えた事はなかった。

自分もこういうことなのかなとは思っているのですが、
このスレッドでも、人によって解釈が異なるようですし、
どの意見も正しく思えてしまいます。
MS自身が「サンプル」として堂々とスレッド跨ぎしちゃってますし。


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

返信する

投稿者名

投稿者メールアドレス

タイトル *

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