レガシー Windows API(概要)について

ここで紹介しているAPIは、Windows95、Windows NT3.5時代のAPI群であり、最新のAPIは含まれていないことに注しして欲しい。なお、当時の解説書は現在よりもより詳細に解説しているため、参考になるはずである。

  • レイアウトを変換する際、崩れているページがあります。
  • 概要およびSampleコードはありますが、APIの解説はありません。

ご要望があれば旧版のWin32 API(HELP形式)をお譲りします。問い合わせフォームより申し込みください。

Windows API プロセスとスレッドの概要

Microsoft(R) Windows(TM) アプリケーションは複数のプロセスで構成でき、 各プロセスは複数のスレッドで構成できます。Microsoft(R) Win32(R) アプリケーション プログラミング インターフェイス (API) はマルチタスクをサポートしており、 複数のプロセスやスレッドを同時に実行できます。このトピックでは、 プロセスとスレッドを説明し、 その作成方法と使い方も説明します。

次に示すトピックでは、 プロセスとスレッドについて説明しています。

マルチタスク

スケジュール

スケジュール優先順位

スレッドの作成

スレッドの実行の中断

複数のスレッドの実行の同期

スレッド ローカル記憶域

スレッドの終了

複数のスレッドとGDIオブジェクト

プロセスの作成

継承

環境変数

StartupInfo

プロセスの終了

プロセスやスレッドのハンドルとID

マルチスレッドMDIアプリケーションの使用

I/Oがリダイレクトされている子プロセスの作成

環境変数の変更

スレッド ローカル記憶域の使用

プロセスとスレッドの関数

マルチタスク

「プロセス」とは、 メモリにロードされて実行されるプログラムの単位です。各プロセスには、 専用の仮想アドレス空間があります。プロセスは、 そのプロセスのスレッドからアクセス可能な、 コード、 データ、 および (ファイルやパイプ、 同期オブジェクトなどの) システム リソースで構成されます。各プロセスは単一のスレッドから起動しますが、 独立して実行されるスレッドを作成して追加できます。

「スレッド」は、 プログラムの一部を実行する単位で、 ほかのスレッドが実行する部分を含む場合もあります。スレッドは、 オペレーティング システムがCPU時間を割り当てるときの基本単位です。各スレッドは、 プロセッサ時間にスケジュールされるのを待つときにコンテキストを保存しておくための構造体のセットを管理しています。コンテキストには、 スレッドのマシン レジスタのセット、 カーネル スタック、 スレッド環境ブロック、 スレッドのプロセスのアドレス空間内のユーザー スタックがあります。1つのプロセスのすべてのスレッドは同じ仮想アドレス空間を共有し、 プロセスのグローバル変数やシステム リソースにアクセスできます。

マルチタスク オペレーティング システムでは、 利用可能なCPU時間を、 CPU時間を必要とする各スレッドに割り当てます。Windowsでは、 Win32 APIはプリエンプティブなマルチタスクを対象として設計されており、 システムは、 競合するスレッド間でCPU時間を小刻みに割り当てることができます。現在実行されているスレッドは「タイムスライス」が経過すると中断され、 別のスレッドが動作するようになります。システムは、 スレッドを切り換えるとき、 中断するスレッドのコンテキストを保存して、 キューに保存されている次のスレッドのコンテキストを復元します。

各タイムスライスは小さいため (20ミリ秒のオーダーです)、 複数のスレッドが同時に動作しているように見えます。マルチプロセッサ システムでは、 実際に複数のスレッドが同時に動作しており、 実行可能なスレッドは利用可能なプロセッサに分散して割り当てられています。しかし、 単一プロセッサ システムでは、 複数のスレッドを使っても、 複数の命令は同時に実行されません。実際、 スレッドが多すぎると、 システムの速度が低下します。

ユーザーにとってのマルチタスクの利点は、 複数のアプリケーションをオープンして同時に作業できることです。たとえば、 あるアプリケーションで印刷したり表を再計算しながら、 別のアプリケーションでファイルを編集できます。

アプリケーション開発者にとってのマルチタスクの利点は、 複数のプロセスを使うアプリケーションを作成でき、 複数の実行スレッドを使うプロセスを作成できることです。たとえば、 ユーザーとの対話 (キーボード入力やマウス入力) を処理するスレッドと、 プロセスのほかの作業を実行する優先順位の低いスレッドを用意できます。入力処理スレッドの優先順位を高くすることによって、 プログラムはユーザーの入力に迅速に対応でき、 さらに、 キーストロークの間のわずかな時間を別のスレッドで効率的に使うことができます。

マルチタスクを使う場合

プロセスのスレッドは、 CreateThread関数やCreateProcess関数を使って、 さらにスレッドやプロセスを作成できます。専用のアドレス空間と専用のリソースを必要とする関数は、 複数のプロセスを使って、 ほかのスレッドの動作から保護してください。関数を独立した実行可能モジュールとして分割し、 かつそれらをまとめて1つのアプリケーションにしようとするのであれば、 複数プロセスという方法がよいでしょう。

複数の作業を同時に実行する場合は、 1つのプロセスで複数のスレッドを作成する方法が役立ちます。たとえば、 複数のウィンドウをオープンするアプリケーションでは、 各ウィンドウの処理を独立したスレッドに割り当てることができます。同じ作業やよく似た作業をいくつか実行するプログラムを構成するには、 複数のスレッドを使うのが簡単です。たとえば、 名前付きパイプ サーバーでは、 パイプに接続されている各クライアント プロセスごとに、 通信を処理するスレッドを作成します。このほかにも、 次のような処理では、 複数のスレッドやプロセスを使います。

複数のウィンドウの入力の処理

複数の通信デバイスからの入力の管理

さまざまな優先順位の作業を区別しなければならない場合。たとえば、 優先順位の高いスレッドで時間的に制約のある作業を処理し、 優先順位の低いスレッドでそれ以外の作業を実行します。

次に示す理由のため、 通常、 マルチタスクを実現するには、 複数のプロセスを作成するよりも、 1つのプロセスの複数のスレッドに作業を分散する方が効率的です。

スレッドのコードはすでにプロセスのアドレス空間にマップされているのに対し、 新しいプロセスのコードはロードしなければならないため、 プロセスよりもスレッドの方がすばやく作成して実行できます。

プロセスのすべてのスレッドは同じアドレス空間を共有し、 プロセスのグローバル変数をアクセスできるため、 スレッド間の通信を簡素化できます。

プロセスのすべてのスレッドは、 ファイルやパイプなどのリソースのオープンされているハンドルを共有できます。

複数のスレッドの場合も複数のプロセスの場合も、 考慮すべき問題と危険性があります。システムは、 プロセスやスレッドに必要な構造体のためのメモリを消費します。スレッドの個数が多いと、 CPU時間が消費されます。また、 同じリソースを複数のスレッドがアクセスする場合、 スレッドを同期して衝突を防がなければなりません。(通信ポートやディスク ドライブなどの) システム リソース、 複数のプロセスが共有するリソース (ファイルやパイプ ハンドルなど) のハンドル、 1つのプロセスのリソース (複数のスレッドからアクセスされるグローバル変数など) を使うときは、 スレッドを同期しなければなりません。(1つのプロセス内や複数のプロセス間で) 複数のスレッドを正しく同期しないと、 「デッドロック」や「競合」などのプログラムのバグにつながります。Win32 APIでは、 複数のプロセスを協調させるための同期オブジェクトと同期関数のセットを用意しています。詳しくは、 複数のスレッドの実行の同期および同期の概要を参照してください。

また、 Win32 APIでは、 複数の作業を実行する方法をもう1つ用意しています。この方法を使えば、 場合によってはマルチスレッドのプロセスは不要になります。この方法には、 オーバーラップI/Oや複数イベントの待機があります。

オーバーラップI/Oによって、 並列に動作する時間のかかるI/O要求を1つのスレッドで複数個実行できます。この非同期I/Oは、 ファイルやパイプ、 シリアル通信デバイス、 テープ デバイスなどで実行できます。ファイルについて詳しくは、 ファイルの概要を参照してください。パイプについて詳しくは、 パイプの概要を参照してください。シリアル通信デバイスについて詳しくは、 通信の概要を参照してください。テープ デバイスについて詳しくは、 テープ バックアップの概要を参照してください。

複数イベントの待機によって、 1つのスレッドがイベントのいずれかが発生するのを待つ間、 スレッドの実行をブロックできます。この方法は、 それぞれが1つのイベントを待つ複数のスレッドを使う方法や、 対象とするイベントを常時チェックしてCPU時間を処理するスレッドを1つ使う方法よりも効率的です。複数イベントの待機について詳しくは、 同期の概要を参照してください。

スケジュール

