【MFC】GetLastError 32「別のプロセスが使用中」:直す共有モードと使用中プロセスの調べ方

【MFC】GetLastError 32「別のプロセスが使用中」:直す共有モードと使用中プロセスの調べ方

CreateFileWやDeleteFileWが失敗して、GetLastError() が32、つまりERROR_SHARING_VIOLATIONを返したときは、同じファイルをほかのハンドルが開いていて、その共有モードと今回の指定がかみ合っていません。メッセージは「別のプロセスが使用中です」ですが、同じプロセスの中で開いたハンドル同士でも起きます。この記事では、先に開いた側と後から開く側のどちらの共有モードを直すかを実行結果で確かめ、ファイルを開いているプロセスをRmGetListで調べます。


目次

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

CreateFileWの説明では、開いているハンドルのアクセスと衝突する共有モードを指定すると、失敗して32になるとされています。次の表の条件を1つでも満たさないと、後から開く側は32で失敗します。

条件満たさないときに直す側
後から開く側のアクセス(読む・書く・消す)が、先のハンドルの共有モードに含まれている先に開いた側(自分のコードでなければ、そのアプリを閉じてもらう)
後から開く側の共有モードが、先のハンドルのアクセスを含んでいる後から開く側(自分のCreateFileWの第3引数)

自分は読むだけでも、相手が書き込み中なら、自分の共有モードにFILE_SHARE_WRITEがないと開けません。


共有モードの組み合わせとエラー32の実行結果

Visual Studio 2026、Debug/x64のMFCダイアログで試しました。1つのログファイルを先に開いたままにして、後から開く側の指定を変えています。

先に開いたハンドルの共有モードと後から開く指定を5通り組み合わせ、GetLastError 32になるケースとRmGetListの結果を表示したMFC実行結果
A・C・D・Eが32、読み手の共有にWRITEを足したBだけが成功した
ケース先のハンドル(アクセス / 共有)後から結果
A. 書き込み中のログを読むWRITE / READREAD / 共有READ32
B. 同じログを共有を広げて読むWRITE / READREAD / 共有READ|WRITE|DELETE成功
C. 共有0で開かれたファイルを読むREAD|WRITE / 0READ / 共有READ|WRITE|DELETE32
D. 読み取り中のファイルへ書くREAD / READWRITE / 共有READ32
E. 開かれたまま削除するREAD / READ|WRITEDeleteFileW32

Aは、読み手の共有にWRITEを足すと開けました(B)。CとDは、先のハンドルが読み取り(C)や書き込み(D)を共有していないので、後から開く側が何を指定しても開けません。Eも同じで、DeleteFileWは、ほかのハンドルがFILE_SHARE_DELETEなしで開いていると失敗します。


書き込み中のファイルを読むならFILE_SHARE_WRITEを足す

別のプロセスが追記しているログを表示する、といった読み手のコードは、共有モードを広く取ります。読み手の共有モードは「自分が開いている間に、ほかのハンドルに何を許すか」の指定です。読むだけなら、書き込みと削除を許しておくと、書き手の追記や削除を邪魔しません。

HANDLE file = ::CreateFileW(logPath, GENERIC_READ,
    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
    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", logPath, error);
}

逆に、書き手が自分のアプリなら、書き手の共有モードにFILE_SHARE_READを入れておきます。ログファイルを共有0で開いたままにすると、ほかのツールからは一切読めなくなります。CFileを使う場合は、CFile::shareDenyWriteがFILE_SHARE_READに、CFile::shareDenyNoneがFILE_SHARE_READ | FILE_SHARE_WRITEに当たります。


ファイルを使っているプロセスをRmGetListで調べる

相手が自分のコードでないときは、どのプロセスが開いているかをログに残すと原因を追えます。Restart ManagerのRmGetListを使えば、ファイルを使っているプロセスのPIDと名前を取得できます(Windows Vista以降、restartmanager.h、Rstrtmgr.lib)。

#include <RestartManager.h>
#pragma comment(lib, "Rstrtmgr.lib")

void TraceFileHolders(LPCWSTR path)
{
    DWORD session = 0;
    WCHAR sessionKey[CCH_RM_SESSION_KEY + 1]{};
    if (::RmStartSession(&session, 0, sessionKey) != ERROR_SUCCESS)
        return;

    LPCWSTR files[] = { path };
    if (::RmRegisterResources(session, 1, files, 0, nullptr, 0, nullptr) == ERROR_SUCCESS)
    {
        RM_PROCESS_INFO processes[10]{};
        UINT needed = 0;
        UINT count = _countof(processes);
        DWORD rebootReasons = RmRebootReasonNone;
        if (::RmGetList(session, &needed, &count, processes, &rebootReasons) == ERROR_SUCCESS)
        {
            for (UINT i = 0; i < count; ++i)
            {
                TRACE(L"in use by PID %lu %s\n",
                    processes[i].Process.dwProcessId, processes[i].strAppName);
            }
        }
    }
    ::RmEndSession(session);
}

別のアプリがファイルを読み書きで開いたままの状態で、上書き保存と同じ条件(書き込み・共有READ)で開くと32になりました。そこでRmGetListを呼ぶと、開いているアプリのPIDと名前が1件返りました。PIDをOpenProcessに渡し、得たハンドルをQueryFullProcessImageNameに渡せば、実行ファイル名も分かります。

別のプロセスが開いたままのファイルを書き込みで開くとGetLastError 32になり、RmGetListでそのプロセスのPID・種類・実行ファイル名を取得したMFC実行結果
別のプロセスが開いているファイルで32になり、RmGetListがそのプロセスのPIDと名前を返した

ファイルを開いているのがRmGetListを呼んだ自分自身のときは、種類(ApplicationType)がRmCriticalで返りました。10件を超えるとRmGetListは234(ERROR_MORE_DATA)を返し、neededに必要な件数が入ります。

なお、似た番号の33(ERROR_LOCK_VIOLATION)は、LockFileなどでファイルの一部の範囲がロックされているときのエラーです。開くときの共有モードの問題ではありません。


まとめ

  • 相手のアクセスを自分の共有モードに含めていなければ、自分の指定で直せます。相手が共有していなければ、相手が閉じるまで開けません。
  • 相手が分からないときは、RmGetListでPIDと名前を取り、失敗時のエラー番号と一緒にログへ残します。
目次