プロが教えるわが家の防犯対策術!

<プログラム環境>
Windows XP
VC++6.0
MFC AppWizard(exe)
ダイアログベース

<質問概略>
CWinThread*を使って無限ループのスレッドを作ったのですが、
無限ループのスレッドをもう一つ作り、同時に実行しようとするとアクセスバイオレーションのエラーでます。
複数スレッドの作り方を教えていただけますと幸いです。

<質問詳細>
現状の正常に実行できるソースの必要最小限を書きます。
<.h>
class CMyDlg : public CDialog{
public:
  static UINT ThreadFunc( LPVOID pParam );
  void Thread(); // スレッドの処理
protected:
  CWinThread* m_pThread;//スレッドのアドレス
};

<.cpp>
void CMyDlg::OnButton(){
  m_pThread = AfxBeginThread( ThreadFunc, this );
  for(;;) /*無限ループ処理1*/ ;
}
UINT CMyDlg::ThreadFunc( LPVOID pParam ){
  ((CMyDlg*)pParam)->Thread();
  return 0;
}
void CMyDlg::Thread(){
  for(;;) /*無限ループ処理2*/ ;
}

これに、
void CMyDlg::Thread2(){
  for(;;) /*無限ループ処理3*/ ;
}
のようなスレッドを追加したいのですが全然出来ません。
宜しければご指摘お願い致します。

A 回答 (12件中1~10件)

<.h>


class CMyDlg : public CDialog{
public:
  static UINT ThreadFunc1( LPVOID pParam );
  static UINT ThreadFunc2( LPVOID pParam );
  // これは不要
  // void Thread(); // スレッドの処理
protected:
  CWinThread* m_pThread1;//スレッドのアドレス
  CWinThread* m_pThread2;//スレッドのアドレス
};

<.cpp>
void CMyDlg::OnButton(){
  m_pThread1 = AfxBeginThread( ThreadFunc1, this );
  m_pThread2 = AfxBeginThread( ThreadFunc2, this );
  // これも不要
  // for(;;) /*無限ループ処理1*/ ;
  // メッセージポンプを止めてしまうのは得策ではありません
}

UINT CMyDlg::ThreadFunc1( LPVOID pParam ){
  // このような使い方をしない
  //((CMyDlg*)pParam)->Thread();
  for ( ;; ) {
    // 何かの処理
  }
  return 0;
}

UINT CMyDlg::ThreadFunc2( LPVOID pParam ){
  // このような使い方をしない
  //((CMyDlg*)pParam)->Thread();
  for ( ;; ) {
    // 何かの処理
  }
  return 0;
}

別のスレッドから CMyDlgへ何らかのアクションを起こすなら
PostMessageやSendMessageでやり取りしましょう
やり取りするメッセージはRegisterWindowMessageなどで作成します
これを CMyDlgのメッセージマップで振り分けるようにしましょう
    • good
    • 0
この回答へのお礼

redfox63様、有難う御座います。
PostMessage、SendMessage、RegisterWindowMessageのリファレンスは調べたのですが、今ひとつ理解できませんでしたが、教えていただいた通りにコーディングして、実行できました。
しかし、2つ問題が発生しました。
<問題1>
void CMyDlg::Thread(){
  for(;;) /*無限ループ処理2*/ ;
}
このメンバ関数内で他のメンバ関数を呼び出していたのですが、
「静的な中で呼び出しがおかしい」のようなエラーが出ます。
また、ダイアログで宣言したエディットボックスのメンバ変数(CString)を使用することにも同様のエラーがでます。

<問題2>
MessageBox()をコーディングするとエラーがでます。

上記2点の問題によって、スレッド内の計算結果や、エラー発生時の警告等を文字列(CString)で表示する手段が分からなくなりました。教えていただいた手法ではスレッド内で文字列を表示することは出来ないのでしょうか?
宜しければご指摘の程お願いいたいます。

お礼日時:2008/11/06 01:35

>「静的な中で呼び出しがおかしい」のようなエラーが出ます。


> static UINT ThreadFunc1( LPVOID pParam );
> static UINT ThreadFunc2( LPVOID pParam );
上記は静的な関数なので、この中で呼び出す物も、
それと同じ実行範囲出なければ行けません。
呼び出そうとするクラスのメソッドも静的にする必要があります。

