スレッド内からウィンドウの親ハンドルへアクセス

[戻る]
やな 2017/04/11(火) 09:51:18
CParentの子供にCTestがいる状態です。

スレッド内から、親のメンバ変数にアクセスしたいのですが、
なぜこうなるのかが、わからない点があるので教えてください。

void CTest::OnInitDialog() {
    CParent *p = GetParent();
    p->bFlag; ★@アクセスできる

    m_Thread = AfxBeginThread( TestThread, this, THREAD_PRIORITY_NORMAL, 0, 
CREATE_SUSPENDED );
    m_Thread->m_bAutoDelete = FALSE;
    m_Thread->ResumeThread();
}

UINT CTest::TestThread(LPVOID pParam) {
    CTest *pDlg = (CTest*)pParam;

    CParent *p = (CParent*)pDlg->GetParent();
    p->bFlag; ★Aアクセスできない(不正な値が入る)

    CParent *p2 = (CParent*)pDlg->GetParent()->m_hWnd;
    p2->bFlag; ★Bアクセスできる
}

なぜAでアクセスできないのでしょうか?
スレッド内のpDlgにCTestのハンドルが入っているので、
てっきり@と同じくGetParentだけでアクセスできると思っていたのですが・・・

仲澤@失業者 2017/04/11(火) 14:56:06
まず、
1.AfxBeginThread()の引数の制御関数はAFX_THREADPROC型つまり
 「UINT __cdecl MyControllingFunction( LPVOID pParam ); 」
 の型になっていなければなりません。従って、static でないクラスメンバ関数を
 指定することはできません。

このあたりは万全でしょうか。次に、

2.スレッド関数からは、自身を生成したCWnd「以外の」CWndのインスタンスに
 アクセス「できません」。 以下を参照してください。

  https://msdn.microsoft.com/ja-jp/library/h14y172e.aspx

 他のCWndにアクセスする場合は、CWnd::m_hWndメンバであるHWND等を使用して
 アクセスします。

例えば親のHWNDは以下の様に取得できますが、
HWND hParent = ::GetParent( pDlg->m_hWnd);
提示のコードのようにやっても、親のインスタンスを取得することは「できません」。

最後に

3.Bの方法はCWndの一時オブジェクトに取得した親のHWNDを代入しているだけで、
 本当の親(実体)ではありません。

仲澤@失業者 2017/04/11(火) 15:02:53
訂正。
3.Bの方法はCWndの一時オブジェクトのポインタに、取得した親のHWNDを
 「不正にキャスト」して代入しているだけで、本当の親(実体)ではありません。

やな 2017/04/11(火) 15:42:36
ご回答ありがとうございます。

>1.このあたりは万全でしょうか。
 スレッド関数の宣言がstaticになっているか、という意味でしょうか?
 ヘッダに以下のように宣言しているので、大丈夫かと思います。
 static UINT TestThread(LPVOID pParam);

>2.スレッド関数からは、自身を生成したCWnd「以外の」CWndのインスタンスに
>アクセス「できません」。 以下を参照してください。
 なるほど、私のコードだと、CTestにはアクセスできるけどその親にはアクセスでき
ない、
 ということですね。

 親の関数を呼びたい時はPostMessageでできそうですが、
 親のメンバ変数にアクセスしたいときはCTestに親のインスタンスを持ってもらって
 そこを経由する・・・とかでしょうか?

仲澤@失業者 2017/04/11(火) 18:57:53
子クラスが、引数でもらったり、スレッドからではなく子WndからGetParent()したCWnd*
は親のインスタンスに間違いありません。

それは置いといて、そもそも論として、
「子(Wnd)が親(Wnd)に特有のメンバ関数を実行する」という処理が必要になった。
という設計に問題はないでしょうか。

この設計によると、子はその親の特性を限定していて、そのような機能を持った
特殊な親の子としてしか生成できないことになります。
つまり一般的なCWndを親に持てないことになります。