システム スケジューラは、 プロセッサ時間の次のスライスを競合するスレッドのうちのどれが受け取るかを決定することによって、 マルチタスクを制御します。1タイムスライスは、 一定数のクロック刻みで構成されます。Windowsのスケジューラはプリエンプティブで、 クロック割り込みが発生すると、 制御を受け取って、 現在実行中のスレッドのタイムスライスのクロック刻みの残りカウントを1つ減らします。カウントが0になると、 スケジューラは、 次に実行すべきスレッドを決定します。スケジューラは、 各優先順位で実行可能なスレッドのキューを管理しています。最高の優先順位を持つスレッドが複数あるときは、 優先順位キューの先頭にあるスレッドに次のタイムスライスが与えられます。実行を終了したばかりのスレッドは、 同じ優先順位のスレッドのキューの最後に置かれます。 (セマフォ オブジェクトや入力の待機などで) 中断したりブロックしているスレッドは実行可能ではないため、 ブロックが解除されるまでは、 優先順位にかかわらず、 スケジューラはプロセッサ時間を割り当てません。スケジューラが別のスレッドに次のタイムスライスを与えると、 現在実行中のスレッドのコンテキストは保存され、 マシンの状態は次の実行可能スレッドのコンテキストに従って設定されます。

スケジュール優先順位

各スレッドのスケジュール優先順位は、 次に示す要因から決定されます。

スレッドが属するプロセスの優先順位クラス (高、 標準、 アイドル)

プロセスの優先順位クラス内でのスレッドの優先順位レベル (最低、 低、 標準、 高、 最高)

スレッドの基本優先順位にシステムが適用する、 優先順位の動的な上昇

デフォルトでは、 プロセスの優先順位クラスは「標準」に設定されています。親プロセスは、 CreateProcess関数を使って、 子プロセスの優先順位クラスを指定できます。プロセスの優先順位クラスを設定するには、 SetPriorityClassを使います。また、 プロセスの現在の優先順位クラスを調べるには、 GetPriorityClassを使います。

各優先順位クラスは、 さらに5つの優先順位レベルにわかれています。レベルの範囲は「最低」(-2) から「最高」(2) までです。どのスレッドも、 「標準」(0) 優先順位で起動します。スレッドの優先順位レベルを5つのレベルのいずれかに変更するには、 SetThreadPriority関数を使います。また、 スレッドの現在の優先順位レベルを調べるには、 GetThreadPriority関数を使います。

各スレッドの基本優先順位とは、 スレッドが属するプロセスの優先順位クラス内での各スレッドの優先順位レベルです。スケジューラは、 この基本優先順位を使って、 スレッドの動的な優先順位を判断し、 スケジュールを決定します。スレッドの動的優先順位は、 基本優先順位より低くなることはありません。スケジューラは、 スレッドの動的優先順位を上下して、 スレッドに重要なイベントが発生したときの応答性を改善します。たとえば、 ウィンドウが (タイマ メッセージやマウス移動メッセージ、 キーボード入力などの) 入力を受け取ると、 スケジューラは、 ウィンドウを所有するプロセスのすべてのスレッドの優先順位を上げます。また、 待機が終了すると待機スレッドの動的優先順位が上げられる場合もあります。たとえば、 ディスクI/OやキーボードI/Oを待機している場合、 入力が発生すると、 動的優先順位が上がります。

スケジューラは、 スレッドの動的優先順位を上げた後、 元の優先順位に戻るまで、 スレッドがタイムスライスを1つ完了するごとに優先順位を1レベルずつ下げていきます。

スケジューラは、 このように動的優先順位を上げるほかに、 前景ウィンドウに関連付けられているプロセスの優先順位クラスを上げて、 バックグラウンド プロセスの優先順位以上になるようにします。プロセスがフォアグラウンドでなくなると、 プロセスの優先順位クラスは元の設定に戻ります。ユーザーは、 ほかのウィンドウをクリックするか、 Alt+TabキーやCtrl+Escキーを使って、 いつでも特定のウィンドウの前景での優先順位の上昇を解除できます。

3つの優先順位クラスとその用途を次に示します。

優先順位 意味

HIGH_PRIORITY_CLASS 正しく動作するにはすぐに実行しなければならない、 時間的な制約のある作業を実行するプロセスを示します。高優先順位クラス プロセスのスレッドは、 標準優先順位クラスやアイドル優先順位クラスのプロセスよりも優先して実行されます。たとえば、 [アプリケーションの切り替え]ダイアログ ボックスがあります。このプロセスは、 オペレーティング システムの負荷状態にかかわらず、 ユーザーの要求にすぐに応答しなければなりません。高優先順位クラスでCPUを使うアプリケーションは利用可能なサイクルをほとんど使ってしまう場合があるため、 高優先順位クラスはとくに注意して使ってください。

IDLE_PRIORITY_CLASS システムがアイドル状態で、 より高い優先順位クラスで動作しているプロセスのスレッドがシステムを占有していないときだけスレッドが実行されるプロセスを示します。たとえば、 スクリーン セーバーがあります。アイドル優先順位クラスは、 子プロセスに継承されます。

NORMAL_PRIORITY_CLASS 特別なスケジュールを必要としない通常のプロセスを示します。

REALTIME_PRIORITY_CLASS 最高の優先順位を持つプロセスを示します。リアルタイム優先順位クラスのプロセスのスレッドは、 重要な作業を実行するオペレーティング システム プロセスを含むほかのどんなプロセスよりも優先して実行されます。たとえば、 リアル タイム プロセスが少し時間のかかる処理を実行すると、 ディスク キャッシュがフラッシュされなくなったり、 マウスが応答しなくなる可能性があります。

高優先順位クラスはとくに注意して使ってください。高優先順位レベルのスレッドが長時間動作すると、 システムのほかのスレッドのCPU時間が不足します。高優先順位のスレッドが同時に複数存在すると、 高優先順位の効果は中和されます。高優先順位クラスは、 時間的に制約のあるイベントに応答しなければならないスレッドのために予約しておいてください。ある処理だけに高優先順位クラスが必要で、 そのほかのほとんどの関数は標準優先順位でかまわないときは、 SetPriorityClassを使って、 優先順位クラスを一時的に上げて、 時間的に制約のある処理が完了したら優先順位を下げてください。また、 通常はブロックされている高優先順位プロセスを作成し、 重要な作業が必要になったときだけ起動する方法もあります。高優先順位でスレッドを実行するのは、 時間的に制約のある処理を実行するときで、 短時間だけにしてください。

マルチスレッド プロセスの優先順位クラスを選択したら、 SetThreadPriorityを使って、 各スレッドの相対優先順位を調整してください。通常、 プロセスの入力スレッドに高い優先順位を設定して、 ユーザーに対する応答を確保します。それ以外のスレッド (とくに、 CPU時間を消費するスレッド) には低い相対優先順位を設定して、 必要ならばほかの処理にCPU時間を譲れるようにしてください。しかし、 優先順位の低いスレッドが処理を完了するのを優先順位の高いスレッドが待つときは、 ループで待機せずに、 (待機関数やクリティカル セクション、 Sleepなどを使って) 待機中のスレッドの実行をブロックしてください。そうしないと、 優先順位の低いスレッドがスケジュールされなくなるため、 プロセスはデッドロックします。

スレッドの作成

CreateThread関数は、 プロセスの新しいスレッドを作成します。作成側のスレッドは、 実行する新しいスレッドのコードの開始アドレスを指定しなければなりません。通常、 開始アドレスは、 プログラムのコード内で定義されている関数の名前です。プロセスは、 同じ関数を同時に実行するスレッドを複数作成できます。スレッドが呼び出す関数は、 32ビットのパラメータを1つ取り、 ダブルワードの結果を返します。

次の例は、 ThreadFunc関数を実行する新しいスレッドの作成を示しています。

DWORD ThreadFunc(LPDWORD lpdwParam) {
printf("ThreadFunc: thread parameter=%d\n", *lpdwParam);
return 0;
}