> MessageBox()をコーディングするとエラーがでます。
どんな?
    • good
    • 0
この回答へのお礼

aris-wiz様、有難うございます。
エラーは「静的でないメンバ関数の中で呼び出しが正しくありません。」
でした。
MessageBox()を静的にすることができるのでしょか?

お礼日時:2008/11/06 14:07

スレッドのプロシージャは クラスのメンバーより外部の単純関数のほうがメンテナンスしやすいでしょう



UINT ThreadProc1( LPVOID );
UINT ThreadProc2( LPVOID );
といった具合の宣言

UINT ThredProc1( LPVOID lpParam )
{
  for( ;; )
    ;
  // 何らかの異常が起きた際には return 1 などを返す
  return 0;
}
といった具合の実装にします

ダイアログとやり取りするなら ダイアログのメンバーに
static UINT m_umsgType1;
afx_msg LRESULT OnType1( WPARAM, LPARAM );
などを宣言しておき

CPPファイルに
UINT CMyDlg::m_umsgType1 = RegisterWindowMessage("識別用の文字列");
を記述します

BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
  //{{AFX_MSG_MAP(CMyDlg)
  //}}AFX_MSG_MAP
END_MESSAGE_MAP()
といった記述があると思います
  //}}AFX_MSG_MAP
の次の行に
  ON_REGISTERED_MESSAGE( CMyDlg::m_umsgType1, OnType1 )
を記述

afx_msg LRESULT CMyDlg::OnType1( WPARAM wParam, LPARAM lParam)
{
  // ここで wParamやlParamから必要な情報を得て
  // ダイアログへ表示する
  // たとえば wParamに文字列のアドレスがあるなら
  CString str( (char*)wParam );
  GetdlgItem( IDC_EDI1)->SetText( str );
  // といった具合で
  return S_OK;
}

Thread側からメッセージを投げるときは
HWND hDlg = ((CMyDlg)lpParam)->m_hWnd;
char buf[256];
sprintf( buf, "%d計算結果", ncalc );
SendMessage(hDlg, CMyDlg::m_umsgType1, (WPARAM)buf, NULL );
といった具合にします

この回答への補足

質問を補足させて頂きます。

