potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C++&WIL パスを含むドライブがSSD等か確認する

動作確認環境:MSVC、C++20 (preview)

Win32 APIDeviceIoControl関数とIOCTL_STORAGE_QUERY_PROPERTYフラグを使ってパスを含むドライブがSSD等か確認するサンプルコードです。オリジナルのソースコードMicrosoft BlogのThe Old New ThingでRaymond Chen氏が公開されている記事であり、ここにSTLコンテナを加えただけのものです。アイデアの権利はRaymond Chen氏にあります。

#include <array>
#include <string>

#define STRICT
#define NOMINMAX
#include <windows.h>

// WIL 1.0.201120.3
#include <wil/resource.h>

// ファイルパスからボリューム名を取得します。
std::wstring GetVolumePathNameW(LPCWSTR path) noexcept(false)
{
    THROW_IF_NULL_ALLOC(path);

    std::array<WCHAR, MAX_PATH> volumePathName;
    THROW_IF_WIN32_BOOL_FALSE(
        ::GetVolumePathNameW(path, volumePathName.data(), volumePathName.size()));
    return volumePathName.data();
}

// ボリュームマウントポイントからボリュームのGUIDパスを取得します。
std::wstring GetVolumeGUIDPathFromVolumeMountPoint(LPCWSTR volumeMountPoint) noexcept(false)
{
    THROW_IF_NULL_ALLOC(volumeMountPoint);

    std::array<WCHAR, MAX_PATH> volumeName;
    THROW_IF_WIN32_BOOL_FALSE(
        ::GetVolumeNameForVolumeMountPointW(
            volumeMountPoint, volumeName.data(), volumeName.size()));
    return volumeName.data();
}

// ファイルパスを含むボリュームのファイルハンドルを取得します。
wil::unique_hfile GetVolumeHandleForPath(LPCWSTR path) noexcept(false)
{
    auto volumePathName = GetVolumePathNameW(path);
    auto volumeGUIDPath = GetVolumeGUIDPathFromVolumeMountPoint(volumePathName.data());
    auto len = volumeGUIDPath.length();
    volumeGUIDPath[len - 1] = L'\0';
    auto hfile = wil::unique_hfile{ ::CreateFileW(
        volumeGUIDPath.data(), 0,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr) };
    THROW_IF_WIN32_ERROR(::GetLastError());
    return hfile;
}

// パスを含むデバイスがシークにペナルティを持つか確認します。
// ペナルティがない場合、おそらくSSDあるいはRAMドライブです。
// 失敗時は例外を発生します。
bool QueryDeviceIncursSeekPenalty(LPCWSTR path) noexcept(false)
{
    auto hfile = GetVolumeHandleForPath(path);

    STORAGE_PROPERTY_QUERY query{
        StorageDeviceSeekPenaltyProperty, PropertyStandardQuery };
    DWORD bytesWritten;
    DEVICE_SEEK_PENALTY_DESCRIPTOR result;
    THROW_IF_WIN32_BOOL_FALSE(DeviceIoControl(hfile.get(),
        IOCTL_STORAGE_QUERY_PROPERTY,
        &query, sizeof(query),
        &result, sizeof(result),
        &bytesWritten, nullptr));
    return result.IncursSeekPenalty != 0;
}

#include <iostream>

int main()
{
    std::cout << "C: " << std::boolalpha << QueryDeviceIncursSeekPenalty(L"C:") << std::endl;
    // Dドライブが存在しない場合、例外が発生します。
    std::cout << "D: " << std::boolalpha << QueryDeviceIncursSeekPenalty(L"D:") << std::endl;

    return 0;
}

補足

  • std::array<WCHAR, MAX_PATH>はこの使い方であればWCHAR[MAX_PATH]std::sizeの組み合わせの方が簡潔かもしれません。ここではSTLコンテナへの統一のため意図的に使用しています。
  • いくつかの関数は戻り値としてstd::wstringを返しています。C++17以降は値のコピー省略(狭義のRVO)が保証されるため、戻り値を変数や引数へ代入してもインスタンスは一度だけ作成されます(コピーコンストラクタは呼び出されません)。
  • THROW_IF_*はWILのマクロです。特定条件で例外を発生します。
  • 関数のnoexcept(false)は省略可能です。省略不可能な(省略するとnoexceptとなる)のはデストラクタ等です。