DWORD main(VOID) {
DWORD dwThreadId, dwThrdParam = 1;
HANDLE hThread;
hThread = CreateThread(
NULL,/* no security attributes*/
0,/* use default stack size*/
(LPTHREAD_START_ROUTINE) ThreadFunc, /* thread function*/
&dwThrdParam,/* argument to thread function*/
0,/* use default creation flags*/
&dwThreadId);/* returns thread the identifier */

/* Check the return value for success. */

if (hThread == NULL)
ErrorExit("CreateThread error\n");

簡単にするため、 この例では、 dwThrdParamを指すポインタをスレッド関数の引数として渡しています。この引数には、 さまざまなデータや構造体を指すポインタを指定できます。また、 ThreadFuncでのパラメータの参照を削除すれば、 NULLポインタを指定できます。作成側のスレッドが新しいスレッドより先に終了する場合にローカル変数のアドレスを渡すと、 ポインタが無効になり危険です。代わりに、 動的に割り当てたパラメータを渡すか、 新しいスレッドが終了するのを作成側のスレッドが待つようにしてください。また、 グローバル変数を使って、 作成側スレッドから新しいスレッドにデータを渡すこともできます。通常、 グローバル変数を使う場合は、 複数のスレッドのアクセスを同期する必要があります。同期について詳しくは、 複数のスレッドの実行の同期を参照してください。

スレッド関数の引数によって新しいスレッドにデータを渡す方法は、 同じコードを実行する複数のスレッドをメイン スレッドが作成する場合にとくに役立ちます。この場合は、 グローバル変数を使うのは不便です。たとえば、 ユーザーが複数のファイルを同時にオープンできるようにするプロセスは、 各ファイルごとに新しいスレッドを作成し、 各スレッドに同じスレッド関数を実行させます。作成側スレッドは、 スレッド関数の各インスタンスが必要とする一意な情報 (ファイル名など) を引数として渡します。

作成側スレッドは、 CreateThreadの引数に、 次に示す情報を指定できます。

新しいスレッドのハンドルのセキュリティ属性。このセキュリティ属性には、 ハンドルを子プロセスが継承するかどうかを示すフラグがあります。また、 セキュリティ属性には、 セキュリティ記述子もあります。システムは、 アクセスを許可する前に、 セキュリティ記述子を使ってスレッド ハンドルに対して必ずアクセス チェックを実行します。セキュリティ属性について詳しくは、 セキュリティの概要を参照してください。

新しいスレッドの初期スタック サイズ。スレッドのスタックは、 プロセスのメモリ空間に自動的に割り当てられ、 必要に応じて増加します。また、 スタックは、 スレッドの終了時に解放されます。

スレッドを中断状態で作成するかどうかを示す作成フラグ。スレッドを中断すると、 ResumeThread関数が呼び出されるまでスレッドは動作しません。

CreateThreadが返すハンドルには、 新しいスレッドに対する完全なアクセス権が設定されています。このハンドルは、 セキュリティ チェックに通過すれば、 スレッド オブジェクトのハンドルを受け付ける任意のWin32 API関数で使えます。ハンドルは、 DuplicateHandleで複製できます。この関数によって、 アクセス権を制限したスレッド ハンドルを作成できます。

また、 CreateRemoteThread関数でスレッドを作成することもできます。この関数は、 ほかのプロセスのアドレス空間で動作するスレッドをデバッガ プロセスが作成するときに使われます。

スレッドの実行の中断

あるスレッドがほかのスレッドの実行を中断したり、 その実行を再開したりするには、 SuspendThread関数とResumeThread関数を使います。スレッドを中断すると、 そのスレッドにはプロセッサ時間がスケジュールされなくなります。

SuspendThreadは、 スレッドの実行を中断するコード位置を指定できないため、 同期にはあまり役立ちません。しかし、 スレッドの動作を終了させるかユーザーに確認するときは、 スレッドの中断が役立ちます。ユーザーがスレッドの終了を指示すると、 スレッドを終了します。そうでないときは、 スレッドを再開します。

中断状態で作成されたスレッドは、 そのスレッドのハンドルを指定してResumeThreadが呼び出されるまで実行が開始されません。これは、 スレッドが実行を開始する前にスレッドの状態を初期化するのに役立ちます。この方法を使って、 スレッドが動作する前に優先順位を修正する方法については、 マルチスレッドMDIアプリケーションの使用を参照してください。スレッドを作成時に中断しておけば、 ResumeThreadは中断しているスレッドをコードの最初から実行するため、 1回だけの同期に役立ちます。

スレッドが自分自身の実行を一定時間だけ中断するには、 Sleep関数かSleepEx関数を呼び出します。この関数によって、 ユーザーが操作の結果を見ることができるようになるまでユーザー対話スレッドの実行を中断しておけるため、 この関数は、 ユーザーとの対話を行うスレッドでとくに役立ちます。スリープ中は、 スレッドにはプロセッサ時間はスケジュールされません。

複数のスレッドの実行の同期

アクセスの衝突を避けるため、 複数のスレッドによる共有リソースのアクセスは同期しなければなりません。また、 相互に依存するコードを正しい順番で実行しなければならないときにも、 同期が必要です。

Win32 APIは、 ハンドルを使って複数のスレッドを同期できるオブジェクトを多数用意しています。同期に使われるオブジェクトを次に示します。

同期オブジェクト (ミューテックス、 セマフォ、 イベント) ハンドル

ファイル ハンドル

名前付きパイプ ハンドル

コンソール入力バッファ ハンドル

通信デバイス ハンドル

プロセス ハンドル

スレッド ハンドル

上記の各オブジェクトの状態は、 シグナル状態か非シグナル状態になります。上記のオブジェクトのハンドルを指定して待機関数 (WaitForSingleObjectWaitForMultipleObjectsWaitForSingleObjectExWaitForMultipleObjectsEx) のいずれかを呼び出すと、 指定したオブジェクトがシグナル状態になるまで、 呼び出し側スレッドの実行はブロックされます。待機中のスレッドは、 中断しているスレッドと同様に、 プロセッサ時間をほとんど消費しません。

上記のオブジェクトには、 何らかのイベントが発生するまでスレッドをブロックするのに役立つものがあります。たとえば、 コンソール入力バッファ ハンドルは、 キーストロークやマウス ボタンのクリックなどの未読の入力があれば、 シグナル状態になります。プロセス ハンドルやスレッド ハンドルは、 プロセスやスレッドが終了するとシグナル状態になります。たとえば、 プロセスは、 子プロセスを作成し、 子プロセスが終了するまで自分自身の実行をブロックできます。

また、 共有リソースが同時にアクセスされないようにするときに役立つオブジェクトもあります。たとえば、 複数のスレッドがミューテックス オブジェクトのハンドルをそれぞれ持っているとします。スレッドは、 共有リソースにアクセスする前に、 待機関数を呼び出して、 ミューテックスの状態がシグナル状態になるまで待たなければなりません。ミューテックスがシグナル状態になると、 いずれか1つのオブジェクトだけがリソースにアクセスできるようになります。ミューテックスの状態はすぐに非シグナル状態になり、 ほかの待機スレッドはブロックされたままになります。スレッドは、 リソースを使い終ったら、 ミューテックスの状態をシグナル状態に設定して、 ほかのスレッドがリソースにアクセスできるようにしなければなりません。

単一プロセスのスレッドの場合、 クリティカル セクション オブジェクトによって、 (若干効率的な) 同期を実現できます。クリティカル セクションは、 ミューテックスと同様に、 保護されているリソースにアクセスできるスレッドを一度に1つだけに限定するのに使われます。スレッドは、 EnterCriticalSection関数を使って、 クリティカル セクションの所有権を要求できます。ほかのスレッドがクリティカル セクションをすでに所有していれば、 クリティカル セクションを要求したスレッドはブロックされます。スレッドは、 所有権を受け取ると、 保護されているリソースを使えるようになります。プロセスのほかのスレッドの実行は、 同じクリティカル セクションに入らない限り影響を受けません。

WaitForInputIdle関数は、 指定されたプロセスが初期化されてユーザー入力を待ち、 未処理の入力がなくなるまで、 スレッドを待機させます。CreateProcessは子プロセスが初期化を完了するのを待たずに戻るため、 親プロセスと子プロセスを同期するにはこの関数が役立ちます。

クリティカル セクションと同期について詳しくは、 同期の概要を参照してください。

スレッド ローカル記憶域

プロセスのすべてのスレッドは、 プロセスの仮想アドレス空間とグローバル変数を共有します。しかし、 場合によっては、 各スレッドにローカルな静的記憶域があった方がよいこともあります。もちろん、 スレッド関数のローカル変数は、 関数を実行する各スレッドにローカルです。しかし、 スレッドがほかの関数を呼び出す場合、 その関数が使う静的変数やグローバル変数の値はどのスレッドでも同じになります。スレッド ローカル記憶域を使う場合、 あるスレッドが割り当てたインデックスを使って、 プロセスの任意のスレッドがスレッドごとに異なる値を格納、 取得できます。

通常、 スレッド ローカル記憶域 (TLS) は、 次のように使います。

1.プロセスやダイナミック リンク ライブラリ (DLL) の初期化時に、 TlsAllocを使ってTLSインデックスを割り当てます。

2. TLSインデックスを使わなければならないスレッドは、 動的記憶域を割り当ててから、 TlsSetValueを使って、 その動的記憶域を指すポインタとインデックスを関連付けます。

3.スレッドは、 記憶域をアクセスするとき、 TLSインデックスを指定してTlsGetValueを呼び出し、 ポインタを取得します。

4.スレッドは、 TLSインデックスに関連付けられている動的記憶域が不要になったら、 記憶域を解放します。すべてのスレッドがTLSインデックスを使い終わると、 TlsFree関数はインデックスを解放します。

TLS_MINIMUM_AVAILABLE定数は、 各プロセスで最小限利用可能なTLSインデックスの個数を定義しています。この値は、 どのシステムでも64以上であることが保証されています。

スレッド ローカル記憶域は、 DLLで役立ちます。DllEntryPoint関数は、 DLLの接続先のプロセスやスレッドのコンテキストで初期TLS操作を実行します。DLLが新しいプロセスに接続するとき、 エントリ ポイント関数は、 TlsAllocを呼び出して、 そのプロセスのTLSインデックスを割り当てます。DLLがそのプロセスの新しいスレッドに接続するとき、 エントリポイント関数は、 そのスレッドの動的メモリを割り当てて、 TlsSetValueを使ってプライベート データのそのインデックスに保存します。DLLは、 接続している各プロセスそれぞれに専用のグローバル変数にTLSインデックスを格納します。DLLの関数は、 TLSインデックスを指定してTlsGetValueを呼び出すことによって、 呼び出し側スレッドのプライベートなデータにアクセスできます。

スレッド ローカル記憶域の使い方の例については、 スレッド ローカル記憶域の使用を参照してください。

スレッドの終了

スレッドは、 次に示すイベントのいずれかが発生するまで動作します。

ExitThreadを呼び出したとき。

スレッド関数が戻ったとき。プライマリ スレッドの場合はExitProcessが暗黙に呼び出され、 それ以外のスレッドの場合はExitThreadが暗黙に呼び出されます。

プロセスのスレッドのいずれかがExitProcessを明示的または暗黙に呼び出したとき。

スレッドのハンドルを指定してTerminateThreadが呼び出されたとき。

スレッドが属するプロセスのハンドルを指定してTerminateProcessが呼び出されたとき。

スレッドが終了すると、 スレッド オブジェクトはシグナル状態になり、 スレッドが終了するのを待っていたほかのスレッドの待機が解除されます。そして、 終了状態がSTILL_ACTIVEからスレッドの終了コードに変更されます。

スレッドをExitThreadで終了すると、 接続されているすべてのDLLのエントリポイント関数が、 スレッドがDLLから接続解除されることを示すコードで起動されます。スレッドをExitProcessで終了すると、 プロセスが接続解除されることを示すコードでDLLのエントリポイント関数が起動されます。スレッドがTerminateThreadTerminateProcessで終了したときは、 DLLには通知されません。DLLについて詳しくは、 ダイナミック リンク ライブラリの概要を参照してください。

GetExitCodeThread関数は、 スレッドの終了状態を返します。実行中の状態の終了状態はSTILL_ACTIVEです。スレッドが終了すると、 終了状態はスレッドの終了コードになります。終了コードは、 ExitThread ExitProcess TerminateThreadに指定した値か、 スレッド関数の戻り値です。

複数のスレッドとGDIオブジェクト

性能を向上させるため、 グラフィック デバイス インターフェイス (GDI) オブジェクト (パレットやデバイス コンテキスト、 リージョンなど) へのアクセスは、 並行して行われます。このため、 複数のスレッドがGDIオブジェクトを共有するプロセスでは、 問題が発生する可能性があります。たとえば、 あるスレッドが使っているGDIオブジェクトをほかのスレッドが削除すると、 不測の自体が発生します。この問題は、 GDIオブジェクトを共有しないようにするだけで解決できます。どうしても共有しなければならないときや、 共有した方がよいときは、 アプリケーション自身でアクセスを同期する機構を実現してください。アクセスの同期について詳しくは、 複数のスレッドの実行の同期を参照してください。

プロセスの作成

CreateProcess関数は、 新しいプロセスを作成します。新しいプロセスは、 作成側プロセスとは独立して動作します。この関数には、 実行するプログラムを2つの方法で指定できます。通常は、 イメージ ファイルのパスとファイル名を指定します。lpszImageNameパラメータにNULLを指定するときは、 プログラムはlpszCommandLineパラメータで指定します。

CreateProcessは、 正常に終了すると、 新しいプロセスとそのプライマリ スレッドのハンドルと識別番号を示すPROCESS_INFORMATION構造体を返します。スレッド ハンドルとプロセス ハンドルはアクセス権をすべて設定して作成されますが、 セキュリティ記述子が指定されているときはアクセスは制限されます。

継承

子プロセスは、 親プロセスのさまざまなプロパティやリソースを継承できます。また、 子プロセスが親プロセスから何も継承しないようにすることもできます。継承可能な情報を次に示します。

CreateFile関数が返す、 オープンされている継承可能なハンドル。このようなハンドルには、 ファイル、 コンソール入力バッファ、 コンソール画面バッファ、 名前付きパイプ、 シリアル通信デバイス、 メールスロットのハンドルがあります。

プロセス、 スレッド、 ミューテックス、 イベント、 セマフォ、 名前付きパイプ、 名前なしパイプ、 ファイル マッピング オブジェクトの継承可能なオープンされているハンドル

環境変数

現在のディレクトリ

親プロセスのコンソール (プロセスがコンソールに接続されていないときや、 新しいコンソールを作成したときは除きます)。また、 子コンソール プロセスは、 親プロセスの標準ハンドルと、 入力バッファやアクティブな画面バッファへのアクセスも継承します。コンソール プロセスについて詳しくは、 コンソールと文字モードのサポートの概要を参照してください。

CreateProcessfInheritHandlesフラグには、 親プロセスの継承可能ハンドルを子プロセスが継承するかどうかを指定します。上記のハンドルが継承されるかどうかは、 ハンドルを作成、 オープン、 複製する関数で指定します。これによって、 子プロセスは、 親プロセスのハンドルの一部だけを継承できます。このようなオブジェクトの種類には、 それぞれ、 ハンドルを継承するかどうかを継承フラグで指定するセキュリティ属性引数を持つ作成関数 (CreateProcessCreateFileなど) があります。セキュリティ属性引数がNULLならば、 ハンドルは継承されません。

このようなオブジェクトには、 継承を決定する継承フラグ パラメータを持つオープン関数 (OpenMutexOpenEventなど) があります。

ハンドルを複製するには、 ハンドルを継承可能にするかどうかを指定してDuplicateHandle関数を呼び出します。ハンドルの複製は、 オープンされている継承可能なハンドルを子プロセスに継承させたくないときに役立ちます。この場合、 DuplicateHandleを使ってハンドルの継承不可能な複製をオープンし、 CloseHandleを使って継承可能なハンドルをクローズしてください。また、 DuplicateHandleは、 継承不可能なハンドルから継承可能な複製を作成できます。

継承したハンドルは、 親プロセスのハンドルと同じ値とアクセス特権を持ちます。ハンドルを使うには、 子プロセスは、 ハンドルを取得してハンドルが参照するオブジェクトに関する情報を取得しなければなりません。ほとんどの場合、 親プロセスは、 この情報を子プロセスに渡さなければなりません。情報を子プロセスに渡すには、 コマンド ラインを使うか、 プロセス間通信 (パイプや共有メモリなど) を使ってください。

継承したハンドルは、 親プロセスのハンドルが参照しているのと同じオブジェクトを参照します。親プロセスか子プロセスの一方がオブジェクトの状態を変更すると、 その変更は両方のプロセスに影響します。たとえば、 ファイル ハンドルの場合、 一方のプロセスがハンドルを使ってファイルを読み取ると、 同じハンドルを使っているすべてのプロセスでファイルの現在位置が移動します。

デフォルトでは、 子プロセスは、 親プロセスの環境変数と現在のディレクトリを継承します。しかし、 親プロセスは、 CreateProcessに、 別の現在のディレクトリや環境変数ブロックを指定できます。環境変数について詳しくは、 環境変数を参照してください。

子プロセスが継承しないものを次に示します。

優先順位クラス

メモリ ハンドル

疑似ハンドル (GetCurrentProcessGetCurrentThreadが返すハンドル)。疑似ハンドルは、 これらの関数を呼び出したプロセスでだけ有効です。これらの関数について詳しくは、 プロセスやスレッドのハンドルとIDを参照してください。

LoadLibraryが返すDLLモジュール ハンドル

GDIハンドルやユーザー ハンドル (HBITMAPHMENUなど)。これらのハンドルの完全なリストについては、 WINDEF.Hを参照してください。

環境変数

どのプロセスにも、 環境変数とその値のセットを含む環境ブロックがあります。Microsoft(R) Windows NT(TM) のコマンド プロセッサ (CMD.EXE) には、 コマンド プロセッサの環境ブロックを表示したり、 (set name=valueの形式で) 新しい環境変数を作成するsetコマンドがあります。コマンド プロセッサが起動したプログラムは、 コマンド プロセッサの環境変数を継承します。

デフォルトでは、 子プロセスは、 親プロセスの環境変数を継承します。しかし、 新しい環境ブロックを作成して、 そのポインタをCreateProcess関数のパラメータに指定すれば、 別の環境変数を子プロセスに継承させることができます。

GetEnvironmentStrings関数は、 呼び出し側プロセスの環境ブロックを指すポインタを返します。このブロックは読み取り専用として扱い、 直接修正しないでください。環境変数を変更するには、 SetEnvironmentVariable関数を使ってください。GetEnvironmentVariable関数は、 指定された変数が呼び出し側プロセスの環境で定義されているか調べて、 定義されていればその値を調べます。環境について詳しくは、 環境変数の変更を参照してください。

STARTUPINFOによるウィンドウ プロパティの設定

親プロセスは、 子プロセスのメイン ウィンドウに関連付けられるプロパティを指定できます。プロパティを指定するには、 CreateProcess関数のパラメータに、 STARTUPINFO構造体を指すポインタを指定します。この構造体のメンバには、 子プロセスのメイン ウィンドウのさまざまな特徴を指定できます。dwFlagsメンバは、 構造体のほかのメンバのうちどれを使うかを示すビット フィールドです。作成側プロセスは、 このメンバを使って、 ウィンドウ プロパティの一部だけを指定できます。dwFlagsで指定しなかったプロパティには、 デフォルト値が使われます。また、 dwFlagsメンバによって、 新しいプロセスの初期化時にフィードバック カーソルが表示されるようにできます。プロセスの作成に使われたSTARTUPINFO構造体を取得するには、 GetStartupInfo関数を使います。

GUIプロセスの場合、 STARTUPINFO構造体には、 新しいプロセスが最初にCreateWindowShowWindowを呼び出してオーバーラップ ウィンドウを作成して表示するときに使われるデフォルト値を指定します。次に示すデフォルト値を指定できます。

CreateWindowで作成されるウィンドウの幅と高さ (ピクセル単位)

CreateWindowで作成されるウィンドウの画面座標系での位置

ShowWindownCmdShowパラメータ

コンソール プロセスの場合、 CreateProcessCREATE_NEW_CONSOLEを指定するか、 AllocConsoleを使って新しいコンソールを作成するときだけ、 STARTUPINFOの指定が使えます。コンソール プロセスについて詳しくは、 コンソールと文字モードのサポートの概要を参照してください。STARTUPINFOには、 次のコンソール ウィンドウ プロパティを指定できます。

新しいコンソール ウィンドウのサイズ (文字セル単位) と位置 (画面座標系)

新しいコンソールの画面バッファのサイズ (文字セル単位)

新しいコンソールの画面バッファのテキストの色属性と背景色属性

新しいコンソールのウィンドウのタイトル

プロセスの終了

次のいずれかが発生すると、 プロセスは終了します。

プロセスのスレッドのいずれかがExitProcessをよびだしたとき。

プロセスのプライマリ スレッドが終了したとき。この場合、 ExitProcessが暗黙に呼び出されます。

プロセスの最後のスレッドが終了したとき。

プロセスのハンドルを指定してTerminateProcessが呼び出されたとき。

プロセスが終了すると、 次に示すイベントが発生します。

ファイルなどのリソースのオープンされているハンドルは自動的にクローズされます。このようなハンドルが参照しているオブジェクトは、 関連するハンドルがすべてクローズされるまで存在します。つまり、 プロセスが終了しても、 ほかのプロセスがオブジェクトのハンドルを持っていればオブジェクトは有効なままです。

プロセス オブジェクトがシグナル状態になり、 プロセスの終了を待っているスレッドの終了条件が満たされます。同期について詳しくは、 複数のスレッドの実行の同期を参照してください。

プロセスの終了状態が、 STILL_ACTIVEからプロセスの終了コードに変更されます。

親プロセスが終了しても、 子プロセスは自動的には終了しません。

ExitProcessはプロセスの終了を接続されているDLLすべてに通知し、 プロセスのスレッドを必ずすべて終了させるため、 プロセスを終了するにはExitProcessを使ってください。ExitProcessでプロセスを終了すると、 プロセスがDLLから接続解除されることを示すコードを指定して、 接続されているDLLすべてのエントリポイント関数が呼び出されます。TerminateProcessでプロセスを終了すると、 DLLには通知されません。DLLについて詳しくは、 ダイナミック リンク ライブラリの概要を参照してください。

プロセスをExitProcessTerminateProcessで終了すると、 プロセスのすべてのスレッドは、 コードをそれ以上まったく実行せずに、 即座に終了します (ただし、 ExitProcessを呼び出したときのDLLプロセス接続解除コードは実行されます)。つまり、 プロセスは、 Microsoftのコンパイラがサポートしているtry-finally終了ハンドラのfinallyブロックなどの終了ハンドラ ブロックのコードを実行しません。ハンドラについて詳しくは、 構造化例外処理の概要を参照してください。

プロセスのプライマリ スレッドはほかのスレッドとは異なり、 プライマリ スレッドが戻るとき、 (ExitThreadではなく) ExitProcessが暗黙に起動されます。また、 プライマリ スレッドは、 自分が終了する前にExitThreadを呼び出すことによって、 ほかのスレッドが終了しないようにできます。この場合、 残ったスレッドのいずれかがExitProcessを呼び出して、 プロセスのスレッドがすべて終了するようにします。たとえば、 接続されているDLLが、 ExitProcessが呼び出されない限り終了しないスレッドを作成できます。

ExitProcessは、 システムによって暗黙に呼び出される場合もあります。例外が処理されないと、 ほとんどの場合、 システムはデフォルトでExitProcessを呼び出します。コンソール プロセスの場合、 Ctrl+CシグナルやCtrl+Breakシグナルを受け取ったときに呼び出されるデフォルトのハンドラ関数は、 ExitProcessを呼び出します。コンソール アプリケーションについて詳しくは、 コンソールと文字モードのサポートの概要を参照してください。コンソールに接続されているすべてのプロセスがシグナルを受け取るため、 コンソール割り込みシグナルのこのデフォルト処理は、 親プロセスと子プロセスを終了させます。コンソール ウィンドウが受け取った割り込みシグナルは、 接続されているコンソール プロセスだけに渡されます。接続されていないプロセスや、 新しいコンソール ウィンドウを作成しているプロセスは影響を受けません。GUIプロセスは、 Ctrl+CシグナルやCtrl+Breakシグナルの影響を受けません。

GetExitCodeProcess関数は、 プロセスの終了状態を返します。プロセスが動作しているときは、 終了状態はSTILL_ACTIVEになっています。プロセスが終了すると、 終了状態はプロセスの終了コードになります。終了コードは、 ExitProcessTerminateProcessに指定された値か、 プロセスのmain関数かWinMain関数が返した値です。これ以外の方法でプロセスが終了したとき (重大な例外が処理されなかったときやCtrl+Cシグナルを受け取ったとき) は、 プロセスの終了コードは、 例外の原因を示す例外コードになります。また、 プロセスの終了コードは、 まだ終了していなかったスレッドの終了コードとしても使われます。

プロセスやスレッドのハンドルとID

CreateThreadCreateRemoteThreadで新しいスレッドを作成すると、 スレッドのハンドルが返されます。同様に、 CreateProcessで新しいプロセスを起動すると、 新しいプロセスとそのプライマリ スレッドのハンドルが返されます。返されるハンドルは、 アクセス権をすべて設定して作成され、 セキュリティ アクセス チェックを通過すれば、 スレッド ハンドルやプロセス ハンドルを受け付けるどの関数でも使えます。子プロセスは、 プロセス作成時で継承フラグがセットされていれば、 このハンドルを継承できます。ハンドルが表すプロセスやスレッドが終了しても、 ハンドルはクローズされるまで有効です。

OpenProcess関数は、 プロセスIDで指定されたプロセスのハンドルをオープンします。OpenProcessには、 ハンドルのアクセス権と継承を指定できます。

プロセスが自分自身のプロセス オブジェクトの疑似ハンドルを取得するには、 GetCurrentProcess関数を使います。また、 スレッドは、 GetCurrentThread関数を使って、 自分自身のスレッド オブジェクトの疑似ハンドルを取得できます。この疑似ハンドルは、 これらの関数を呼び出したプロセスでだけ有効です。疑似ハンドルを継承したり複製してほかのプロセスで使うことはできません。

また、 プロセスやスレッドを作成する関数は、 システム内でプロセスやスレッドを一意に識別するIDを返します。プロセスが自分自身のプロセスIDを取得するには、 GetCurrentProcessId関数を使います。また、 スレッドは、 GetCurrentThreadId関数を使って自分自身のスレッドIDを取得できます。IDは、 スレッドやプロセスが作成されてから終了するまでの間だけ有効です。

プロセスとスレッドの使用

ここの例では、 次に示す処理を示しています。

複数ウィンドウを持つプロセス用のマルチスレッド作成。ウィンドウはそれぞれ別個のスレッドに関連付けられています。

標準入出力ハンドルがリダイレクトされているコンソール子プロセスの作成

親プロセスと異なる環境変数を持つ子プロセスの作成

スレッド ローカル 記憶域 (TLS)の使用

マルチスレッドMDIアプリケーションの使用

次の例は、 マルチ ドキュメント インターフェイス (MDI) プロセスで複数のスレッドを使う方法を示しています。プロセスはメイン ウィンドウを1つだけ持っていますが、 子ウィンドウは複数あります。プロセスのプライマリ スレッドは、 初期化を実行しアプリケーションで定義された、 MainWndProc関数とChildWndProc関数によって、 すべてのウィンドウに対するメッセージを処理します。

子ウィンドウを作成するときは、 新しいスレッドも作成します。この例では、 新しいスレッドは、 終了すべきかどうか常時チェックするだけです。実際のアプリケーションでは、 新しいスレッドを使って、 ウィンドウの内容を描画したり、 ウィンドウに関する関数を実行します。

CreateThreadには、 新しいスレッドが実行するコードとしてThreadFunc関数を指定します。スレッドに関連付けられている子ウィンドウのハンドルを、 ThreadFuncのパラメータとして渡します。また、 メッセージを子ウィンドウにディスパッチするとき、 子ウィンドウのハンドルをChildWndProcのパラメータとして渡します。子ウィンドウとそれに対応するスレッドが通信するには、 ハンドルが必要です。ThreadFuncとChildWndProcは、 どちらも、 SetWindowLong関数にウィンドウ ハンドルを指定して、 各ウィンドウ構造体にアプリケーション用に予約されている32ビット値にアクセスします。この例では、 この32ビット値は終了フラグです。ChildWndProcは、 WM_CLOSEメッセージを受け取ると、 終了フラグを設定します。ThreadFuncは、 ループをまわるたびにこのフラグをチェックします。

この例は、 プライマリ スレッドで標準優先順位を使い、 それ以外のスレッドでは低優先順位を使う方法を示しています。メイン ウィンドウと子ウィンドウのメッセージはすべてプライマリ スレッドが処理するため、 プライマリ スレッドの相対優先順位を高くすれば、 ユーザー入力への応答が保証されます (子スレッドが標準優先順位のままでも、 スケジューラは、 必要ならば入力スレッドの優先順位を自動的に上げます)。

ユーザーがメイン ウィンドウをクローズしてプロセスを終了させると、 子プロセスを終了させるようにグローバルなブール値パラメータを設定します。この時点で、 プライマリ スレッドは、 それ以上処理を進めずに、 各子スレッドが終了するのを待ちます。この手順は、 スレッドがクローズ前に変更をファイルに保存したりDLLから接続解除して後始末しなければならないときだけ必要です。子スレッドを待機しない場合、 プライマリ スレッドは、 低優先順位スレッドがコードをさらに実行できるようになる前に戻って、 プロセスを終了します。

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

#define MM_NEWWIN8001
typedef struct _PTHREADLIST {
HANDLE hThread;
LPVOID lpvNext;
} THREADLIST, *PTHREADLIST;

/* Globals */

HANDLE hModule; /* handle to .EXE file for this process */
HWNDhwndMain = NULL;/* handle to main window*/
BOOL fKillAll = FALSE;/* sets TRUE to terminate all threads*/
PTHREADLIST pHead = NULL;/* head of linked list of thread info. */

/* Forward declarations */

BOOL InitializeApp(VOID);
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM);
DWORD ThreadFunc(HWND);
VOID AddThreadToList(HANDLE);
VOID ErrorExit(LPSTR);