void CMyDlg::OnButton(){
  m_pThread1 = AfxBeginThread( ThreadFunc1, this );
  m_pThread2 = AfxBeginThread( ThreadFunc2, this );
  for(;;){
    //メッセージポンプをここに挿入
    MSG msg;
    while(::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)) AfxGetApp()->PumpMessage();
    if(/*グローバル変数BOOL blEnd == TRUE) break;
    m_Display =csMessage;
    UpdateData(FALSE);UpdateWindow();
    Sleep(1);
  }
}
上記のように、OnButton()はスレッド二つを起動したら無限ループに入り、1ms毎にダイアログエディットボックスメンバ変数m_Displayに文字列csMessageを代入し、表示させています。この使いかたも良くないでしょうか?
また、ThreadFunc1()とThreadFunc2()の戻り値を受け取る方法が分かりません。(スレッドがどこに値を返しているのか分かりません・・。)
よろしくお願いします。

補足日時:2008/11/06 15:15
    • good
    • 0
この回答へのお礼

redfox63様、有難うございます。
教えて頂いた、
>UINT ThreadProc1( LPVOID );
>UINT ThreadProc2( LPVOID );
>といった具合の宣言

>UINT ThredProc1( LPVOID lpParam )
>{
>  for( ;; )
>    ;
>  // 何らかの異常が起きた際には return 1 などを返す
>  return 0;
>}
の部分をやってみたのですが、質問文にあるOnButton()内の、
m_pThread = AfxBeginThread( ThreadFunc, this );
の部分で、「2 のオーバーロードは 1 番目の引数を 'unsigned int (void *)' から要求の型に変換できません。」というエラーがでるようになりました。

宣言で「static」を消しただけでは、ダメなのでしょうか?
宜しければご指摘の程お願い致します。

お礼日時:2008/11/06 14:28

 スレッドでは、自身の処理だけを行い、画面表示はメインスレッドにやらせたほうがいいです。

MFCはスレッドセーフには作られていないので、もし、スレッド内からウインドウオブジェクトに直接アクセスすると、メインスレッドとの処理がぶつかり思わぬ現象になります。
 この事は、自身のクラスについてもいえます。メンバー変数を各スレッドが任意のタイミングで書き込みが行なわれることを考慮しなければなりません。

 スレッド内から、他のメンバー関数を呼べないのは、スレッドの関数にstatic がついていることに着目してください。class 内で、メンバー変数、メンバー関数にstatic をつけると、インスタンスではなく、クラスの変数・関数になります。

 スレッド内で発生した通知をどうすればいいかというと、
1. スレッドからメインスレッドへの受け渡し情報を検討
まとめて構造体にすればよいでしょう。
2. スレッド内でnew で受け渡し構造体バッファ確保
3. バッファ内に情報をセット
4. PostMessage の引数に、バッファアドレスを入れてコール
5. メインスレッドは、メッセージを受け取ると、構造体を参照してメッセージ表示を行う。ハンドラから抜けるときに、構造体をdelete で開放

 これをもう一歩進めると、受け渡し構造体をクラス化して、スレッドをそのクラスに実装し、元のクラスからはスレッドを遮蔽することができます。UIとロジックの分離という意味もあります。
    • good
    • 0

お礼の部分は解決したのですね



Threadからの戻り値を取得したい場合は
AfxBeginThreadのdwCreateFlagsまで設定するようにしておいて
m_pThread1 = AfxBeginThread( ThreadFunc1, this,THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED );
m_pThread1->m_bAutoDelete = false;
m_pThread1->ResumeThread();
といった具合にします

スレッドが終了したどうかは
UINT dwExit1 = 0;
if ( ( dwExit1 = ::GetExitCodeThread( m_pThread1->m_hThread ) ) != STILL_ACTIVE ) {
  // dwExitをチェックする
  delete m_pThread1;
  m_pThread1 = NULL;
}
といった具合でしょう

または m_pThread1->m_hThreadを別のメンバー変数に記憶しておいて
GetExitCodeThreadに使いましょう

VC++付属のMSDNで
CWinThreadクラスの概要やそのページの下のほうにあるリンク先を熟読しましょう

イベントハンドラ内のメッセージポンプはあまり感心しません
1msごとの更新も多すぎるような気がします
人間さんはそんなに速く更新されても理解できません
適切なタイマーイベントの実装のほうが現実的な気がしますが
    • good
    • 0
この回答へのお礼

redfox63様、有難う御座います。
VC++付属のMSDNでCWinThreadクラスの概要と複数のリンク先を熟読しまして、戻り値の取得の仕方はよく理解できました。
計算結果が更新された時だけ表示を更新するようにして、
さらにこれをスレッドにして、スレッドからメッセージを投げるようにしようと思います。
前回のお礼内容については、まだ解決できておりません。
m_pThread = AfxBeginThread( ThreadFunc, this );
の部分で、「2 のオーバーロードは 1 番目の引数を 'unsigned int (void *)' から要求の型に変換できません。」というエラーがでるのですが、何が原因でしょうか?
宜しくお願い致します。たびたび申し訳ありません・。

お礼日時:2008/11/06 22:29

> m_pThread = AfxBeginThread( ThreadFunc, this );


> の部分で、「2 のオーバーロードは 1 番目の引数を 'unsigned int (void *)' から要求の型に変換できません。」

ThreadFuncでは無くThreadFunc1やThreadFunc2を指示しないといけませんがその点はあってますでしょうか

ThreadFunc1/ThreadFunc2はCMyDlgのメンバー関数からはずしましょう
メンバー関数にするのであれば static属性にしないとダメですよ

ヘッダーの
class CMyDlg : public CDialog
{
// 諸々の定義
};

の外に宣言します
UINT ThreadFunc1(LPVOID);
UINT ThreadFunc2(LPVOID);
といった具合です

CPPファイルのほうも
UINT CMyDlg::ThreadFunc1(LPVOID lpParam)
{
}
では無く
UINT ThreadFunc1(LPVOID lpParam)
{
}
といった具合にCMyDlgクラスのメンバーから外しましょう
    • good
    • 0
この回答へのお礼

redfox63様、有難うございます。
教えて頂いた通りに修正してエラー無くなりました!
ANo.3で教えて頂いた、ダイアログとやり取りする部分に進めてコーディングしたのですが、メッセージマップのところでエラーが出ます。
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
  ON_REGISTERED_MESSAGE(CMyDlg::m_umsgType1,OnType1) //..(1)
END_MESSAGE_MAP()
エラーは(1)に対して、「'OnType1' : 定義されていない識別子です。」と
「type cast' : 'int *' から 'long (__thiscall CWnd::*)(unsigned int,long)' に変換することはできません。」です。
何が原因でしょうか?
宜しければご回答お願い致します。

書いたコードを以下に書き写しましたのでご参照願います。
<.h>
class CMyDlg : public CDialog
{
public:
  static UINT m_umsgType1;
  afx_msg LRESULT OnType1( WPARAM, LPARAM );
}
<.cpp>
UINT CMyDlg::m_umsgType1 = RegisterWindowMessage("識別用の文字列");
afx_msg LRESULT CMyDlg::OnType1( WPARAM wParam, LPARAM lParam)
{
  CString str( (char*)wParam );
  SetDlgItemText(IDC_EDIT1,str);
  return S_OK;
}

お礼日時:2008/11/07 17:22

メッセージマップが違いますよ


> BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
これは CAboutDlgクラスのメッセージマップです

これではなく
BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
のほうのメッセージマップへ追加しましょう

この回答への補足

redfox63様、有難うございます。
メッセージマップ修正してエラー無くなり、
スレッドからメッセージを表示させることができました。

スレッドの終了と終了の確認はどのようにしたら良いでしょうか?
1.OnButton()を実行し、スレッド起動
2.OnStop()を実行し、スレッド終了
3.以降OnStop()を実行すると、「スレッドありません」と表示
という動作にしたいのですが、3.がうまくいきません。
何度OnStop()を実行しても、「スレッドありません」と表示されません。
原因はなんでしょうか?
プログラムを実行し、終了させた後も、スレッドが残っているみたいで、毎回タスクマネージャで強制終了させています・・。
宜しければご指摘の程お願い致します。

以下、現在のコードです。(本当はスレッド2つ起動させてます)
BOOL m_bThread1 = FALSE;//グローバル変数
void CMyDlg::OnButton(){
  m_bThread1 = TRUE;
  m_pThread1 = AfxBeginThread( ThreadFunc1, this );
  for(;;){
    while(::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)) AfxGetApp()->PumpMessage();//メッセージポンプ
    if(m_bThread1 == FALSE) break;
  }
}
void CMyDlg::OnStop(){
  if( !m_pThread1 ){
    MessageBox("スレッドありません");
    return;//終了
  }
  m_bThread1 = FALSE;//スレッドを止める
}
UINT ThreadFunc1(LPVOID lpParam)
{
  while(m_bThread == TRUE){
    /*処理*/
  }
  return 0;
}

