Win32 API の呼び出しが失敗したとき、GetLastError() でエラーコードを取得するのは基本です。しかし、失敗直後に読まないと、別の API が設定したエラーコードを読んでしまうことがあります。
この記事では、GetLastError() をいつ読むべきかと、別 API を挟むと値が変わる理由を解説します。
GetLastError の基本仕様
GetLastError() は、呼び出し元スレッドの最後に設定されたエラーコードを返します。Win32 API の多くは、失敗時に内部で SetLastError() を呼んでエラーコードを設定します。
重要なのは、成功した API もエラーコードを上書きする場合があるという点です。つまり、失敗した API の直後に別の API を呼ぶと、エラーコードが消えたり、別のエラーコードに変わったりします。
失敗直後に読む
GetLastError() の値を元の失敗理由として使えるのは、API が失敗を返した直後、他の API を一切呼ばずに取得した場合です。
// OK: 失敗の直後に GetLastError を呼ぶ
HANDLE hFile = CreateFile(_T("C:\\nonexistent.txt"),
GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
DWORD err = GetLastError(); // ← ここで取得する
// err == ERROR_FILE_NOT_FOUND (2)
}
別 API を挟むと値が変わる
ケース 1:別の API を挟んでしまう
// NG: 別の API を挟んでからエラーコードを取得
HANDLE hFile = CreateFile(_T("C:\\nonexistent.txt"),
GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
// ↓ この OutputDebugString が内部で SetLastError を呼ぶ可能性がある
OutputDebugString(_T("ファイルが開けませんでした\n"));
DWORD err = GetLastError(); // ← 元の失敗理由とは限らない
}
OutputDebugString、TRACE、wprintf など、一見「エラーと無関係」な処理でも内部で Win32 API を呼ぶことがあり、その結果エラーコードが上書きされる可能性があります。
ケース 2:成功時のエラーコードを読む
// NG: API が成功したのに GetLastError を呼ぶ
BOOL ok = DeleteFile(_T("C:\\temp\\test.txt"));
DWORD err = GetLastError(); // ok == TRUE でも err に古い値が残っている
// ↑ 成功時の err は意味がない(前回のエラーコードが残っているだけ)
API が成功した場合、GetLastError() の値は不定です。成功時にエラーコードを 0 にクリアする API もあれば、何もしない API もあります。必ず「失敗を確認してから」呼んでください。
ケース 3:API が「成功 + エラーコード」を返す場合
一部の API は、成功であっても追加情報をエラーコードに設定します。代表例は CreateMutex です。
HANDLE hMutex = CreateMutex(NULL, TRUE, _T("MyMutex"));
if (hMutex != NULL)
{
// 作成には成功したが、既に存在していた場合
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
// 2つ目のインスタンスが起動された
}
}
このような API では、ドキュメントに「成功時でも GetLastError を確認せよ」と明記されています。API ごとのドキュメントを確認してください。
安全なパターン:即座に保存する
エラーコードを確実に捕捉するには、失敗判定の直後にローカル変数に保存するのが鉄則です。
HANDLE hFile = CreateFile(path, GENERIC_READ, 0, NULL,
OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
DWORD dwErr = GetLastError(); // ← 即座に保存
// 以降は dwErr を使う(GetLastError は二度と呼ばない)
CString msg;
msg.Format(_T("CreateFile 失敗: エラーコード %lu"), dwErr);
AfxMessageBox(msg);
return;
}
FormatMessage でエラーコードを文字列に変換する
エラーコードの数値だけではユーザーに伝わりません。FormatMessage を使えば、OS が持つエラーメッセージ文字列に変換できます。
void ShowErrorMessage(DWORD dwErr)
{
LPTSTR pMsg = NULL;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, dwErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&pMsg, 0, NULL);
if (pMsg)
{
// 例: "指定されたファイルが見つかりません。"
AfxMessageBox(pMsg);
LocalFree(pMsg);
}
}
※ FORMAT_MESSAGE_ALLOCATE_BUFFER で確保されたメモリは LocalFree で解放してください。
デバッガでのエラーコード確認
Visual Studio のデバッガには、GetLastError() をコードに書かなくてもエラーコードを確認する方法があります。
ウォッチウィンドウに @err,hr と入力してください。現在のスレッドの最終エラーコードと、その HRESULT 形式の説明が表示されます。
ブレークポイントで停止した時点のエラーコードがリアルタイムで確認できるため、GetLastError() の呼び出しタイミングを気にせずに原因を追えます。
SetLastError(0) で事前クリアする手法
API のドキュメントに「成功時でもエラーコードが設定される」と書かれている場合は、事前に SetLastError(0) でクリアしてから API を呼ぶことで、エラーの有無を明確に判定できます。
SetLastError(0);
LONG prev = SetWindowLongPtr(hWnd, GWL_STYLE, newStyle);
if (prev == 0)
{
DWORD err = GetLastError();
if (err != 0)
{
// 本当に失敗した
}
// err == 0 なら、以前の値が 0 だっただけ(成功)
}
SetWindowLongPtr は、以前の値が 0 の場合と失敗の場合の両方で 0 を返します。事前に SetLastError(0) を呼ぶことで区別できます。
まとめ
GetLastError()は失敗した API の直後に、他の API を挟まずに呼んでください- 取得したエラーコードはローカル変数に即座に保存し、以降はその変数を使ってください
- 別 API を挟んでから読むと、元の失敗理由ではなく、その後の API の結果を読んでしまうことがあります
CreateMutexのように「成功 + エラーコード」のパターンがある API は、ドキュメントで個別に確認してください- デバッガのウォッチウィンドウで
@err,hrを使えば、コードを書かずにエラーコードを確認できます
