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

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

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

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

Windows API 構造化例外処理の概要

「例外」とは、 プログラムの実行時に発生するイベントで、 ソフトウェアの実行が通常の状態ではなくなります。ハードウェア例外は、 0による除算や無効なメモリ アドレスのアクセスなどの特定の命令シーケンスの実行によって発生します。また、 ソフトウェア ルーチンから例外を明示的に発生させることもできます。

Microsoft(R) Win32(R) アプリケーション プログラミング インターフェイス (API) では、 ハードウェアやソフトウェアが生成した例外を処理するための機構である「構造化例外処理」をサポートしています。構造化例外処理によって、 例外の処理をプログラムから完全に制御できます。また、 デバッガの作成や、 さまざまなプログラミング言語とマシンの仲介にも役立ちます。

Win32 APIは、 終了処理もサポートしています。終了処理によって、 コードの監視対象部分が実行されたときに、 指定した終了コードも実行されるようにできます。終了コードは、 監視対象部分の終了方法にかかわらず実行されます。たとえば、 終了ハンドラによって、 コードの監視対象部分の実行時にエラーが発生しても後始末処理が行われるようにできます。

構造化例外処理と構造化終了処理はWin32システムに不可欠な部分で、 強靭なシステム ソフトウェアを実現するのに役立っています。また、 この機構は、 強靭で信頼性のあるアプリケーションの作成も目的としています。

構造化例外処理は、 主にコンパイラによるサポートで利用できます。たとえば、 Win32開発キットに付属するMicrosoftのコンパイラは、 コードの監視対象部分を識別するtryキーワードと、 例外ハンドラや終了ハンドラを識別するexceptキーワードとfinallyキーワードをサポートしています。このトピックではMicrosoftのコンパイラによるサポートを扱っていますが、 ほかのコンパイラでも同様にサポートできます。

次に示すトピックでは、 Win32の構造化例外処理について説明しています。

例外処理

終了処理

例外処理の構文

例外ハンドラの使用

終了ハンドラの使用

構造化例外処理関数

例外処理

例外はハードウェアやソフトウェアによって発生し、 カーネル モードとユーザー モードの両方で発生する可能性があります。Win32の構造化例外処理によって、 ハードウェアやソフトウェアによって生成されたカーネル モード例外とユーザー モード例外の両方を1つの機構で処理できます。

特定の命令シーケンスを実行すると、 ハードウェアによる例外が発生します。たとえば、 プロセスが適切なアクセス権を持たない仮想アドレスを読み書きしようとすると、 ハードウェアによってアクセス違反が生成されます。

また、 ソフトウェア ルーチンの実行中に、 (たとえば、 無効なパラメータ値が指定されたときなど) 例外処理を必要とするイベントが発生する場合もあります。このような場合、 スレッドは、 RaiseException関数を呼び出すことによって、 明示的に例外を発生させることができます。スレッドは、 この関数に、 例外を記述する情報を指定できます。

例外のディスパッチ

ハードウェア例外やソフトウェア例外が発生すると、 プロセッサは、 例外が発生した時点で実行を中止し、 システムに制御を移行します。システムは、 まず、 現在のスレッドのマシン状態と、 例外を記述した情報を保存します。それから、 例外を処理する適切な例外ハンドラを探します。

例外が発生したときのスレッドのマシン状態は、 CONTEXT構造体に保存されます。例外を正常に処理できれば、 システムは、 この情報 (「コンテキスト レコード」と呼ばれます) を使って、 例外が発生した位置から実行を再開できます。例外の記述 (「例外レコード」と呼ばれます) は、 EXCEPTION_RECORD構造体に保存されます。マシンに依存する情報はコンテキスト レコードに、 マシンに依存しない情報は例外レコードにそれぞれ分離して格納されるため、 例外処理機構をさまざまなプラットフォームに移植できます。コンテキスト レコードと例外レコードの情報はGetExceptionInformation関数で取得できます。また、 例外によって実行される例外ハンドラからもこの情報を取得できます。例外レコードには、 次に示す情報があります。

例外の種類を識別する例外コード。

例外が続行可能かどうかを示すフラグ。続行不可能な例外の後で実行を続行しようとすると、 さらに例外が発生してしまいます。

別の例外レコードを指すポインタ。これによって、 ネストした例外が発生した場合に、 例外のリンク リストを作成できます。

例外が発生したアドレス。

例外に関する詳細な情報を示す32ビット引数の配列。