例えば、子スレッドの進捗などを報告するのは、親からもらった進捗報告用のデータのポ
インタに設定するなどの方法がとれます。
つまり、親のCWndの派生クラス全体を知っている必要は無いはずなのですが。
どうでしょう。

ITO 2017/04/12(水) 10:27:25
うーーん、
 > 親の関数を呼びたい時はPostMessageでできそうですが、
 > 親のメンバ変数にアクセスしたいときはCTestに親のインスタンスを持ってもらって
 > そこを経由する・・・とかでしょうか?

いつも、event使っていますが、メッセージを使った処理は、

 1. スレッド関数内で処理が終了したら、継承用の変数(クラス)に
   データを保存してPostMessageを実行。
 
 2. 親でメッセージを受けて、継承用の変数(クラス)からデータを取得し
   メディアに保存・表示等の処理を行う。
ですね。

 短所は、メッセージの送受に最低100mSぐらいかかることですね。
対象のPCの状況でもっとかかる場合もあります。
   

> つまり、親のCWndの派生クラス全体を知っている必要は無いはずなのですが。

 リアルタイムでデータを表示させたいため、
     「スレッドから直接処理したい。」  
 のかな?
 と思います。

 

やな 2017/04/12(水) 15:45:51
やりたい事は、CD挿入/排出のイベントを見て
CD排出中は小ダイアログのスレッドを止めたい、です。

OSからのCDイベントを、親ダイアログが受け取っているので
その状態をメンバ変数に保持しておいて
小ダイアログのスレッドから監視しよう、と考えていました。

スレッドがある小ダイアログが複数あるので、
親の状態を小ダイアログから監視できればまとめられるかな、と思っています。


仲澤@失業者さんのおっしゃるように、
この仕組みは設計上問題がありますかね?

仲澤@失業者 2017/04/12(水) 18:59:11
親ダイアログがCDの状態を監視しているのであれば、
その親が 子のスレッドに対して CWinThread::SuspendThread() するだけで
良いように思うのですが、何か、子から見に行く必要性があったのでしょうか。

やな 2017/04/14(金) 15:24:28
SuspendThread()は使ったことが無かったので、
考慮に入れていませんでした。

ただ、このAPIってどこまで信用して良いものなのでしょうか?
ファイルコピーやダウンロード、アニメーションなどをスレッドで行っているのです
が
それらはSuspendThread()が綺麗に止めてくれるものなのでしょうか?

とりあえずSuspendThread()/ResumeThread()で一時停止/再開を行ってみましたが
今のところは期待通りの動作をしています。

タイミング次第でスレッド内部の処理がきちんと停止/再開できているのか、
と不安が少し残ります。
こういう場合には使っちゃいけない、こういう場合は不具合がおきる可能性がある、
みたいなのがあれば教えて頂きたいです。


>何か、子から見に行く必要性があったのでしょうか。
私が行おうとしていたのは、スレッド自身でwhile()で回しておき、
CD挿入のイベントが親にきたらそれを監視してスレッド自身で止める、
というのを想定していました。

仲澤@失業者 2017/04/14(金) 19:48:27
特に、指定の状態まで遷移してから一時停止したい場合など、
SuspendThread()を使わないでやりたい場合もあります。
それには一般に「同期オブジェクト」を使用します。同期オブジェクトには

1.クリティカルセクション CCriticalSection
2.ミューテックス CMutex
3.セマフォ CSemaphore
4.イベント CEvent

等があり、適材適所で使わなくてはなりません。
プロセス間で同期をとる目的のミューテックスやセマフォを使用する機会はあまりないか
もしれませんが、同一プロセス内でも機能します。
本件は単純な排他であるため、クリティカルセクション、または、セマフォが使えるので
はないでしょうか。

上記の代わりに volatile 修飾したフラグを使ってもWindowsではある程度信頼できます
。
いずれにしても、スレッド側は参照するだけとし、設定するのは管理側の親が行うのが良
いのではないでしょうか。

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