Ryuzのプログラミング講座
<第2回 MFCで制御アプリ>

 第2回が来てしまいました。第1回から2年ぐらいでしょうか?
私、MFCで制御系のアプリをかかされる羽目になりその際のテクニックなどをかいときます。


MFCでスレッドを使う

 MFCでスレッドを使うには AfxBeginThread を利用してCWinThread型のオブジェクトを作るわけですが、通信アプリとか制御系のアプリの場合しばしば「スレッドの終了を待ちたい!」と言う要求が発生します。で、そのやり方です。


class CHogeHoge
{
public:
   CHogeHoge();		// コンストラクタ
   ~CHogeHoge();	// デストラクタ
   void Start(void);	// スレッドの開始
   void Stop(void);	// スレッドの終了

protected:
   static UINT ThreadEntry(LPVOID pParam);	// スレッド開始位置
   UINT ThreadProc(void);		// スレッド処理

   CWinThread* m_pThread;	// スレッドオブジェクト
   HANDLE m_hEventStop;		// 終了イベント(CEventでも良いんだけどね
				// 私の趣味でAPIじかうち)
};


// コンストラクタ
CHogeHoge::CHogeHoge()
{
    // 終了イベント作成
    m_hEventStop = ::CreateEvent(NULL, TRUE, FALSE, NULL);

    m_pThread = NULL; // スレッドオブジェクトのポインタクリア
}


// デストラクタ
CHogeHoge::~CHogeHoge()
{
   // 終了イベント破棄
   ::CloseHandle(m_hEventStop);
}


// スレッドの開始
void CHogeHoge::Start(void)
{
    ASSERT(m_pThread == NULL);
    
    // サスペンド状態でスレッドを作成
    m_pThread = ::AfxBeginThread(ThreadEntry, (LPVOID)this,
			THREAD_PRIORITY_NORMAL,
			0, CREATE_SUSPENDED, NULL);
    m_pThread->m_bAutoDelete = FALSE;	// 自動破棄フラグクリア
    m_pThread->ResumeThread();		// サスペンド解除
}


// スレッドの終了
void CHogeHoge::Stop(void)
{
   ASSERT(m_pThread != NULL);
   
   ::SetEvent(m_hEventStop);	// 終了イベントセット
   
   // スレッド終了待ち
   if ( ::WaitForSingleObject(m_pThread->m_hThread, 2000)
		   				== WAIT_TIMEOUT )
   {
      // スレッド強制停止
      // (絶対に停止するなら WaitForSingleObjectで INFINITE も可)
      ::TerminateThread(m_pThread->m_hThread, 0xffffffff);
      ::AfxMessageBox(_T("スレッド強制停止");
   }
   
   // スレッドオブジェクト破棄
   delete m_pThread;
   m_pThread = NULL;
   
   ::ResetEvent(m_hEventStop);	// 終了イベントクリア
}


// スレッド開始位置
UINT CHogeHoge::ThreadEntry(LPVOID pParam)
{
    CHogeHoge* pHogeHoge = (CHogeHoge*)pParam;	// 自オブジェクトの取得
    
    return pHogeHoge->ThreadProc();		// スレッド処理
}


// スレッド処理
UINT CHogeHoge::ThreadProc(void)
{
    // 終了イベントがセットされるまでループ
    while ( ::WaitForSingleObject(m_hEventStop, 0) == WAIT_TIMEOUT )
    {
        // 後はここで目的のイベントとm_hEventStopを
        // WaitForMultipleObjectsで待つなり
        // Sleep() を挟んで(いいかげんな)周期スレッドとするなり
        // 好きに処理をしましょう。
    }
    
    return 0;
}

 コツはとりあえずサスペンド状態で生成してm_bAutoDeleteをクリアしてしまうことです。これやらないとスレッドが勝手にdeleteされてオナー側のクラスとのやり取りが面倒です。はい。
 後、処理スレッド自身から Stop() を呼びたい場合もあるかと思います。そう言う場合はGetCurrentThreadIdなどでIDを取得して自分自身かどうか調べて適当な処理をしてください。





アクティブクラスの扱い

 クラスがスレッドを持つと一つの問題が生じます。それはスレッドを持つクラスはアクティブになる(外から関数を呼ぶなどしなくても非同期に事象が変わる)。
 制御系のアプリケーションではスレッドは大量に発生し、非同期なイベントは大量に発生します。センサが変化した、データを受信した、処理がタイムアウトしたなど全て非同期に発生します。
 アクティブなクラスはしばしば内部の事象を外部に通知しなければなりません。この通知をどうやって別のクラスに行うかが問題となります。いくつか方法があると思います。

 ざっと上げると。

  1. イベントやメッセージキューなどを使う方法
  2. コールバックによる方法
  3. ポーリングによる方法
といったところでしょうか。

 ここで1の方法はオーナーからあらかじめイベントやスレッドあるいはウィンドウハンドルを受け取っておき、SetEvent()、PostThreadMessage(),PostMessage()などで通知を行う方法です。一番簡単なのはPostMessage()を使う方法でしょうか。ただし、これはオーナー側のスレッドがそのメッセージを処理するのがいつかわからないため、メッセージのパラメータに入らないデータの受け渡しにはデータキューなどを作成して間に挟む必要があります。

 2の方法はイメージ的には割りこみ処理に似ています。ただし、コールバック先のアドレスの渡し方が重要になります。単純に関数のポインタを渡すのも手ですが、クラスとして処理したければ、JavaでいうところのInterfaceクラスを作成してしまうのがすっきりします。C言語からC++言語に進んでOOPを勉強された方には釈然としないかも知れません。オーナークラスの型に依存せずに子クラスにオーナークラスのポインタを持たせる術です。C++の場合継承を使います(Javaだとインプリメントと言うけどC++だとどっちっも継承になってしまう)。純粋仮想関数だけのクラスを定義しておき、オーナー側のクラスはインターフェースとなるクラスを継承します。このとき多重継承になってもOKです。純粋仮想関数だけのクラスの継承は意味的にはインプリメントであり、継承ではありませんからオブジェクト指向を壊しません。むしろインターフェースを介さずに直にクラスポインタを持たせると汎用性が失われ、悪い結果となります。

 3番目ですが、これも手です。Windowsアプリケーションなどではアクティブクラスを所有するオーナーとなるクラスはUI(ユーザーインターフェース)部分の制御を行うことが多く、一方でアクティブクラスは実際の制御を行うことが殆どです。ですので単に制御状況を画面に表示したいだけと言うような場合は、制御部分のクラスは黙々と制御を続け、オーナーには通知は行わず、逆にオーナーであるUI部分はタイマなどで一定間隔で状況を監視して画面に表示しても良いわけです。


Ryuzのページに戻る