potisanのプログラミングメモ

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

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;
};