/* Primary thread: Initialize the application and dispatch messages. */

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst,
LPSTR lpszCmdLn, int nShowCmd) {
MSG msg;
hModule = GetModuleHandle(NULL);
if (! InitializeApp())
ErrorExit("InitializeApp failure!");
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 1;
UNREFERENCED_PARAMETER(hInst);
UNREFERENCED_PARAMETER(hPrevInst);
UNREFERENCED_PARAMETER(lpszCmdLn);
UNREFERENCED_PARAMETER(nShowCmd);
}

/* Register window classes and create the main window. */

BOOL InitializeApp(VOID) {
HMENU hmenuMain, hmenuPopup;
WNDCLASS wc;

/* Register a window class for the main window. */

wc.style= CS_OWNDC;
wc.lpfnWndProc= MainWndProc;
wc.cbClsExtra= 0;
wc.cbWndExtra= 0;
wc.hInstance= hModule;
wc.hIcon= LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor= LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground= (HBRUSH)(COLOR_BACKGROUND+1);
wc.lpszMenuName= NULL;
wc.lpszClassName= "MainWindowClass";
if (! RegisterClass(&wc))
return FALSE;

/* Register a window class for child windows. */

wc.lpfnWndProc= ChildWndProc;
wc.lpszClassName= "ThreadWindowClass";
if (! RegisterClass(&wc))
return FALSE;

/* Create a menu for the main window. */

hmenuMain = CreateMenu();
hmenuPopup = CreateMenu();
if (! AppendMenu(hmenuPopup, MF_STRING, MM_NEWWIN, "&New Window"))
return FALSE;
if (! AppendMenu(hmenuMain, MF_POPUP, (UINT) hmenuPopup, "&Threads"))
return FALSE;

/* Create the main window. */

hwndMain = CreateWindow("MainWindowClass", "Primary Window",
WS_OVERLAPPED | WS_CAPTION | WS_BORDER | WS_THICKFRAME |
WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CLIPCHILDREN |
WS_VISIBLE | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, NULL, hmenuMain, hModule,
NULL);
if (hwndMain == NULL)
return FALSE;

/* Set the initial focus. */

SetFocus(hwndMain);
return TRUE;
}

