【MFC】GetLastError 3「指定されたパスが見つかりません」:コード2との違いと確認順

【MFC】GetLastError 3「指定されたパスが見つかりません」:コード2との違いと確認順

CreateFileW の直後に GetLastError() を読み、3が返ってきた。そんなときにまず疑うのは、末尾のファイル名ではなく、その手前のディレクトリです。3は ERROR_PATH_NOT_FOUND。似たエラーである2(ERROR_FILE_NOT_FOUND)との違いを、実際の戻り値を見ながら切り分けます。


目次

エラー3で最初に見る場所

まず必要なのは、CreateFileW の第1引数へ渡った文字列です。設定画面の値では足りません。途中でフォルダー名を足しているなら、組み立て後のパスを残します。エラー番号は別のAPIを呼ぶ前に保存します。

HANDLE file = ::CreateFileW(path, GENERIC_READ, FILE_SHARE_READ,
    nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file == INVALID_HANDLE_VALUE)
{
    const DWORD error = ::GetLastError(); // ほかのAPIより先に保存
    TRACE(L"CreateFileW failed: path=%s error=%lu\n", path, error);
}

たとえば対象が C:\ProgramData\MyApp\config\app.ini なら、筆者は C:\ProgramDataC:\ProgramData\MyAppC:\ProgramData\MyApp\config の順に見ます。最初の2つがあり、config だけがなければ、ファイル名を調べる前に原因を絞れます。


コード2と3の違い

Microsoftのシステムエラーコード一覧は、2を「指定されたファイルが見つからない」、3を「指定されたパスが見つからない」と分けています。

コード定数見る場所
2ERROR_FILE_NOT_FOUND末尾のファイル名、拡張子、ファイルの生成漏れ
3ERROR_PATH_NOT_FOUND親ディレクトリのうち、最初に存在しなくなる区間

Windows 11、Visual Studio 2026、Debug/x64で小さなMFCダイアログを作り、CreateFileW に4種類のパスを渡しました。既存ファイルは0(成功)。存在するフォルダーの中でファイル名だけを変えると2、途中に存在しないフォルダーを挟むと3でした。相対パスは、カレントディレクトリに置いた既存ファイルを開けています。

CreateFileWで既存ファイル、コード2、コード3、相対パスを比較したMFC実行結果
既存の親フォルダー内で末尾だけがない場合は2、親フォルダーが欠けている場合は3になった実行結果

ただし、2か3だけで「見つからない」系を全部説明できるわけではありません。不正な名前なら123(ERROR_INVALID_NAME)、存在しないドライブなら15(ERROR_INVALID_DRIVE)になることがあります。ここでの2と3の分け方は、今回の CreateFileWOPEN_EXISTING の結果です。


相対パスが指す場所は実行環境で変わる

data\config.ini は、exeの隣を指すとは限りません。サービス化したあとだけ失敗する、タスクスケジューラから起動したときだけ失敗する、といった場合はカレントディレクトリの違いを疑います。Visual Studioからの起動でも、プロジェクト設定次第で基準位置は変わります。

wchar_t fullPath[32768]{};
const DWORD length = ::GetFullPathNameW(
    inputPath, _countof(fullPath), fullPath, nullptr);
if (length == 0)
{
    const DWORD error = ::GetLastError();
    TRACE(L"GetFullPathNameW failed: error=%lu\n", error);
}
else if (length >= _countof(fullPath))
{
    // length は終端の null を含む必要バッファ長
    TRACE(L"full-path buffer too small: required=%lu\n", length);
}
else
{
    TRACE(L"input=%s full=%s\n", inputPath, fullPath);
}

GetFullPathNameW の結果を元の入力と並べると、「コード上で想定した場所」と「Windowsが解決した場所」のずれが見えます。相対指定が不要なら、設定ファイルの保存先など用途ごとの基準フォルダーを決め、そこから絶対パスを組み立てた方が後で追いやすくなります。


最初に欠けたパス区間の見つけ方

絶対パスが分かったら、末尾のファイル名を外して親ディレクトリを一段ずつ調べます。エクスプローラーで見るだけでも切り分けられますが、ログへ組み込むなら GetFileAttributesW が使えます。

const DWORD attributes = ::GetFileAttributesW(directoryPath);
if (attributes == INVALID_FILE_ATTRIBUTES)
{
    const DWORD error = ::GetLastError();
    TRACE(L"missing path part: %s error=%lu\n", directoryPath, error);
}
else if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
{
    TRACE(L"not a directory: %s\n", directoryPath);
}

GetFileAttributesW が成功しても、FILE_ATTRIBUTE_DIRECTORY がなければ、その名前で存在しているのはフォルダーではありません。実際に、設定値を連結するコードでは「途中の名前と同じファイルがあった」という形もあり得ます。


UNCパスでエラー3になる場合

\\server\share\folder\file.dat では、サーバー名、共有名、共有配下のフォルダーを分けて見ます。サーバー名なら名前解決やVPN、共有名なら共有の変更・廃止、配下ならフォルダーの移動や古い設定値が候補です。

ここでは GetFileAttributesW の癖にも注意が要ります。Microsoftの説明では、共有ルート \\server\share に対する属性取得は失敗します。このAPIで試すなら共有配下からです。また、ネットワークへ到達できないときは53(ERROR_BAD_NETPATH)など別の番号も返ります。UNCだから3、と決め打ちはできません。


まとめ

  • 今回の CreateFileWOPEN_EXISTING)検証では、親ディレクトリが欠けると3、存在する親の中で末尾のファイルだけがないと2になりました。
  • 相対パスは GetFullPathNameW で絶対化し、APIへ渡した文字列と並べてログへ残します。
  • 親ディレクトリを先頭から調べ、最初に欠けた区間と、その場で得たエラー番号を記録します。
目次