補足日時:2008/11/08 18:33
    • good
    • 0

このコードでは判定できないでしょう


Threadが終了しても m_pThread1は NULLにはなりません
NULLにするのはプログラマの責任です

ワーカースレッドとメインスレッド両方から操作するフラグが必要ですからちょっとした工夫が必要になります

本格的にやるなら CriticalSectionやMutexなどの同期オブジェクトを遣うことになります

今回のようにただループを脱出したいだけなら 単純なlong型変数を使う方法が良いでしょう

メンバー変数に Threadのハンドルを保持しておきます
HANDLE m_hThread1= NULL, m_hThread2 = NULL;

クラスメンバーではない long型のフラグ用変数を用意します
long nFlag1 = -1, nFlag2 = -1;

スレッド起動前に 0で初期化します
nFlag1 = 0;
m_pTherad1 = afxBeginThread(...);
m_hTHread1 = m_pThread1->m_hThread;

スレッドのforループを
for( ; !nFLag1 ; )
  ;
にしておきます

OnStopイベントで
if ( m_pThread1 ) {
  // nFlag1を0以外にして Threadのループを終了させる
  // InterlockedIncrement複数のスレッドから参照されても安全に
  // 変更できるAPIです
  ::InterlockedIncrement( &nFlag1 );
  for( int n = 0; n < 100; n++ ) {
    // スレッドの終了を監視
    if ( WAIT_OBJECT_0 == WaitForSngleObject( m_hThread1, 10 ) ) {
      // 終了できたなら ポインタをNULLにする
      // m_bAutoDeleteをFalseに設定したならば
      // delete m_pThread1; を実行する必要があります
      m_pThread1 = NULL;
      return;
    }
  }
  // 100回チェックしてもダメなら エラーが起きていそうです
  AfxMessageBox( "Thread1が終了できません");
} else {
  AfxMessageBox("Thread1はすでに終了しています");
}

