potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。

C++20&Win32 API&WIL レジストリに登録されたCLSIDの情報をCSVへ書き出す

レジストリに登録されたCLSIDの情報(キー名、概要、InProcServer32のパス)をCSVへ書き出すコードです。コンセプト、std::wstring、バージョン情報の取得などを含んでいます。

main.cpp

レジストリHKEY_CLASSES_ROOT\CLSIDキーのサブキーの名前・既定値・InProcServer32の既定値のベクトルを返します。

#include "stl_util.hpp"

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

#include "FileVersionInfo.hpp"
#include "win32_reg_util.hpp"
#include "win32_util.hpp"

#include <wil/resource.h>

// レジストリのHKEY_CLASSES_ROOT\CLSIDキー以下に登録されたCLSID・概要・InProcServer32のパスをすべて取得します。
std::vector<std::tuple<std::wstring, std::wstring, std::wstring>> GetCLSIDDescriptionInProcServers32()
{
    wil::unique_hkey hkeyClsid;
    THROW_IF_WIN32_ERROR(::RegOpenKeyExW(
        HKEY_CLASSES_ROOT, L"CLSID", 0, KEY_READ | KEY_WOW64_64KEY, hkeyClsid.put()));

    auto clsidKeyNames{ GetSubKeyNames(hkeyClsid.get()) };
    auto maxClsidEntryNameLen{ max_size(clsidKeyNames) };

    std::vector<std::tuple<std::wstring, std::wstring, std::wstring>> info;
    info.reserve(clsidKeyNames.size());
    for (auto& clsidKeyName : clsidKeyNames)
    {
        wil::unique_hkey hkeyClsidEntry;
        if (::RegOpenKeyExW(hkeyClsid.get(), clsidKeyName.c_str(),
            0, KEY_READ | KEY_WOW64_64KEY, hkeyClsidEntry.put()) != ERROR_SUCCESS)
        {
            continue;
        }

        wil::unique_hkey hkeyInprocServer32;
        if (::RegOpenKeyExW(hkeyClsidEntry.get(), L"InprocServer32",
            0, KEY_READ | KEY_WOW64_64KEY, hkeyInprocServer32.put()) != ERROR_SUCCESS)
        {
            continue;
        }

        info.emplace_back(std::make_tuple(
            std::move(clsidKeyName),
            RegReadString(hkeyClsidEntry.get(), nullptr),
            RegReadStringExpanded(hkeyInprocServer32.get(), nullptr)));
    }
    return std::move(info);
}

// ファイルバージョン情報の既定の製品名がある値を一致するか比較します。
// 文字列のcompare結果。
int CompareFileVersionDefaultProductName(std::wstring_view path, std::wstring_view productName)
{
    FileVersionInfo verInfo(path);
    auto Translations{ verInfo.QueryTranslations() };
    if (Translations.empty())
        return {};
    return productName.compare(verInfo.QueryStringValue(Translations[0], L"CompanyName"));
}

// ファイルバージョン情報の既定の製品名がMicrosoft Corporationと一致するか判定します。
bool IsFileMicrosoftProduct(std::wstring_view path)
{
    return CompareFileVersionDefaultProductName(path, L"Microsoft Corporation") == 0;
}

#include <sstream>

int main()
{
    std::wstring output;

    // 見出しの出力
    output += L"CLSID,Description,InProcServer32 Path\r\n";

    // 行の出力
    auto infos{ GetCLSIDDescriptionInProcServers32() };
    for (const auto& [clsid, description, inprocServer32] : infos)
    {
        auto path{ SearchPath(inprocServer32.c_str()) };
        if (!IsFileMicrosoftProduct(path))
            continue;
        output += std::format(L"\"{}\",\"{}\",\"{}\"\r\n", clsid, description, path);
    }

    write_utf16(L"test.csv", output);

    return 0;
}

stl_util.hpp

#include <fstream>
#include <filesystem>
#include <ranges>

// レンジ要素にstd::ranges::size関数が適用可能(コンセプト)
template<typename T>
concept range_value_sized = requires(T) {
    std::ranges::size(std::declval<T>());
};

// レンジ要素のstd::ranges::sizeの最大値を返します。
template <std::ranges::range T>
requires range_value_sized<T>
size_t max_size(const T& r)
{
    size_t max{};
    for (const auto& s : r)
        max = std::max(max, std::size(s));
    return max;
}