ユーザー モードのコードで例外が発生すると、 システムは、 次に示す手順で例外ハンドラを検索します。

1. プロセスがデバッグされていれば、 システムは、 まずデバッガに通知します。

2. プロセスがデバッグされていないときや、 関連付けられているデバッガが例外を処理しないときは、 システムは、 例外が発生したスレッドのスタック フレームを検索して、 フレーム ベースの例外ハンドラを探します。システムは、 まず現在のスタックを調べて、 スタック フレームを順番にさかのぼっていきます。

3. フレーム ベースのハンドラがないときや、 フレーム ベース ハンドラが例外を処理しなかったときは、 システムは、 もう一度プロセスのデバッガに通知します。

4. プロセスがデバッグされていないときや、 関連付けられているデバッガが例外を処理しないときは、 システムは、 例外の種類に基づいてデフォルトの処理を行います。ほとんどの例外では、 デフォルトでExitProcess関数が呼び出されます。

カーネル モードのコードで例外が発生すると、 システムは、 カーネル スタックのスタック フレームを検索して例外ハンドラを探します。ハンドラが見つからないときや、 ハンドラが例外を処理しないときは、 システムは、 ExitWindows関数が呼び出されたときと同様にシャットダウンします。

デバッガのサポート

システムのユーザー モード例外の処理は、 高性能なデバッガをサポートしています。例外が発生したプロセスがデバッグされていれば、 システムはデバッグ イベントを生成します。デバッガがWaitForDebugEvent関数を使っていれば、 デバッグ イベントが発生すると、 関数はDEBUG_EVENT構造体を指すポインタを返して戻ります。この構造体は、 デバッガがスレッドのコンテキスト レコードにアクセスするためのプロセス識別子とスレッド識別子を含みます。また、 この構造体は、 例外レコードのコピーを含むEXCEPTION_DEBUG_INFO構造体を含みます。

システムは、 例外ハンドラを検索するとき、 プロセスのデバッガへの通知を2回行います。最初の通知で、 デバッガは、 ブレークポイント例外やシングル ステップ例外を処理できます。このとき、 デバッガのユーザーは、 デバッガ コマンドを発行して、 例外ハンドラが実行される前にプロセスの環境を操作できます。2回目の通知は、 例外を処理するフレーム ベース例外ハンドラをシステムが発見でなかったときだけ発生します。

通知を受け取ったデバッガは、 ContinueDebugEvent関数を使って制御をシステムに返します。デバッガは、 制御を返す前に例外を処理してスレッドの状態を修正したり、 例外を処理せずにそのままにしておくことができます。デバッガは、 ContinueDebugEventを使って、 例外を処理したことを示すことができます。この場合、 マシン状態は復元され、 例外が発生した位置からスレッドの実行が続行されます。また、 デバッガは、 例外を処理しなかったことを示すこともできます。この場合、 システムは、 例外ハンドラの検索を続けます。

フレーム ベースの例外処理

「フレーム ベース例外ハンドラ」とは、 特定のコード部分で例外が発生する可能性があるときに使われる機構です。フレーム ベース例外ハンドラは、 次に示す要素で構成されます。

コードの監視対象部分

フィルタ式

例外ハンドラ ブロック

フレーム ベース例外ハンドラは、 言語固有の構文で宣言します。たとえば、 Microsoft C Optimizing Compilerの場合、 try-except文で実現されています。Microsoft Cの例外処理の構文について詳しくは、 例外処理の構文を参照してください。

「コードの監視対象部分」とは、 フィルタ式と例外ハンドラ ブロックの例外処理保護の対象となる1つ以上の文の集合です。監視対象部分には、 コードのブロック、 ネストしたブロックの集合、 プロシージャや関数全体が使えます。Microsoft Cでは、 監視対象部分は、 tryキーワードの後の中かっこ ({}) で囲まれた部分です。

フレーム ベース例外ハンドラの「フィルタ式」とは、 監視対象部分内で例外が発生したときにシステムが評価する式です。この式の評価結果に応じて、 システムは次に示す動作を実行します。

システムは、 例外ハンドラの検索を中止し、 マシン状態を復元して、 例外が発生した位置からスレッドの実行を続行します。

システムは、 例外ハンドラの検索を続行します。