/* Main window procedure: Handle messages for the main window. */

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uiMessage,
WPARAM wParam, LPARAM lParam) {
static HWND hwndClient;
static DWORD dwCount = 1;
CLIENTCREATESTRUCT ccsClientCreate;
HWND hwndChildWnd;
DWORD IDThread;
PTHREADLIST pNode;

switch (uiMessage) {

/* Create a client window to receive child window messages. */

case WM_CREATE:

ccsClientCreate.hWindowMenu = NULL;
ccsClientCreate.idFirstChild = 1;
hwndClient = CreateWindow("MDICLIENT", NULL,
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE, 0, 0, 0, 0,
hwnd, NULL, hModule, (LPVOID)&ccsClientCreate);
return 0L;

/*
* Close the main window. First set fKillAll to TRUE to
* terminate all threads. Then wait for the threads to exit
* before passing a close message to a default handler. If you
* don't wait for threads to terminate, process terminates
* with no chance for thread cleanup.
*/

case WM_CLOSE:

fKillAll = TRUE;
pNode = pHead;
while (pNode) {
DWORD dwRes;
SetThreadPriority(pNode->hThread,
THREAD_PRIORITY_HIGHEST);
dwRes = WaitForSingleObject(pNode->hThread,
INFINITE);
pNode = (PTHREADLIST) pNode->lpvNext;
}
return DefFrameProc(hwnd, hwndClient, uiMessage,
wParam, lParam);

/* Terminate the process. */

case WM_DESTROY:

PostQuitMessage(0);
return 0L;

/* Handle the menu commands. */

case WM_COMMAND:

switch (LOWORD(wParam)) {

/* Create a child window and start a thread for it. */

case MM_NEWWIN: {
HANDLE hThrd;
MDICREATESTRUCT mdicCreate;
TCHAR tchTitleBarText[32];
LONG lPrev;

sprintf(tchTitleBarText, "Thread Window %d", dwCount);
mdicCreate.szClass = "ThreadWindowClass";
mdicCreate.szTitle = tchTitleBarText;
mdicCreate.hOwner= hModule;
mdicCreate.x = mdicCreate.y =
mdicCreate.cx = mdicCreate.cy = CW_USEDEFAULT;
mdicCreate.style = mdicCreate.lParam = 0L;

/* Send a "create child window" message to the client window. */

hwndChildWnd = (HWND) SendMessage(hwndClient,
WM_MDICREATE, 0L, (LONG)&mdicCreate);
if (hwndChildWnd == NULL)
ErrorExit("Failed in Creating Thread Window!");

/* Window structure used to pass a quit message to the thread. */

lPrev = SetWindowLong(hwndChildWnd, GWL_USERDATA, 0);

/* Create suspended, alter priority before resuming. */

hThrd = CreateThread(NULL,/* no security attributes */
0,/* use default stack size */
(LPTHREAD_START_ROUTINE) ThreadFunc,
(LPVOID)hwndChildWnd, /* parameter to thrd func */
CREATE_SUSPENDED, /* creation flag*/
&IDThread);/* returns thread id*/
if (hThrd == NULL)
ErrorExit("CreateThread Failed!");
AddThreadToList(hThrd);
dwCount++;

/*
* Set the priority lower than the primary (input) thread, so
* that the process is responsive to user input. Then resume
* the thread.
*/

if (! SetThreadPriority(hThrd,
THREAD_PRIORITY_BELOW_NORMAL))
ErrorExit("SetThreadPriority failed!");
if ((ResumeThread(hThrd)) == -1)
ErrorExit("ResumeThread failed!");
return 0L;
}

default:

return DefFrameProc(hwnd, hwndClient, uiMessage,
wParam, lParam);
}

default:

return DefFrameProc(hwnd, hwndClient, uiMessage,
wParam, lParam);
}
}

