potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

C++&Win API ドライブのジオメトリ情報(物理的なサイズ情報)の取得(IOCTL_DISK_GET_DRIVE_GEOMETRY_EX)

DeviceIoControl関数を用いてドライブの物理ディスクジオメトリ情報(物理的なサイズ情報)を取得するサンプルコードです。この投稿ではIOCTL_DISK_GET_DRIVE_GEOMETRY_EXを用いた場合を紹介します。詳細な説明はいつか。

サンプルコード

#include <bit>
#include <string>
#include <string_view>
#include <sstream>
#include <memory>
#include <vector>
#include <iostream>

#define STRICT
#include <Windows.h>

struct HANDLE_deleter {
    inline void operator()(HANDLE h) const noexcept {
        ::CloseHandle(h);
    }
};
using unique_HANDLE = std::unique_ptr<std::remove_pointer_t<HANDLE>, HANDLE_deleter>;

class DiskGeometry
{
public:
    DiskGeometry(wchar_t driveLetter)
    {
        wchar_t path[7]{ L"\\\\.\\X:" };
        path[4] = driveLetter;

        unique_HANDLE volumeHandle = unique_HANDLE(
            ::CreateFileW(path, 0, 0, nullptr,
                OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
        if (volumeHandle.get() == INVALID_HANDLE_VALUE)
            return;

        m_buffer.resize(sizeof(DISK_GEOMETRY_EX) + sizeof(DISK_PARTITION_INFO) + sizeof(DISK_DETECTION_INFO));
        DWORD returnedBytes{ 0 };
        auto succeeded = ::DeviceIoControl(
            volumeHandle.get(),
            IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
            nullptr, 0,
            m_buffer.data(), static_cast<DWORD>(m_buffer.size()),
            &returnedBytes,
            nullptr);
        if (!succeeded)
            m_buffer.clear();
    }
    DiskGeometry(const DiskGeometry& source)
        : m_buffer(source.m_buffer)
    {
    }
    DiskGeometry(DiskGeometry&& source)
        : m_buffer(std::move(source.m_buffer))
    {
    }

    constexpr const DISK_GEOMETRY_EX& GetDiskGeometryEx() const noexcept {
        return *std::bit_cast<const DISK_GEOMETRY_EX*>(m_buffer.data());
    }
    constexpr const DISK_PARTITION_INFO& GetPartitionInfo() const noexcept {
        return *DiskGeometryGetPartition(&GetDiskGeometryEx());
    }
    constexpr const DISK_DETECTION_INFO& GetDetectionInfo() const noexcept {
        return *DiskGeometryGetDetect(&GetDiskGeometryEx());
    }

private:
    std::vector<BYTE> m_buffer;
};

std::wstring_view GetPartitionStyleName(const DISK_PARTITION_INFO& partationInfo) noexcept;
std::wstring_view GetDetectionTypeName(const DISK_DETECTION_INFO& detectionInfo) noexcept;
std::wstring GUIDToWString(const GUID& guid) noexcept;

int wmain()
{
    DiskGeometry diskGeometryForC('C');

    // 各情報の参照
    auto diskGeometry = diskGeometryForC.GetDiskGeometryEx();
    auto partitionInfo = diskGeometryForC.GetPartitionInfo();
    auto detectionInfo = diskGeometryForC.GetDetectionInfo();

    // 出力
    std::wstringstream output;

    // 全般情報
    output << L"■ジオメトリ情報" << std::endl;
    output << L"(省略)" << std::endl;
    output << L"ディスク当たりのバイト数: " << diskGeometry.DiskSize.QuadPart << std::endl;

    // パーティション情報
    output << L"□パーティション" << std::endl;
    output << L"スタイル: " << GetPartitionStyleName(partitionInfo) << L" (" << partitionInfo.PartitionStyle << L")" << std::endl;
    switch (partitionInfo.PartitionStyle)
    {
    case PARTITION_STYLE::PARTITION_STYLE_MBR:
        output << L"Signature: " << partitionInfo.Mbr.Signature << std::endl;
        output << L"CheckSum: " << partitionInfo.Mbr.CheckSum << std::endl;
        break;
    case PARTITION_STYLE::PARTITION_STYLE_GPT:
        output << L"DiskId: " << GUIDToWString(partitionInfo.Gpt.DiskId) << std::endl;
        break;
    case PARTITION_STYLE::PARTITION_STYLE_RAW:
        break;
    }

    // ディテクション情報
    output << L"□ディテクション" << std::endl;
    output << L"型: " << GetDetectionTypeName(detectionInfo) << L" (" << detectionInfo.DetectionType << L")" << std::endl;
    switch (detectionInfo.DetectionType)
    {
    case DETECTION_TYPE::DetectNone:
        break;
    case DETECTION_TYPE::DetectInt13:
        output << L"DriveSelect: " << detectionInfo.Int13.DriveSelect << std::endl;
        output << L"ヘッド当たりのシリンジの最大数: " << detectionInfo.Int13.MaxCylinders << std::endl;
        output << L"トラック当たりのセクタ数: " << detectionInfo.Int13.SectorsPerTrack << std::endl;
        output << L"ディスクのヘッド数: " << detectionInfo.Int13.MaxHeads << std::endl;
        output << L"ドライブ数: " << detectionInfo.Int13.NumberDrives << std::endl;
        break;
    case DETECTION_TYPE::DetectExInt13:
        output << L"拡張ドライブパラメーターのバッファサイズ: " << detectionInfo.ExInt13.ExBufferSize << std::endl;
        output << L"フラグ: " << detectionInfo.ExInt13.ExFlags << std::endl;
        output << L"シリンダの数: " << detectionInfo.ExInt13.ExCylinders << std::endl;
        output << L"ヘッドの最大数: " << detectionInfo.ExInt13.ExHeads << std::endl;
        output << L"トラック当たりのセクタ数: " << detectionInfo.ExInt13.ExSectorsPerTrack << std::endl;
        output << L"セクタの数: " << detectionInfo.ExInt13.ExSectorsPerDrive << std::endl;
        output << L"セクタのサイズ: " << detectionInfo.ExInt13.ExSectorSize << std::endl;
        output << L"予約: " << detectionInfo.ExInt13.ExReserved << std::endl;
        break;
    }

    std::wcout.imbue(std::locale("", std::locale::ctype));
    std::wcout << output.str();

    return 0;
}

std::wstring_view GetPartitionStyleName(const DISK_PARTITION_INFO& partationInfo) noexcept
{
    switch (partationInfo.PartitionStyle)
    {
    case PARTITION_STYLE::PARTITION_STYLE_MBR: return L"MBR";
    case PARTITION_STYLE::PARTITION_STYLE_GPT: return L"GPT";
    case PARTITION_STYLE::PARTITION_STYLE_RAW: return L"RAW";
    default: return {};
    }
}

std::wstring_view GetDetectionTypeName(const DISK_DETECTION_INFO& detectionInfo) noexcept
{
    switch (detectionInfo.DetectionType)
    {
    case DETECTION_TYPE::DetectNone: return L"None";
    case DETECTION_TYPE::DetectInt13: return L"Int13";
    case DETECTION_TYPE::DetectExInt13: return L"ExInt13";
    default: return {};
    }
}

std::wstring GUIDToWString(const GUID& guid) noexcept
{
    std::wstring s(38, L'\0');
    if (FAILED(::StringFromGUID2(guid, s.data(), 39)))
        return {};
    return std::move(s);
}

メモ

DISK_GEOMETRY_EX構造体の確保方法

DISK_GEOMETRY_EX構造体は末尾にBYTE Data[1]を含む構造体として定義されています。DiskGeometryGetPartitionマクロがこのDataを明示的に型変換してDISK_PARTITION_INFOへのポインタを取得するように実際にはDISK_GEOMETRY_EX構造体のサイズは可変です(厳密にはNTDDI_VERSION < NTDDI_WS03の場合)。

#define DiskGeometryGetPartition(Geometry)\
                        ((PDISK_PARTITION_INFO)((Geometry)->Data))

単純にDISK_GEOMETRY_EX構造体を定義してそのサイズをDeviceIoControl関数に渡すとDISK_GEOMETRY_EXには最小限の情報がセットされます。

実際にはDISK_PARTITION_INFO構造体やDISK_DETECTION_INFO構造体を含むことのできる十分な大きさのバッファを確保してDeviceIoControl関数を呼び出すことでそれらの情報を取得することができます。

上のサンプルコードではstd::vectorを用いて各構造体分のバッファーを確保しています。