システムは例外ハンドラに制御を移行し、 例外ハンドラが見つかったスタック フレームでスレッドの実行が続けられます。例外が発生したスタック フレームにハンドラがなければ、 システムは、 現在のスタック フレームやそのほかのスタック フレームをそのままにして、 例外ハンドラのスタック フレームに到達するまでスタックをさかのぼります。システムは、 例外ハンドラを実行する前に、 例外ハンドラへの制御の移行によって終了した監視対象部分のコードの終了ハンドラを実行します。終了ハンドラについて詳しくは、 終了処理を参照してください。

フィルタ式には、 単純な式か、 例外の処理を試みる「フィルタ関数」を指定できます。フィルタ関数からは、 GetExceptionCode関数やGetExceptionInformation関数を呼び出して、 フィルタ対象の例外に関する情報を取得できます。GetExceptionCodeは例外の種類を識別するコードを返し、 GetExceptionInformationは、 EXCEPTION_POINTERS構造体を指すポインタを返します。この構造体には、 CONTEXT構造体やEXCEPTION_RECORD構造体を指すポインタがあります。

これらの関数はフィルタ関数内からは呼び出せませんが、 戻り値をフィルタ関数のパラメータに渡すことはできます。GetExceptionCodeは例外ハンドラ ブロック内で使えますが、 GetExceptionInformationが返すポインタが指す情報は通常はスタックにあり、 制御が移行したときに破棄されるため、 GetExceptionInformationは例外ハンドラ ブロックでは使えません。しかし、 アプリケーションは、 安全な記憶域に情報をコピーして、 例外ハンドラから利用できるようにすることができます。

フィルタ関数の利点は、 例外を処理して、 例外が発生した位置からシステムに実行を続けさせる値を返すことができる点です。これに対して、 例外ハンドラ ブロックでは、 例外が発生した位置ではなく、 例外ハンドラから実行が続行されます。

例外の処理では、 エラーの記録と後で参照可能なフラグの設定、 警告メッセージやエラー メッセージの表示など、 簡単な処理だけを行ってください。また、 実行を続けるときは、 コンテキスト レコードを修正してマシン状態を変更しなければならない場合もあります。ページ フォールトを処理するフィルタ関数の例については、 メモリ管理の概要を参照してください。

フィルタ式のフィルタ関数には、 UnhandledExceptionFilter関数を使うことができます。この関数は、 プロセスがデバッグされていなければEXCEPTION_EXECUTE_HANDLERを返し、 プロセスがデバッグされていればEXCEPTION_CONTINUE_SEARCHを返します。

終了処理

「終了ハンドラ」とは、 特定の監視対象コードが終了したときに特定のコード ブロックが実行されるようにするための機構です。終了ハンドラは、 次に示す要素で構成されます。

コードの監視対象部分

監視対象部分を終了したときに実行される終了コードのブロック

終了ハンドラは、 言語固有の構文で宣言します。Microsoft Cの場合、 終了ハンドラはtry-finally文で実現されています。Microsoft Cの例外処理の構文について詳しくは、 例外処理の構文を参照してください。

コードの監視対象部分には、 コードのブロック、 ネストしたブロックの集合、 1つのプロシージャや関数全体が使えます。監視対象部分が実行されると、 終了コードのブロックが実行されます。ただし、 監視対象部分の実行中にスレッドが終了したとき (たとえば、 監視対象部分からExitThread関数やExitProcess関数を呼び出したとき)は除きます。

監視対象部分の実行が終了すると、 監視対象部分が正常に終了したかどうかに関わらず、 終了ブロックが実行されます。ブロックの最後の文が実行された時点で監視対象部分が正常終了したものとみなされ、 制御はそのまま終了ブロックに移行します。例外やreturngotobreakcontinueなどの制御文で監視対象部分が途中で終了すると、 異常終了が発生します。監視対象部分が正常終了したかどうかを終了ブロックから調べるには、 AbnormalTermination関数を呼び出します。

例外処理の構文

ここでは、 Microsoft Cで実現されている構造化例外処理の構文と使い方について説明します。Microsoft Cは、 次に示すキーワードを構造化例外処理機構の一部として解釈します。

キーワード 説明

try コードの監視対象部分を開始します。exceptキーワードとともに使うと例外ハンドラになり、 finallyキーワードとともに使うと終了ハンドラになります。

except 対応するtryブロックで例外が発生したときだけ実行されるコード ブロックを開始します。

finally 対応するtryブロックの実行が終了したときに実行されるコード ブロックを開始します。