/* Handle messages for the child windows. */

LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT uiMessage, WPARAM wParam, LPARAM lParam) {
LONG lPrevLong;
switch (uiMessage) {

/* Use a window structure to pass "close" message to the thread. */

case WM_CLOSE:

lPrevLong = SetWindowLong(hwnd, GWL_USERDATA, 1);
return DefMDIChildProc(hwnd, uiMessage, wParam, lParam);

case WM_DESTROY:

return 0L;

default:

return DefMDIChildProc(hwnd, uiMessage, wParam, lParam);
}
}

/*
* Each child window has a thread that can be used to perform tasks
* associated with that window--for example, drawing its contents.
*/

DWORD ThreadFunc(HWND hwnd) {
LONG lKillMe = 0L;
while (TRUE) {
lKillMe = GetWindowLong(hwnd, GWL_USERDATA);
if (fKillAll || lKillMe) break;

/* Perform tasks. */

}

/* Perform actions needed before thread termination. */

return 0;
}

VOID AddThreadToList(HANDLE hThread) {
PTHREADLIST pNode;
pNode = (PTHREADLIST) LocalAlloc(LPTR, sizeof(PTHREADLIST));
if (pNode == NULL)
ErrorExit("malloc Failed!");
pNode->hThread = hThread;
pNode->lpvNext = (LPVOID) pHead;
pHead = pNode;
}

