Win32 API関数が呼び出されると、関数はそれぞれで決められた処理を行ないます。このとき、API関数を呼び出したプログラムが無効な引数を渡したり、何らかの理由で処理を行なうことができなかったりした場合には、そのAPI関数は、戻り値を通して、エラーが発生したことを呼び出し側プログラムに知らせます。
Win32 API関数を呼び出したプログラムが、エラーが発生したかどうかを判断してそれに応じた処理を行なわずに、関数が成功したとみなして処理を続けていってしまうと、最悪の場合、プログラムが暴走するという危険性さえ出てきます。絶対にエラーは起こらない、ということが分かっている場合には必要ありませんが、そうでない場合には適切なエラー処理を行なうべきです。
では、Win32 API関数がどのようなエラー値を返すのかを見ていきましょう。関数のデータ型によってエラーを示す値はさまざまです。いくつか例をあげてみましょう。
VOID型の関数は、戻り値を持ちません。これらの関数は、呼び出し側に返す情報はなく、また、ほとんどエラーを起こす可能性もないものです。したがって、これらの関数の後でエラー処理を行なう必要はほとんどありません。(というより、エラーかどうかの判断を行なうこと自体ができませんが。)
ブール型(BOOL型やBOOLEAN型)を返す関数は、関数が正常に処理を行なった場合には0以外の値が返ります。また、関数がエラーを起こした場合、戻り値は0になります。
ブール型とは、もともと真か偽かを表す型であり、0以外の値である場合を真とみなし、0の場合を偽とみなします。Win32では、便宜上、真(TRUE)を1と定義し、偽(FALSE)を0と定義していますが、実際に真偽を判断する(エラーかどうかを判断する)には、「0か1か」ではなく、「0か、それとも0以外の値か」で判断しなくてはなりません。
PVOID型やLPSTR型などといった、メモリブロックへのポインタ型を戻り値として返す関数は、関数が正常に処理されると、目的のメモリブロックへのポインタ(メモリアドレス)が返されますが、関数が失敗すると0 (NULL) を返します。このような関数は、エラーが起こっていないかどうかを確認しておかないと、返されたアドレスのメモリ領域にアクセスしようとした時に、アドレス0x00000000の領域にアクセスしようとしてアクセス違反が発生し、プログラムが強制終了してしまう可能性があります。
HANDLE型、HGDIOBJ型、HDC型、HMENU型、HWND型などといった、Windowsオブジェクトのハンドルを返す関数は、関数が成功するとオブジェクトのハンドルを返し、関数が失敗すると、ほとんどの関数は0 (NULL) を返します(Windowsオブジェクトについては後述)。しかし、一部の関数には、エラーが発生したときに0 (NULL) ではなくて-1 (INVALID_HANDLE_VALUE) を返すものがあります。例えば、ファイルオブジェクトなどを作成するCreateFile関数は、エラーが発生したときには-1 (INVALID_HANDLE_VALUE) を返します。このような関数に対して、戻り値が0 (NULL) であるかどうかでエラー判断を行なってしまうと、正しく処理できなくなってしまうので、気をつける必要があります。関数がエラー時に-1 (INVALID_HANDLE_VALUE) を返すかどうかはは、API関数のリファレンスを参照して判断しなくてはなりません。
他にもDWORD型やLONG型など、上記のデータ型以外の戻り値を返すAPI関数も、多く存在します。これらの関数の戻り値は関数によってさまざまで、エラーが発生したときにどのような戻り値が返るかはそれぞれの関数によります。これらについてはAPI関数のリファレンスに記述されているとおりにしなければなりません。
さて、Win32 API関数の中には、エラーが発生したときに戻り値でエラーがあることを通知すると同時に、発生したエラーがどのようなものであるかを表すエラーコードを設定するものがあります。
これらの関数の処理中にエラーが発生したとき、呼び出したスレッドにエラーコードが割り当てられます。このエラーコードはスレッドごとに割り当てられるものであるため、マルチスレッドアプリケーションのある1つのスレッドのエラーコードが置き換わっても、別のスレッドのエラーコードは影響を受けないようになっています。(HSPはシングルスレッドであるため、マルチスレッドについてはあまり関係ありませんが。)
関数を呼び出して、返された戻り値がエラーの発生を知らせてきた場合、それがどのようなエラーかを知るためには、GetLastError関数を呼び出します。
DWORD GetLastError();
この関数は、単純にスレッドのエラーコードを戻り値として返します。
このGetLastError関数を使う上で注意すべきことがあります。
まず1つは、関数がエラーを起こした場合には、直ちにGetLastError関数を呼び出すべきであるということです。なぜなら、他のAPI関数を呼び出した場合に、エラーコードが上書きされてしまう可能性があるためです。
次に、すべての関数がエラー発生時にエラーコードを設定するわけではないということです。特に、Windows 95/98/Meでは、関数のコードの多くが16ビット版Windowsの時代の16ビットコードをそのまま用いて実装されているため、そういった関数ではGetLastError関数によるエラーコード報告がされるように実装されていないのです。したがって、これらについては、Win32 API関数のリファレンスを参照して、関数がGetLastError関数によるエラーコード取得をサポートしているかどうかを確認する必要があります。
もう1つは、Win32 API関数は、エラーが発生したときのみエラーコードを書き換えるということです。呼び出されたAPI関数が成功した場合には、このエラーコードは以前のままで、変更されることがありません。したがって、API関数の戻り値がエラーを示す値であった場合にだけ、GetLastError関数を呼び出してエラーコードを取得するようにしなくてはなりません。ただし、一部のWin32 API関数には、成功した場合にもエラーコードを書き換えるものがあります。このような関数はAPIのリファレンスにそのことが明記されています。(例:CreateFileMapping関数やCreateMutex関数など)
GetLastError関数によって、エラーコードを取得することはできますが、ただそれだけではそのエラーコードが何を意味しているのかはわかりません。また、プログラム中でWin32 APIを呼び出したときにエラーを検出した場合には、テキストで書かれた説明を表示したい場合があります。Win32 APIには、エラーコードをテキストに変換するためのFormatMessage関数があります。
DWORD FormatMessageA( DWORD dwFlags, // 動作フラグ PCVOID pSource, // メッセージ定義位置 DWORD dwMessageID, // メッセージID DWORD dwLanguageID, // 言語ID PTSTR pszBuffer, // バッファのアドレス DWORD nSize, // バッファのサイズ va_list *Arguments // 挿入句の配列のアドレス );
システムのエラーコードからテキストを取得する場合、この関数のdwFlagsパラメータにはFORMAT_MESSAGE_FROM_SYSTEMフラグ(0x00001000と定義されている)を指定する必要があります。dwMessageIDにはエラーコードを、pszBufferにはテキストを格納するバッファアドレスを、nSizeにはそのバッファサイズを指定します。
ここで、GetLastError関数を呼び出してエラーコードを取得したり、FormatMessage関数を呼び出してエラーコードからテキスト形式のエラーメッセージを取得したりするモジュール命令を定義してみましょう。エラー処理に役立つはずです。
このモジュールでは、2つのモジュール命令geterrcodeとgeterrtextを定義しています。
パラメータなし
このgeterrcode命令は、GetLastError関数を呼び出して、スレッドのエラーコードを取得し、システム変数statに格納します。
n1:エラーコード
このgeterrtext命令は、FormatMessage関数を呼び出して、n1パラメータで与えられたエラーコードからエラーテキストを取得し、システム変数refstrに格納します。また、システム変数statにはエラーテキストのサイズがバイト単位で格納されます。エラーテキストが取得できなかった場合にはstatの値が0になります。
以下はモジュールスクリプトです。
;################################################################### ; 拡張エラー情報取得モジュール apierr.as ;################################################################### #module "apierr" ; apierr モジュール初期化ルーチン #deffunc _init_apierr mref stt, 64 ; stat mref rfst, 65 ; refstr ; refstr のアドレスを取得 getptr pstr, rfst ; pfn.0 に GetLastError 関数のアドレスを取得 dll_getfunc pfn.0, "GetLastError", D_KERNEL ; pfn.1 に FormatMessage 関数のアドレスを取得 dll_getfunc pfn.1, "FormatMessageA", D_KERNEL return ;=================================================================== ; geterrcode ; スレッドエラーコード取得し stat に格納 ;=================================================================== #deffunc geterrcode ll_callfnv pfn.0 stt = dllret@ ; 戻り値を stat に格納 return ;=================================================================== ; geterrtext n1 ; n1 : エラーコード ; エラーコードからエラーテキストを取得し refstr に格納 ; stat は取得文字列サイズ(stat=0 の時エラー) ;=================================================================== #deffunc geterrtext int mref code ; エラーコード prm = 0x00001000, 0, code, 0, pstr, 4096, 0 ll_callfunc prm, 7, pfn.1 stt = dllret@ ; 戻り値を stat に格納 return #global _init_apierr ; モジュールの初期化
このモジュールスクリプトを見ると、初期化用の命令_init_apierrによって、あらかじめ2つの関数のアドレスの取得を行なっていることが分かります。これは、実行速度を速くするためというもののほかに、もう1つ理由があります。
これはgeterrcode命令内でのみの問題ですが、GetLastError関数の呼び出しにdllproc命令を使うと、関数アドレス取得のために内部でWin32 API関数GetProcAddressが呼び出されてしまい、「直ちにGetLastError関数を呼び出す」という原則を破ることになってしまうためです。あらかじめ関数のアドレスを取得しておき、ll_callfnv命令やll_callfunc命令によってアドレスから直接呼び出すことによって、この問題を回避することができます。
apierr.asモジュールを使って、指定されたエラーコードからエラーテキストを取得して表示する簡単なプログラムを作ってみましょう。
#include "llmod.as"
#include "apierr.as"
sdim buf, 4096
screen 0, 300, 100
title "Win32 API エラーテキスト表示"
objsize 150, 25
pos 0, 4 : mes "エラーコード"
pos 100, 0 : input code, 50, 25
pos 150, 0 : button "テキスト取得", *lb_gettext
pos 0, 25 : mesbox buf, 300, 75
stop
*lb_gettext
;エラーテキストを取得
geterrtext code
if stat == 0 {
dialog "エラーテキストを取得できません。"
} else {
objprm 2, refstr
}
stop