【MFC】CreateFileがGetLastError 5(ERROR_ACCESS_DENIED)で失敗するときの確認箇所

【MFC】CreateFileがGetLastError 5(ERROR_ACCESS_DENIED)で失敗するときの確認箇所

CreateFileが失敗してGetLastError() が5(ERROR_ACCESS_DENIED)を返すと、とりあえず管理者として実行して通るかを試したくなりますが、管理者で開けても、標準ユーザーで起動すればまた5になります。先に、書き込み先がProgram Filesのような保護された場所になっていないかと、読むだけのファイルをGENERIC_WRITE付きで開いていないかを確かめます。どちらでもなければ対象ファイルのDACL(UNCパスなら共有のアクセス許可も)を見て、それでも分からなければProcess Monitorで拒否された操作を探します。


目次

エラー5でまず見る場所

たとえば読むつもりで開いて5になったときと、インストール先で設定を書こうとして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を外す

つねにGENERIC_READ | GENERIC_WRITEで開くように書いていると、内容を読むだけのファイルでも、書き込み権のない場所では5になります。dwDesiredAccessに指定した権利のうち1つでも許可されていなければ、読み取りが許可されていてもCreateFileは失敗します。

// 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でGENERIC_READが5になり、要求アクセス0の成功とACL復元後の再オープンを確認した実行結果

Program Filesに書いているなら %LOCALAPPDATA% へ逃がす

実行ファイルと同じフォルダーに書く設定ファイルや、ログの保存先がProgram Filesのような保護された場所になっていると、標準ユーザーで起動したときの書き込みが拒否されて5になります。インストール直後は動いていたのに、一般ユーザーで起動し直すと5になる場合は、CreateFileに渡している保存先のパスを確かめます。

// 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% のような、そのユーザーが書き込める場所へ移します。そうすれば、管理者として実行しなくても標準ユーザーのまま書き込めます。

マニフェストにrequestedExecutionLevelの指定がない32ビットの古いアプリでは、Program Filesへの書き込みが5にならず、%LOCALAPPDATA%\VirtualStore\ へ振り替えられることがあります。この場合はエラーが出ないまま、保存先だけがずれます。マニフェストでの指定と、管理者権限が要る処理だけを別にする方法は「UAC対応: 管理者権限とファイルアクセスの注意点」で扱っています。


GetLastErrorは上書きされる前に保存する

原因を追うときは、失敗したパスとエラーコードをログに残します。GetLastError() の値は、そのあとに呼んだ別のAPIが書き換えることがあるので、ログを書く処理やFormatMessageより先に、失敗した直後に読んで変数へ保存します。

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列が、実際に拒否されているファイルです。想定と違うフォルダーを指していれば、保存先が違っています。想定どおりのファイルなら、下の節のようにそのファイルのアクセス許可を確かめます。行を右クリックして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を確認します。
  • 管理者として実行すれば開ける場合でも、標準ユーザーでは5のままです。要求する権利と保存先を直します。
目次