antenn-a
company profile

MFC航海日記 五十四日目(マルチスレッド)


さて、描画も出来る様になったし、サムネイルも作ることが出来たのでいよいよアプリケーションに組み入れようと思うんだけど、やっぱりスレッドを使いたいやね。
スレッドというのは、処理を複数のルーチンに分けて、優先順位を決める事で優先順位の高い奴が終わってCPUが暇してる時に優先順位の低い奴が動くという物。
ま、そういうのをスレッドプログラム、とか言うんだけど、MFCでスレッド使うの初めてだしドキドキですな。
今考えているのは、アプリにファイルがドロップされたら、その画像を描画サイズにサイズ変更してサムネイル画像として保存し、そのサムネイルを読んで表示するという風にしたい。
一番最初に作った描画プログラムは(公開して無いけど)ファイルを読んで描画時だけ縮小して表示してたんだけど、今使ってるデジカメ君は 1200 × 1600 とかいうサイズで撮ってたりするので、サイズがキツイのだ。
1個200Kぐらい有るので、100枚読み込もうと思っただけで2M近くあるわけ。
こりゃーメモリ一杯食って嫌だなあという事で、120 × 97というサイズに縮小したデータを作る事で表示を楽にしたいなあと。
すると、
1)メインのスレッド(そのまま)
2)サムネイル作成用スレッド(ドロップされたらスレッドを作って、暇なときにサムネイルを作って終わる)
3)表示用スレッド
の、3つ構成かなあと。

prev


スレッドを作るのは
AfxBeginThread() とかいう関数で出来るっぽいし、幾つか有るけどMFC的にはコレっぽい。
アンダースコア(_)で始まるSDK関数も有るけど(CreateThreadとか)今回は AfxBeginThread()でやろうかなーと
で、実装してみましたよ。
ま、すぐ出来たんだけど、、
AfxBeginThread( foo, &struct );
という感じですな。引数は第一引数に関数名、第二引数にパラメータという感じで呼び出しをすると、このfooに書いた関数が別スレッドとして動くという仕組み。
もう少し具体的なコードを書くと、
class MakeSumPicsInfo
{
public:
	HDROP     m_hDropInfo;	  // Dropされた情報保持用
	CRect     m_PicWinRct;    // サムネイル画像サイズ
	CString   PicturePath;    // 元画像ファイルの在り処
};

UINT t_MakeSumPics( LPVOID pParam )
{
    // pParam で受け取った構造体の型変換
    MakeSumPicsInfo*    p_MakeSumPicsInfo = ( MakeSumPicsInfo* )pParam;
    //
    /*
    処理
    */
}

void EMainDialog::OnDropFiles(HDROP hDropInfo){
    MakeSumPicsInfo  m_MakeSumPicsInfo;
    CRect            rct;
    CString          path;
    
    m_PictureWindow.GetWindowRect( rct );
    path = "hogehoge\\hogehoge\\";
    
    m_MakeSumPicsInof.m_hDropInfo = hDropInfo;
    m_MakeSumPicsInof.m_PicWinRct = rct;
    m_MakeSumPicsInof.m_hDropInfo = path;
    
	  
    /* サムネイルの作成スレッドを起動 */
    AfxBeginThread( t_MakeSumPics, &m_MakeSumPicsInfo );
    
    CDialog::OnDropFiles(hDropInfo);
}
						


prev


実はこれは非常に駄目な書き方をしているところが1個有って、書き方の都合上こうなっちゃったんだけど、駄目です。
上のはコピー&ペーストしないで下さい!
先にそれを言っておいて、幾つかルールを紹介
1)呼び出す関数は必ず UINT foo( LPVOID pParam ) の様に、UINTを返す、LPVOID を引数にするグローバルな関数で有ること
2)渡すパラメータは予め宣言しておき、消えない様にしておく事

上の例ではOnDropFiles()の先頭で渡す構造体の宣言をしているが、これは駄目!
実際はt_MakeSumPics()が呼ばれた直後にOnDropFiles()に返ってきて、この関数は終わります。
って事は、スコープが外れちゃうので、ここで宣言したm_MakeSumPicsInfo のメモリ領域は開放されちゃうわけ。
で、開放された後に、先ほど作ったスレッド関数からアクセスしようとすると・・・
ま、当然駄目ですよね。なので、本当は関数内じゃ無くて、クラスのメンバとして宣言しておくのが良いと思います。
ちなみに、何故かグローバル宣言しても駄目で、タイミングが悪いと落ちちゃうという現象が有ったが、これは謎。
良いと思うんだけどなー。取り合えず、クラスのメンバにしたら大丈夫になりますた。