また、 コンパイラは、 GetExceptionCode関数、 GetExceptionInformation関数、 AbnormalTermination関数をキーワードとして解釈します。適切な例外処理の外部でこれらの関数を使うと、 コンパイラ エラーが生成されます。各関数の簡単な説明を次に示します。

関数 説明

GetExceptionCode 例外の種類を識別するコードを返します。この関数は、 try-except例外ハンドラのフィルタ式か例外ハンドラ ブロックからしか呼び出せません。

GetExceptionInformation コンテキスト レコードや例外レコードを指すポインタを含むEXCEPTION_POINTERS構造体を指すポインタを返します。この関数は、 try-except例外ハンドラのフィルタ式からしか呼び出せません。

AbnormalTermination 対応するtryブロックの終了がブロックの最後の文の実行によるものかどうかを示します。この関数は、 try-finally終了ハンドラのfinallyブロックからしか呼び出せません。

例外ハンドラの構文

フレーム ベースの例外ハンドラを構築するには、 tryキーワードとexceptキーワードを使います。次の例は、 try-except例外ハンドラの構造を示しています。

try {

/* guarded body of code */

}
except (filter-expression) {

/* exception-handler block */

}

tryブロックと例外ハンドラ ブロックには、 中かっこ ({}) が必要です。goto文によるtryブロックや例外ハンドラ ブロックへのジャンプはできません。この規則は、 try-except文とtry-finally文の両方に適用されます。

tryブロックには、 例外ハンドラの対象となるコードを置きます。1つの関数内に任意個数のtry-except文を置くことができ、 例外処理文は同じ関数や別の関数でネストできます。tryブロックで例外が発生すると、 システムが制御を受け取り、 例外ハンドラの検索を開始します。この検索について詳しくは、 例外処理を参照してください。

例外ハンドラは、 1つのスレッド内で発生した例外だけを受け取ります。つまり、 tryブロックでCreateProcess関数やCreateThread関数を呼び出している場合、 新しいプロセスやスレッドで発生した例外はこのハンドラにはディスパッチされません。

システムは、 例外が処理されるか、 ハンドラが見つからなくなるまで、 例外が発生したコードを監視対象とする各例外ハンドラのフィルタ式を評価します。フィルタ式の評価結果は、 次の3つの値のいずれかにならなければなりません。

意味

EXCEPTION_EXECUTE_HANDLER システムは例外ハンドラに制御を移行し、 ハンドラがあるスタック フレームで実行が再開されます。

EXCEPTION_CONTINUE_SEARCH システムはハンドラの検索を続けます。

EXCEPTION_CONTINUE_EXECUTION システムはハンドラの検索を中止し、 例外が発生した位置に制御を返します。例外が続行不可能ならば、 EXCEPTION_NONCONTINUABLE_EXCEPTION例外になります。

例外がほかの関数で発生した場合でも、 フィルタ式は、 try-except文がある関数のコンテキストで評価されます。つまり、 フィルタ式は関数のローカル変数にアクセスできます。同様に、 例外ハンドラ ブロックは、 そのブロックがある関数のローカル変数にアクセスできます。

フィルタ式とフィルタ関数について詳しくは、 フレーム ベースの例外処理を参照してください。

終了ハンドラの構文

終了ハンドラを構築するには、 tryキーワードとfinallyキーワードを使います。次の例は、 try-finally終了ハンドラの構造を示しています。

try {

/* guarded body of code */

}
finally {

/* finally block */

}

try-except文と同様に、 tryブロックとfinallyブロックには中かっこ ({}) が必要で、 これらのブロックにgoto文でジャンプすることはできません。

tryブロックには、 終了ハンドラの監視の対象となるコードを置きます。1つの関数に任意個数のtry-finally文を置くことができ、 終了処理構造は、 同じ関数や別の関数でネストできます。

tryブロックが終了すると、 finallyブロックが実行されます。しかし、 tryブロックでExitProcess関数かExitThread関数を呼びしたときは、 finallyブロックは実行されません。

フレーム ベース例外ハンドラの例外処理ブロックを起動する例外によってtryブロックが終了した場合、 例外処理ブロックの前にfinallyブロックが実行されます。同様に、 tryブロックから標準C言語のlongjmp関数を呼び出すと、 longjmpの飛び先から実行を再開する前に、 finallyブロックが実行されます。通常の制御文 (returnbreakcontinuegoto) tryブロックの実行が終了したときも、 finallyブロックが実行されます。

