2002/11/05 その2
マルチスレッドをやらにゃ〜あかんということで、見本を探してみましょう。
http://www.sado.co.jp/system/taka/progress/default.htm
に、なんとプログレスバー付きの見本がありました。これを解析して取り込んでみることとしましょう。
この見本を動かすと以下のようになります。
ProgressTestが、ダイアログベースのアプリケーションとして生成され、そこから、プログレスバー用のダイアログTestが生成されているようです。
ソースをよくみてみると、Testダイアログのなかで、スレッドを生成している事が伺えます
BOOL CProgressDialog::OnInitDialog() { CDialog::OnInitDialog(); // TODO: この位置に初期化の補足処理を追加してください m_ProgressCtrl.SetRange(0, 100); m_ProgressCtrl.SetPos(0); SetWindowText(m_Caption); if (!m_EnableCancel) { m_CancelButton.ShowWindow(SW_HIDE); } DWORD threadID; m_Thread=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CProgressDialog::ThreadProc, (LPVOID)this, 0, &threadID); return TRUE; // コントロールにフォーカスを設定しないとき、戻り値は TRUE となります // 例外: OCX プロパティ ページの戻り値は FALSE となります }
まず、CreateThread をMSDNで調べてみましょう
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to thread security attributes DWORD dwStackSize, // initial thread stack size, in bytes LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function LPVOID lpParameter, // argument for new thread DWORD dwCreationFlags, // creation flags LPDWORD lpThreadId // pointer to returned thread identifier );
lpThreadAttributes | 取得したハンドルの子プロセスへの継承を許可するかどうかを決める、SECURITY_ATTRIBUTES 構造体へのポインタを指定します。NULL を指定すると、ハンドルは継承されません。 | NULL |
dwStackSize | 新しいスレッドが持つスタックのサイズを、バイト単位で指定します。利用可能なメモリのサイズよりも大きなサイズを指定すると、CreateThread
関数は失敗します。 0 を指定すると、デフォルトのサイズになります。スタックのサイズは、必要に応じて大きくなります。 |
0 |
lpStartAddress |
新しいスレッドの開始アドレスを指定します。通常、WINAPI
呼び出し規約で定義された関数のアドレスを指定します。この関数の書式を次に示します。 |
(LPTHREAD_START_ROUTINE)CProgressDialog::ThreadProc, |
lpParameter | スレッドに渡す 32 ビット値を指定します。 | (LPVOID)this |
dwCreationFlags | スレッド作成に関する制御フラグを指定します 0、またはCREATE_SUSPENDEDを指定します |
0 |
lpThreadId | DWORD 型の変数へのポインタを指定します。この変数に、スレッド ID が格納されます。 | &threadID |
戻り値:
関数が成功すると、新しいスレッドのハンドルが返ります。
関数が失敗すると、NULL が返ります。
だそうだ。
関数の戻り値のスレッドハンドルと、第6引数のスレッド
ID は何が違うんだろうね?
まず、型がちがう。デバッグモードで動かして、値を見てみよう。
型 | 元の型 | 値 |
HANDLE |
void * | 0x000000b4 |
lpThreadId | unsigned long | 1000 |
どうも、スレッドIDは、本当にIDみたいだ。
第3引数は、スレッドを生成してそこで走らすメソッドの先頭アドレスと思われる。LPTHREAD_START_ROUTINE を調べていくと、関数ポインタになってました。
2002/11/06
では、この見本を昨日作った関数で呼んでみることにしましょう。三つ目のボタンを作って、それをクリックしたら、スレッドを生成してみるようにします。
void CLesson1Dlg::OnButton3()
{
// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
HANDLE Thread;
DWORD threadID;
Thread=CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)
CLesson1Dlg::ThreadProc,
(LPVOID)this, 0, &threadID);
}
// バックグランドのスレッド実行関数
DWORD WINAPI CLesson1Dlg::ThreadProc(CLesson1Dlg* dlg){
DWORD ret;
int i;
for(i=0;i<2;i++){
Sleep(1000);
Beep(440,100);
}
ret = 0;
return ret;
}
lesson1Dlg.h
static DWORD WINAPI ThreadProc(CLesson1Dlg* dlg);
ヘッダーに関数を追加するのを忘れていましたが、追加してもしばらくコンパイルが通りませんでした。
:\data\VC++\20021105 ダイアログ奮戦記\lesson1\lesson1Dlg.cpp(126) : error C2440: 'type cast' : '' から 'unsigned long (__stdcall *)(void *)' に変換することはできません。(新しい動作 ; ヘルプを参照)
スコープ内でこの名前を持つ関数でターゲット型に一致するものはありません。
これは、ThreadProcをstatic宣言していなかったからのようです。WINAPI呼び出しする時は、staticでないとダメなのかナな〜。今はまだよく解っていませんが、見本がそうなっていたので合わせてみました。
どうやらちゃんと動いたようです。ThreadProcが動いている間も他のボタンがきちんと効きます!
メッセージも出ていますので、良いみたいです。