VOID ErrorExit(LPSTR lpszMessage) {
MessageBox(hwndMain, lpszMessage, "Error", MB_OK);
ExitProcess(0);
}

I/Oがリダイレクトされている子プロセスの作成

次の例は、 コンソール プロセスによる子プロセスの作成と、 名前なしパイプによる子プロセスの標準入出力のリダイレクトを示しています。STDINから入力を読み取ってSTDOUTに出力を書き込むプログラムがあれば、 I/Oをリダイレクトすることによってそのプログラムを子プロセスとして実行し、 入力を制御して出力を受け取ることができます。

この例では、 SECURITY_ATTRIBUTES構造体を使って子プロセスが継承できるハンドルを作成する方法を示しています。この構造体をCreatePipe関数に渡して、 2つのパイプの読み取り側と書き込み側の継承可能ハンドルを作成しています。一方のパイプの読み取り側は子プロセスの標準入力として使い、 もう一方のパイプの書き込み側は子プロセスの標準出力として使います。これらのパイプ ハンドルをSetStdHandle関数に指定すると、 ハンドルは、 子プロセスが継承可能な標準ハンドルになります。子プロセスを作成したら、 もう一度SetStdHandleを使って、 親プロセスの元の標準ハンドルを復元します。

親プロセスは、 パイプの反対側を使って、 子プロセスの入出力を読み書きします。パイプの親プロセス側のハンドルも継承可能です。しかし、 親プロセス側は継承させてはいけません。親プロセスは、 子プロセスを作成する前に、 DuplicateHandleを使ってアプリケーション定義のhChildStdinWrの継承不可能な複製を作成しなければなりません。それから、 CloseHandleを使って、 継承可能ハンドルをクローズします。これによって、 親プロセスはパイプ ハンドルを使い続けられますが、 子プロセスはそのパイプ ハンドルを継承できなくなります。パイプについて詳しくは、 パイプの概要を参照してください。

親プロセスのコードを次に示します。

#include <stdio.h>
#include <windows.h>

#define BUFSIZE 4096

HANDLE hChildStdinRd, hChildStdinWr, hChildStdinWrDup,
hChildStdoutRd, hChildStdoutWr,
hInputFile, hSaveStdin, hSaveStdout;

BOOL CreateChildProcess(VOID);
VOID WriteToPipe(VOID);
VOID ReadFromPipe(VOID);
VOID ErrorExit(LPTSTR);
VOID ErrMsg(LPTSTR, BOOL);

DWORD main(int argc, char *argv[]) {
SECURITY_ATTRIBUTES saAttr;
BOOL fSuccess;

/* Set the bInheritHandle flag so pipe handles are inherited. */

saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;

/*
* The steps for redirecting child's STDOUT:
* 1. Save current STDOUT, to be restored later.
* 2. Create anonymous pipe to be STDOUT for child.
* 3. Set STDOUT of parent to be write handle of pipe, so
*it is inherited by child.
*/

/* Save the handle to the current STDOUT. */

hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE);

/* Create a pipe for the child's STDOUT. */

if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
ErrorExit("Stdout pipe creation failed\n");

/* Set a write handle to the pipe to be STDOUT. */

if (! SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr))
ErrorExit("Redirecting STDOUT failed");

/*
* The steps for redirecting child's STDIN:
* 1. Save current STDIN, to be restored later.
* 2. Create anonymous pipe to be STDIN for child.
* 3. Set STDIN of parent to be read handle of pipe, so
*it is inherited by child.
* 4. Create a noninheritable duplicate of write handle,
*and close the inheritable write handle.
*/

/* Save the handle to the current STDIN. */

hSaveStdin = GetStdHandle(STD_INPUT_HANDLE);

/* Create a pipe for the child's STDIN. */

if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
ErrorExit("Stdin pipe creation failed\n");

/* Set a read handle to the pipe to be STDIN. */

if (! SetStdHandle(STD_INPUT_HANDLE, hChildStdinRd))
ErrorExit("Redirecting Stdin failed");

/* Duplicate the write handle to the pipe, so it is not inherited. */

fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdinWr,
GetCurrentProcess(), &hChildStdinWrDup, 0,
FALSE,/* not inherited */
DUPLICATE_SAME_ACCESS);
if (! fSuccess)
ErrorExit("DuplicateHandle failed");

CloseHandle(hChildStdinWr);

/* Now create the child process. */

if (! CreateChildProcess())
ErrorExit("Create process failed");

/* After process creation, restore the saved STDIN and STDOUT. */

if (! SetStdHandle(STD_INPUT_HANDLE, hSaveStdin))
ErrorExit("Re-redirecting Stdin failed\n");

if (! SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout))
ErrorExit("Re-redirecting Stdout failed\n");

/* Get a handle to the parent's input file. */

if (argc > 1)
hInputFile = CreateFile(argv[1], GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
else
hInputFile = hSaveStdin;

if (hInputFile == INVALID_HANDLE_VALUE)
ErrorExit("no input file\n");

/* Write to the pipe that is the standard input for a child process. */

WriteToPipe();

/* Read from the pipe that is the standard output for the child process. */

ReadFromPipe();

return 0;
}

BOOL CreateChildProcess() {
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;

/* Set up members of STARTUPINFO structure. */

siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.lpReserved = NULL;
siStartInfo.lpReserved2 = NULL;
siStartInfo.cbReserved2 = 0;
siStartInfo.lpDesktop = NULL;
siStartInfo.dwFlags = 0;

/* Create the child process. */

return CreateProcess(NULL,
"child",/* command line*/
NULL,/* process security attributes*/
NULL,/* primary thread security attributes */
TRUE,/* handles are inherited*/
0,/* creation flags*/
NULL,/* use parent's environment*/
NULL,/* use parent's current directory*/
&siStartInfo,/* STARTUPINFO pointer*/
&piProcInfo);/* receives PROCESS_INFORMATION*/


VOID WriteToPipe(VOID) {
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];

/* Read from a file and write its contents to a pipe. */

for (;;) {
if (! ReadFile(hInputFile, chBuf, BUFSIZE, &dwRead, NULL) ||
dwRead == 0) break;
if (! WriteFile(hChildStdinWrDup, chBuf, dwRead,
&dwWritten, NULL)) break;
}

/* Close the pipe handle so the child stops reading. */

if (! CloseHandle(hChildStdinWrDup))
ErrorExit("Close pipe failed\n");
}

VOID ReadFromPipe(VOID) {
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);

/* Close the write end of the pipe before reading from the read end of the pipe. */

if (! CloseHandle(hChildStdoutWr))
ErrorExit("Closing handle failed");