といった具合で ・・・
    • good
    • 0
この回答へのお礼

redfox63様、有難う御座います。
教えていただいた通りにして、スレッド終了できました。

しかし、スレッド終了後、ダイアログの「OK」や「キャンセル」や「×」ボタンを押して、ダイアログ終了後、再度コンパイルすると、「書き込みモードで開けません」というエラーが出ます。
この状態でタスクマネージャを開くと、"プロジェクト名".exeというプログラムが実行されていて、これを強制終了すると、コンパイルエラーが無くなります。
このエラーはスレッドが終了出来ていない事が原因だと思っていたのですが、スレッド終了しても症状が治りませんでした・・。

これは何が原因なのでしょうか?
この症状は、スレッド終了・ダイアログ終了後にすぐコンパイルしてもエラー出ず、数十秒後にコンパイルすると上記エラーが出ます。このエラーはマルチスレッドにしてからもう50回以上出たと思います。

ご指摘の程宜しくお願いします。

お礼日時:2008/11/16 19:59

AfxBeginTheradで作成したスレッドの終了判定を


WaitForSingleObjectでやっていましたが CWwinThreadは通常m_bAutoDeleteがTrueで作成されます
このフラグが Trueですと制御関数の終了(またはCWinThreadの派生クラスの破棄) に伴いスレッドのハンドルも閉じられてしまって WaitForSingleObjectで判定が出来ませんでした
# WaitForSingleObject関数が失敗してしまうため ・・・

スレッド作成時に
m_pThread1 = AfxBeginThread( ThreadFunc1, this );
m_pThread1->m_bAutoDelete = FALSE;
を実行しておいて

Threadの終了確認側で m_pThread1をDeleteするようにし見てください
OnStopイベントで
if ( m_pThread1 ) {
  // nFlag1を0以外にして Threadのループを終了させる
  // InterlockedIncrement複数のスレッドから参照されても安全に
  // 変更できるAPIです
  ::InterlockedIncrement( &nFlag1 );
  for( int n = 0; n < 100; n++ ) {
    // スレッドの終了を監視
    // ハンドルをm_pThread1->m_hTHreadに変更
    if ( WAIT_OBJECT_0 == WaitForSngleObject( m_pTherad1->m_hThread1, 10 ) ) {
      // 終了できたなら ポインタをNULLにする
      // m_bAutoDeleteをFalseに設定したならば
      delete m_pThread1; // を実行する必要があります
      m_pThread1 = NULL;
      return;
    }
  }
  // 100回チェックしてもダメなら エラーが起きていそうです
  AfxMessageBox( "Thread1が終了できません");
} else {
  AfxMessageBox("Thread1はすでに終了しています");
}

OnButton1の中のメッセージポンプでWM_CLOSEなどの処理をしてしまっているがfor文自体を抜けれないため
『書き込みモードで開けません』のエラーになるのではないかと考えられます
    • good
    • 0
この回答へのお礼

redfox63様、有難うございます。
教えて頂いた通りにしたのですが、エラー出ました。

一度コンパイルが成功しても、数分放置してからまたコンパイルすると「fatal error LNK1168: 書き込みモードで Debug/試作1.exe を開けません」がでます。
タスクマネージャのプロセスで確認すると試作1.exeが二つ実行されていて、共にCPUが50前後、メモリ使用量は7,028kバイトと7,032kバイトでした。
暫く放置した場合だけこのエラーが出ます。
この原因はなんでしょうか・・?

よろしくお願いいたします。

お礼日時:2008/11/17 15:32

IDEからデバッグ実行を行い、スレッドを起動、Stopボタンでスレッドを終了、CloseボタンでAppを終了


といった手順でデバッグモードが終了できるのか確認しましょう

どうもAppのウィンドウが閉じてはいるが App自体が終了していないのが原因に思えます
    • good
    • 0
この回答へのお礼

redfox63様、有難うございます。
デバッグモードで確認したところ、CloseボタンでAppを終了してもデバッグモードが終了出来ませんでした。

App自体を終了させるにはどうしたら良いのでしょうか?
ご指摘の程よろしくお願い致します。

お礼日時:2008/11/18 11:53

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!