CreateFile が失敗して GetLastError() が 5 を返したとき、まず疑うのは2つです。ひとつは Program Files のような保護された場所へ書き込んでいること、もうひとつは読むだけのファイルに GENERIC_WRITE を付けていることです。管理者として実行するのは最後の手段です。この記事では、その2つを先に潰したうえで、残ったときに見る DACL・UNC・Process Monitor の順に説明します。
エラー5でまず見る場所
5(0x5)は ERROR_ACCESS_DENIED です。番号だけでは拒否された理由は分かりません。Microsoft のシステムエラーコード一覧でも定義は「アクセスが拒否されました」だけです。まず症状から見る場所を絞ります。
| 症状 | 先に見る場所 |
|---|---|
| 読むつもりで開くと 5 になる | dwDesiredAccess に GENERIC_WRITE が混ざっていないか |
| 開発PCでは通り、インストール先で 5 になる | 保存先が Program Files 配下になっていないか |
| パスも要求権も正しいのに 5 になる | 対象ファイルの DACL、UNC なら共有アクセス許可 |
| どのアクセスで拒否されたか分からない | Process Monitor で ACCESS DENIED を絞る |
どの行を見るにしても、先に CreateFile へ渡した絶対パスを確定してください。相対パスのままだと、起動方法によって実際に触っているファイルが変わり、調査対象がずれます。
読むだけなら GENERIC_WRITE を外す
いちばん多いのがこれです。内容を読むだけなのに、dwDesiredAccess に GENERIC_READ | GENERIC_WRITE を指定していると、読み取りは許可されていても書き込み権のない場所で 5 になります。
// NG: 読むだけなのに書き込み権まで要求している
HANDLE h = ::CreateFile(path,
GENERIC_READ | GENERIC_WRITE, // ← 書き込み権のない場所で 5 になる
FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, nullptr);
// OK: その処理で本当に使う権利だけにする
HANDLE h = ::CreateFile(path,
GENERIC_READ, // 読むだけなら READ だけ
FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, nullptr);
Windows は、要求されたアクセス権、呼び出し元のアクセストークン、対象ファイルのセキュリティ記述子の3つで可否を判定します(CreateFileW / File Security and Access Rights)。要求する権利が DACL の許可を超えると、同じファイルでも拒否されます。
この関係を Visual Studio 2026 の Debug/x64 で確認しました。一時ファイルから読み取りデータ権を外した DACL に一時変更すると、GENERIC_READ は 5 で失敗し、要求アクセス 0(メタデータ照会)は成功します。元の DACL に戻すと GENERIC_READ でも開けました。要求する権利と DACL の組み合わせで結果が変わることが見て取れます。

Program Files に書いているなら %LOCALAPPDATA% へ逃がす
インストール直後だけ動き、一般ユーザーで起動し直すと 5 になる。この形なら保存先を疑います。実行ファイルと同じフォルダーに設定やログを書いていると、Program Files のような保護された場所への書き込みで拒否されます。
// NG: 実行ファイルと同じフォルダー(Program Files 配下)へ書く
TCHAR path[MAX_PATH];
::GetModuleFileName(nullptr, path, MAX_PATH);
::PathRemoveFileSpec(path);
::PathAppend(path, _T("app.log")); // C:\Program Files\...\app.log → 5
// OK: ユーザーごとに書ける場所へ移す
PWSTR local = nullptr;
::SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &local);
CString logPath = local;
::CoTaskMemFree(local);
logPath += _T("\\MyApp");
::CreateDirectory(logPath, nullptr); // 初回のみ作成される
logPath += _T("\\app.log"); // ...\AppData\Local\MyApp\app.log
管理者として実行すれば開けますが、それだけでは恒久対策になりません。標準ユーザーで動かすアプリなら、書き込むファイルを %LOCALAPPDATA% や %APPDATA% へ移し、誰が書くのかを決め直します。
GetLastError は上書きされる前に保存する
原因を追うには、失敗したパスとエラーコードを残す必要があります。ここで GetLastError() の取得が遅れると、値が別の API に書き換えられます。TRACE や FormatMessage を先に呼ぶと、その内部で 5 が消えることがあります。
HANDLE h = ::CreateFile(path, GENERIC_READ, FILE_SHARE_READ,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (h == INVALID_HANDLE_VALUE)
{
DWORD err = ::GetLastError(); // ← ログを書く前にここで保存する
TRACE(_T("CreateFile 失敗: %s (err=%lu)\n"), (LPCTSTR)path, err);
// 以降は err を使う。GetLastError は二度と呼ばない
return;
}
デバッガで止めているなら、ウォッチウィンドウに @err,hr と入力すれば、コードを書かずに現在のエラーコードとその説明を確認できます。取得タイミングの詳細は「GetLastError() は失敗直後に読む」で扱っています。
原因が見えないときは Process Monitor で ACCESS DENIED を絞る
パスも要求権も正しく見えるのに 5 が出るなら、どの操作が拒否されているかを Process Monitor で直接見ます。憶測で DACL を触る前に、拒否された行を1つ特定するほうが速いです。
フィルターを2つ足します。Process Name を対象の exe に、Result を ACCESS DENIED に設定します。残った行の Path 列が、実際に拒否されているファイルです。想定と違うフォルダーを指していれば保存先の問題、想定どおりなら DACL の問題だと切り分けられます。行を右クリックして Properties を開けば、要求したアクセス権も確認できます。
DACL・UNC が残ったときの確認
対象ファイルが確定し、要求権も正しいのに拒否されるなら、そのファイルと親フォルダーのアクセス許可を見ます。コマンドプロンプトで icacls を使うと、誰にどの権利があるかを一覧できます。
icacls "C:\ProgramData\MyApp\config.ini"
// 実行ユーザーに (R) や (W) が付いているかを見る
// (N) や拒否ACE があれば、そこで止まっている
アプリの実行中に既存ファイルの DACL を書き換えて回避するのは避けます。対象を取り違えたり、拒否 ACE で別の読み書きまで止めたりすると、元に戻す手間が増えます。変更するなら対象とユーザーを限定し、インストーラーか運用手順として管理します。
ローカルでは開けるのに UNC パス(\\server\share\...)だけ 5 になる場合は、NTFS の DACL に加えて共有アクセス許可も対象です。両方の許可の重なりが有効な権利になるため、共有側で読み取りしか許可されていないと書き込みで拒否されます。接続に使われたユーザーが想定どおりかも併せて確認します。
まとめ
- 5 は
ERROR_ACCESS_DENIED。まず疑うのは保存先(Program Files)と、読むだけのファイルへのGENERIC_WRITEです。 - 読み取りは
GENERIC_READだけにし、書き込みは%LOCALAPPDATA%など書ける場所へ移します。 GetLastError()は失敗判定の直後に保存し、ログはそのあとで書きます。- 原因が見えないときは Process Monitor で
ACCESS DENIEDを絞り、icaclsで DACL を確認します。 - 管理者実行は回避策であって対策ではありません。要求権と保存先を直すのが先です。