// UTF-16文字列を指定されたパスへ出力します。BOMは付加しません。
void write_utf16(std::filesystem::path path, std::wstring_view s)
{
    std::ofstream fout(path, std::ios::binary);
    fout.write(std::bit_cast<const char*>(s.data()), s.size() * sizeof(wchar_t));
    fout.flush();
}

win32_reg_util.hpp

#include <vector>
#include <string>

// レジストリキーのすべてのサブキーの名前を取得します。
// 失敗時は空を返します。
std::vector<std::wstring> GetSubKeyNames(HKEY hkey)
{
    DWORD subKeyCount;
    DWORD maxSubKeyLen;
    if (::RegQueryInfoKeyW(hkey, nullptr, nullptr, nullptr,
        &subKeyCount, &maxSubKeyLen, nullptr, nullptr,
        nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS)
    {
        return {};
    }

    std::vector<std::wstring> subKeyNames;
    subKeyNames.reserve(subKeyCount);
    std::wstring buffer(maxSubKeyLen++, L'\0');
    for (DWORD i = 0; i < subKeyCount; i++)
    {
        auto nameLen{ maxSubKeyLen };
        auto status = ::RegEnumKeyExW(hkey, i, buffer.data(), &nameLen,
            nullptr, nullptr, nullptr, nullptr);
        if (status != ERROR_SUCCESS)
        {
            return {};
        }
        subKeyNames.emplace_back(buffer.c_str());
    }
    return std::move(subKeyNames);
}

// 文字列に含まれる環境変数を展開した文字列を返します。
// 失敗時は空文字列を返します。
std::wstring ExpandEnvironmentStrings(std::wstring_view s)
{
    auto required{ ::ExpandEnvironmentStringsW(s.data(), nullptr, 0) };
    std::wstring s2(required - 1, L'\0');
    if (::ExpandEnvironmentStringsW(s.data(), s2.data(), required) == 0)
        return {};
    return std::move(s2);
}

// レジストリからREG_SZデータを読み込みます。
// 失敗時は空を返します。
std::wstring RegReadString(HKEY hkey, LPCWSTR valueName)
{
    DWORD type;
    DWORD size;
    if (::RegQueryValueExW(hkey, valueName, nullptr, &type, nullptr, &size) != ERROR_SUCCESS)
        return {};
    if (type != REG_SZ)
        return {};

    std::wstring s(size / sizeof(wchar_t) - 1, L'\0');
    if (::RegQueryValueExW(hkey, valueName, nullptr, &type,
        std::bit_cast<LPBYTE>(s.data()), &size) != ERROR_SUCCESS)
    {
        return {};
    }

    return std::move(s);
}

// レジストリからREG_SZ/REG_EXPAND_SZデータを展開して読み込みます。
// 失敗時は空を返します。
std::wstring RegReadStringExpanded(HKEY hkey, LPCWSTR valueName)
{
    DWORD type;
    DWORD size;
    if (DWORD err = ::RegQueryValueExW(hkey, valueName, nullptr, &type, nullptr, &size) != ERROR_SUCCESS)
        return {};
    if (type != REG_SZ && type != REG_EXPAND_SZ)
        return {};

    std::wstring s(size / sizeof(wchar_t) - 1, L'\0');
    if (DWORD err = ::RegQueryValueExW(hkey, valueName, nullptr, &type,
        std::bit_cast<LPBYTE>(s.data()), &size) != ERROR_SUCCESS)
    {
        return {};
    }
    if (type == REG_SZ)
        return std::move(s);

    return ExpandEnvironmentStrings(s);
}

win32_util.hpp

#include <string>

// パスを検索します。
// 失敗時は空を返します。
std::wstring SearchPath(LPCWSTR path)
{
    auto size{ ::SearchPathW(nullptr, path, nullptr, 0, nullptr, nullptr) };
    if (size == 0)
        return {};

    std::wstring s(size - 1, L'\0');
    auto copied{ ::SearchPathW(nullptr, path, nullptr, size, s.data(), nullptr) };
    if (copied == 0)
        return {};
    if (size == copied + 1)
        return std::move(s);
    return s.c_str();
}

FileVersionInfo.hpp

#pragma once

#pragma comment(lib, "version.lib")

#include <array>
#include <bit>
#include <span>
#include <format>
#include <string>
#include <vector>
#include <unordered_map>

// Win32 APIでファイルのバージョン情報を取得するクラス
class FileVersionInfo final
{
public:
    // 言語IDとコードページ
    struct Translation final
    {
        union {
            DWORD value;
            struct {
                WORD Language;
                WORD CodePage;
            };
        };

        Translation(DWORD x) : value(x) {}

        [[nodiscard]]
        inline std::wstring ToSubBlockString() const
        {
            return std::format(L"{:04x}{:04x}", Language, CodePage);
        }

        constexpr operator DWORD() const noexcept
        {
            return value;
        }
    };

public:
    // バージョン情報のバイト数を取得します。
    static UINT GetSize(std::wstring_view filename, DWORD flags = 0) noexcept
    {
        return ::GetFileVersionInfoSizeExW(flags, filename.data(), nullptr);
    }

    // ファイルのバージョン情報を取得します。
    FileVersionInfo(std::wstring_view filename, DWORD flags = 0)
    {
        auto size{ GetSize(filename, flags) };
        m_data.resize(size);
        if (!::GetFileVersionInfoExW(flags, filename.data(), 0, size, m_data.data()))
            m_data.clear();
    }

    FileVersionInfo(const FileVersionInfo& source) = delete;

    constexpr FileVersionInfo(FileVersionInfo&& source) noexcept
        : m_data(std::move(source.m_data))
    {
    }

    // データをバイト形式で返します。
    constexpr std::span<const BYTE> data() const noexcept
    {
        return std::span(m_data.cbegin(), m_data.cend());
    }

    // サブブロックの値をバイトスパンとして取得します。
    const std::span<BYTE> QueryValue(std::wstring_view sub_block) const noexcept
    {
        if (m_data.empty())
            return{};

        LPVOID p;
        UINT size;
        if (!::VerQueryValueW(m_data.data(), sub_block.data(), &p, &size))
            return {};
        if (sub_block.compare(L"\\") != 0)
            size *= sizeof(wchar_t);
        return std::span<BYTE>(std::bit_cast<LPBYTE>(p), size);
    }

    // ルートブロック(固定ファイルバージョン情報)を取得します。
    const VS_FIXEDFILEINFO& QueryRootBlock() const
    {
        return *std::bit_cast<const VS_FIXEDFILEINFO*>(QueryValue(L"\\").data());
    }

    // 対応する言語IDとコードページを取得します。
    const std::span<Translation> QueryTranslations() const
    {
        auto sp{ QueryValue(L"\\VarFileInfo\\Translation") };
        return std::span<Translation>(
            std::bit_cast<Translation*>(sp.data()), sp.size() / sizeof(Translation));
    }

    // 言語IDとコードページに対応する文字列値を取得します。
    std::wstring_view QueryStringValue(Translation Translation, std::wstring_view string_name) const
    {
        auto sub_block{ std::format(L"\\StringFileInfo\\{}\\{}",
            Translation.ToSubBlockString(), string_name) };
        auto sp{ QueryValue(sub_block) };
        if (sp.empty())
            return {};
        return std::wstring_view((WCHAR*)sp.data(), sp.size() / sizeof(WCHAR) - 1);
    }

    // 定義済みの名前を取得します。
    static auto GetPredefinedStringNames()
    {
        return std::array{
            L"Comments", L"FileDescription", L"InternalName",
            L"ProductName", L"CompanyName", L"LegalCopyright",
            L"ProductVersion", L"FileDescription", L"LegalTrademarks",
            L"PrivateBuild", L"FileVersion", L"OriginalFilename",
            L"SpecialBuild" };
    }

    // 定義済みの名前と値のマップを返します。
    std::unordered_map<std::wstring_view, std::wstring_view> QueryPredefinedStringValues(
        Translation Translation) const
    {
        auto names{ GetPredefinedStringNames() };
        std::unordered_map<std::wstring_view, std::wstring_view> map;
        for (const auto& name : names)
        {
            map.insert_or_assign(name, QueryStringValue(Translation, name));
        }
        return std::move(map);
    }

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