/* Read output from the child, and write it to the parent's STDOUT. */

for (;;) {
if (! ReadFile(hChildStdoutRd, chBuf, BUFSIZE, &dwRead, NULL) ||
dwRead == 0) break;
if (! WriteFile(hSaveStdout, chBuf, dwRead, &dwWritten, NULL))
break;
}
}

VOID ErrorExit (LPTSTR lpszMessage) {
fprintf(stderr, "%s\n", lpszMessage);
ExitProcess(0);
}

子プロセスのコードを次に示します。

#include <windows.h>
#define BUFSIZE 4096
VOID main(VOID) {
CHAR chBuf[BUFSIZE];
DWORD dwRead, dwWritten;
HANDLE hStdin, hStdout;
BOOL fSuccess;

hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
hStdin = GetStdHandle(STD_INPUT_HANDLE);
if ((hStdout == INVALID_HANDLE_VALUE) ||
(hStdin == INVALID_HANDLE_VALUE))
ExitProcess(1);

for (;;) {

/* Read from standard input. */

fSuccess = ReadFile(hStdin, chBuf, BUFSIZE, &dwRead, NULL);
if (! fSuccess || dwRead == 0)
break;

/* Write to standard output. */

fSuccess = WriteFile(hStdout, chBuf, dwRead, &dwWritten, NULL);
if (! fSuccess)
break;
}
}

環境変数の変更

環境ブロックは、 NULLで終わる文字列のNULLで終わるブロックで構成されます (つまり、 各ブロックの最後にはNULLバイトが2つあります)。各文字列は、 NAME=valueの形式になっています。等号は区切り記号であるため、 環境変数の名前には使えません。次の例は、 子プロセスに渡される新しい環境ブロックを作成します。

TCHAR tchNewEnv[BUFSIZE];
LPTSTR lpszCurrentVariable;
BOOL fSuccess;

/* Copy name=value environment variable pairs into the environment block. */

lpszCurrentVariable = tchNewEnv;
if (lstrcpy(lpszCurrentVariable, "OperatingSystem=Windows NT") == NULL)
ErrorExit("lstrcpy failed");

lpszCurrentVariable += lstrlen(lpszCurrentVariable) + 1;
if (lstrcpy(lpszCurrentVariable, "API=Win32") == NULL)
ErrorExit("lstrcpy failed");

/* Terminate the block with a NULL byte. */

lpszCurrentVariable += lstrlen(lpszCurrentVariable) + 1;
*lpszCurrentVariable = '\0';

/* Create the child process, specifying a new environment block. */

fSuccess = CreateProcess(NULL, "childenv", NULL, NULL, TRUE, 0,
(LPVOID) tchNewEnv,/* new environment block */
NULL, &siStartInfo, &piProcInfo);

if (! fSuccess)
ErrorExit("CreateProcess failed");

親プロセスの環境を少しだけ変更して子プロセスに継承させるには、 現在の値を保存しておいて、 子プロセスに継承させるための変更を行い、 保存しておいて値を復元します。例を次に示します。

LPTSTR lpszOldValue;

TCHAR tchBuf[BUFSIZE];

BOOL fSuccess;

/*

* lpszOldValue gets current value of "varname", or NULL if "varname"

* environment variable does not exist. Set "varname" to new value,

* create child process, then use SetEnvironmentVariable to restore

* original value of "varname". If lpszOldValue is NULL, the "varname"

* variable will be deleted.

*/

lpszOldValue = ((GetEnvironmentVariable("varname",

tchBuf, BUFSIZE) > 0) ? tchBuf : NULL);

/* Set a value for the child to inherit. */

if (! SetEnvironmentVariable("varname", "newvalue"))

ErrorExit("SetEnvironmentVariable failed");

/* Create a child process. */

fSuccess = CreateProcess(NULL, "childenv", NULL, NULL, TRUE, 0,

NULL,/* inherit parent's environment */

NULL, &siStartInfo, &piProcInfo);

if (! fSuccess)

ErrorExit("CreateProcess failed");

/* Restore the parent's environment. */

if (! SetEnvironmentVariable("varname", lpszOldValue))

ErrorExit("SetEnvironmentVariable failed");

次の例はコンソール プロセスの一部で、 プロセスの環境ブロックの内容を表示します。

LPTSTR lpszVariable;
LPVOID lpvEnv;

/* Get a pointer to the environment block. */

lpvEnv = GetEnvironmentStrings();

/*
* Variable strings are separated by NULL byte, and the block is
* terminated by a NULL byte.
*/

for (lpszVariable = (LPTSTR) lpvEnv; *lpszVariable; lpszVariable++) {
while (*lpszVariable)
putchar(*lpszVariable++);
putchar('\n');
}

スレッド ローカル記憶域の使用

スレッド ローカル記憶域 (TLS) によって、 同じプロセスの複数のスレッドが、 TlsAllocで割り当てたインデックスを使ってスレッドにローカルな値を格納、 取得できます。この例では、 プロセスが起動するときにインデックスを割り当てます。各スレッドは、 起動時に、 動的メモリのブロックを割り当てて、 そのメモリを指すポインタをTLSインデックスを使って格納します。TLSインデックスは、 ローカルに定義されたCommonFunc関数で、 呼び出し側スレッドにローカルなデータをアクセスするのに使います。各スレッドは、 終了する前に、 自分が割り当てた動的メモリを解放します。

#include <stdio.h>
#include <windows.h>

#define THREADCOUNT 4
DWORD dwTlsIndex;

VOID ErrorExit(LPTSTR);

VOID CommonFunc(VOID) {
LPVOID lpvData;

/* Retrieve a data pointer for the current thread. */

lpvData = TlsGetValue(dwTlsIndex);
if ((lpvData == 0) && (GetLastError() != 0))
ErrorExit("TlsGetValue error");

/*
*Use the data stored for the current thread.
*/

printf("common: thread %d: lpvData=%lx\n",
GetCurrentThreadId(), lpvData);

Sleep(5000);
}

DWORD ThreadFunc(VOID) {
LPVOID lpvData;

/* Initialize the TLS index for this thread. */

lpvData = (LPVOID) LocalAlloc(LPTR, 256);
if (! TlsSetValue(dwTlsIndex, lpvData))
ErrorExit("TlsSetValue error");

printf("thread %d: lpvData=%lx\n", GetCurrentThreadId(), lpvData);

CommonFunc();

/* Release the dynamic memory before the thread returns. */

lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != 0)
LocalFree((HLOCAL) lpvData);

return 0;

}

DWORD main(VOID) {
DWORD IDThread;
HANDLE hThread[THREADCOUNT];
int i;

/* Allocate a TLS index. */

if ((dwTlsIndex = TlsAlloc()) == 0xFFFFFFFF)
ErrorExit("TlsAlloc failed");

/* Create multiple threads. */

for (i = 0; i < THREADCOUNT; i++) {
hThread[i] = CreateThread(NULL,/* no security attributes*/
0,/* use default stack size*/
(LPTHREAD_START_ROUTINE) ThreadFunc, /* thread function */
NULL,/* no thread function argument */
0,/* use default creation flags*/
&IDThread);/* returns thread identifier*/

/* Check the return value for success. */

if (hThread[i] == NULL)
ErrorExit("CreateThread error\n");
}

for (i = 0; i < THREADCOUNT; i++)
WaitForSingleObject(hThread[i], INFINITE);

return 0;

}


VOID ErrorExit (LPTSTR lpszMessage) {
fprintf(stderr, "%s\n", lpszMessage);
ExitProcess(0);
}

プロセスとスレッドの関数

プロセスに関する関数を次に示します。

AttachThreadInput

CommandLineToArgvW

CreateProcess

CreateRemoteThread

CreateThread

DuplicateHandle

ExitProcess

ExitThread

FreeEnvironmentStrings

GetCommandLine

GetCurrentProcess

GetCurrentProcessId

GetCurrentThread

GetCurrentThreadId

GetEnvironmentStrings

GetEnvironmentVariable

GetExitCodeProcess

GetExitCodeThread

GetPriorityClass

GetProcessAffinityMask

GetProcessShutdownParameters

GetProcessTimes

GetProcessWorkingSetSize

GetStartupInfo

GetThreadPriority

GetThreadTimes

OpenProcess

ResumeThread

SetEnvironmentVariable

SetPriorityClass

SetProcessShutdownParameters

SetThreadAffinityMask

SetThreadPriority

Sleep

SleepEx

SetProcessWorkingSetSize

SuspendThread

TerminateProcess

TerminateThread

TlsAlloc

TlsFree

TlsGetValue

TlsSetValue

WaitForInputIdle

WinExec

使われなくなったか、 または削除された関数

Yield

▲ページトップに戻る

【本を読んでもよくらからない】 … 個別指導でわかりやすくお教えします