prev


上の例で、
// pParam で受け取った構造体の型変換
MakeSumPicsInfo* p_MakeSumPicsInfo = ( MakeSumPicsInfo* )pParam;

こんな事してますが、これ重要です。これから言える事は、ポインタなら何でも渡せるんジャンって事。
つー事で、こんなことしてみると、
UINT t_MakeSumPics( LPVOID pParam )
{
    // pParam で受け取った構造体の型変換
    EMainDialog*    pThis = ( EmainDialog* )pParam;
    //
    /*
    処理
    */
}

void EMainDialog::OnDropFiles(HDROP hDropInfo){
    /* サムネイルの作成スレッドを起動 */
    AfxBeginThread( t_MakeSumPics, this );
    
    CDialog::OnDropFiles(hDropInfo);
}
					
こうしてやると、pThis->foo(); とかやって別スレッドからメンバ関数や変数にアクセス出来る様になりまうす。
でもー、どうもこの呼び方は怪しさ満点で、不思議なタイミング問題が勃発してしまうのですよ。
サムネイルを読んで描画するスレッドは本当はこうやって実装したかったんですが、何か良く分からないタイミング問題が一杯出たので辞めました。
結局、そっちは昔紹介した PumpMessages って奴で実装しちゃいました。
時間が有れば、もう少し追えるんだけど、むーん、そこはサンデー(VC)プログラマの性!無理!
で、これはVC7からの話だけども、どうもコントロールの初期化その他のタイミングが分からないのですわ。
昔は一括で OnInit...でやってたんですが、VC7だとここでやるとエラーが起きて落ちちゃう事が有るのです。
良く分からないですね・・・。多分、俺の使い方がマズイのですけどもねえ。ふー。
上に書いた自身のポインタを渡す奴は、シンプルな物であれば実装可能ですよ。
ただ、ウィンドウ生成と同時に作ると、ワケわからん事になったりします。やですねー(そんなの紹介するなよ・・・)

prev


さて、スレッドを作って困ったのはスレッド起動中にウィンドウ消されたらどうしようって事。
で、実際問題、これが上手くWorkしてるかどうか怪しいのだけども、
WaitForSingleObject() なる物を呼んでみた。
今回作っているのは、SDI アプリにダイアログを張ってその上で行っているので、ダイアログの DefWindowProc をオーバーライドしてそこでWM_DESTROY を捕まえてる。
LRESULT EMainDialog::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	switch( message ){
        case WM_DESTROY:
			if(	g_LoadListThreadWorkingFlag == TRUE ){
				g_EndThreadFlag = TRUE;
				WaitForSingleObject( g_ThreadHandle, INFINITE );
	            // return (0L);
			}
			else if( g_MakeListThreadWorkingFlag == TRUE ){
				g_EndThreadFlag = TRUE;
				WaitForSingleObject( g_ThreadHandle, INFINITE );
			}
			
			PostQuitMessage(0);
			break;
	}

	return CDialog::DefWindowProc(message, wParam, lParam);
}
						
こんな具合です。引数に渡している g_ThreadHandle はグローバル変数で、AfxBeginThread()の戻り値ですね。
これを呼ぶと、スレッドが終わるまで待つ・・・かと思ったらそうでも無くて、どっか都合の良いところで終わらせている匂いがするのですよ。
エラーは出ないんですけどねぇ。うーん。謎が多いなぁ。

prev


というワケで、今回はマルチスレッドでした。
実際の所、スレッドにはもっと複雑な問題が有って、同期という奴ですね。
複数のスレッドで同じ変数にアクセスする時や、スレッドに順番を守らせたいとか、そういう事をしようとすると非常に複雑になります。
今回のも、同期を取れば解決した問題かも知れないんですが、面倒なので辞め(おいおい)
ま、目的は果たせたしー(いいわけ)
えーと、今回もサンプル無いっす。良いサンプルが思いつかなくて・・・。
次回かその次ぐらいに、現状の作りかけPicture Albumソースを載せますのでそれ見てください。
はっきり言って、超汚いソース(色々実験しちゃったから・・・)ですけどね。すんまそん。


antenn-a

prev next


antenn-a