tryブロックが通常どおり終了したかどうか (閉じ中かっこ (})) まで到達したかどうか)finallyブロック内で調べるには、 AbnormalTermination関数を使います。longjmpの呼び出し、 例外ハンドラへのジャンプ、 returnbreakcontinuegotoなどの文の実行によるtryブロックの終了は異常終了とみなされます。通常どおり終了しなければ、 システムは、 スタック フレームをすべてさかのぼって、 終了ハンドラを呼び出すべきか判断します。これには数百個の命令を実行しなければならないため、 性能の低下を招く場合があります。

finallyブロックの実行は、 次に示す方法で終了できます。

ブロックの最後の文を実行してそのまま次の命令の実行に移行する方法。

通常の制御文 (returnbreak continuegoto) を使う方法。

longjmpを使うか、 例外ハンドラにジャンプする方法。

finallyブロックは、 try-finally文がある関数のコンテキストで実行されます。このため、 finallyブロックでは、 関数のローカル変数にアクセスできます。

構造化例外ハンドラの使用

ここでは、 次の処理を説明します。

try-except例外処理構文を使ったフレーム ベース例外ハンドラの作成

try-finally終了処理構文を使った終了ハンドラの作成

例外ハンドラの使用

次の例は、 無効なポインタが渡されたときにはNULLを返すように修正した標準C言語のstrcpy関数を示しています。

この例外ハンドラはアクセス違反例外を処理するためのもので、 ほかの種類の例外が発生したときには使えません。このため、 この例のフィルタ関数では、 ハンドラを実行する前に、 GetExceptionCode関数を使って例外の種類をチェックしています。これによって、 アクセス違反以外の例外が発生した場合、 システムが適切なハンドラの検索を続行できます。

また、 try-except文のtryブロックでreturn文を使っても問題はありません。しかし、 try-finally文のtryブロックでreturnを使うと、 tryブロックの異常終了になります。

LPTSTR SafeStrcpy(LPTSTR lpszString1, LPTSTR lpszString2) {
try {
return strcpy(string1, string2);
}
except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?
EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH ) {
return NULL;
}
}

次の例は、 ネストしたtry-finally文とtry-except文の関係を示しています。RaiseException関数は、 try-except例外ハンドラの監視対象部分にあるtry-finally例外ハンドラの監視対象部分の例外を発生させます。この例外によって、 システムはFilterFunction関数を評価します。この関数の戻り値によっては、 例外ハンドラが起動されます。しかし、 終了ハンドラのtryブロックを終了しているため、 例外ハンドラ ブロックを実行する前に、 終了ハンドラのfinallyブロックが実行されます。

DWORD FilterFunction() {
printf("1 ");/* this is printed first*/
return EXCEPTION_EXECUTE_HANDLER;
}

VOID main(VOID) {
try {
try {
RaiseException(1,/* exception code*/
0,/* continuable exception*/
0, NULL); /* no arguments*/
}
finally {
printf("2 ")/* this is printed second */
}
}
except ( FilterFunction() ) {
printf("3\n");/* this is printed last*/
}
}

終了ハンドラの使用

次の例は、 try-finally文を使って、 コードの監視対象部分の実行が終了したときにリソースが解放されるようにします。スレッドは、 EnterCriticalSection関数を使って、 クリティカル セクション オブジェクトの所有権を取得します。スレッドは、 クリティカル セクションで保護されているコードを実行し終わったら、 LeaveCriticalSection関数を呼び出して、 ほかのスレッドがクリティカル セクション オブジェクトを利用できるようにしなければなりません。ここでは、 try-finally文を使って、 この作業が必ず行われるようにしています。

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

LPTSTR lpBuffer = NULL;
CRITICAL_SECTION csCriticalSection;

try {

/*
* EnterCriticalSection synchronizes code
* with other threads.
*/

EnterCriticalSection(&CriticalSection);

/* Perform a task that may cause an exception. */

lpBuffer = (LPTSTR) LocalAlloc(LPTR, 10);
strcpy(lpBuffer,"Hello");/* possible access violation */
printf("%s\n",lpBuffer);
LocalFree(lpBuffer);
}

/*
* LeaveCriticalSection is called even if
* an exception occurred.
*/

finally {

LeaveCriticalSection(&CriticalSection);
}

構造化例外処理関数

構造化例外処理に関する関数を次に示します。

AbnormalTermination

GetExceptionCode

GetExceptionInformation

RaiseException

SetUnhandledExceptionFilter

UnhandledExceptionFilter

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

Catch

Throw

▲ページトップに戻る

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