potisanのプログラミングメモ

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

C++20&WinAPI PEファイルのバージョン情報を取得する

Ver系関数を使ってPEファイルのバージョン情報を取得するサンプルコードです。ひっそりとstd::format(実質fmt)を使用しています。

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

#define STRICT
#define NOMINMAX
#include <Windows.h>
#pragma comment(lib, "version.lib")

class file_version_info final
{
public:
    struct translation final
    {
        union {
            DWORD value;
            struct {
                WORD language;
                WORD codepage;
            };
        };

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

        std::wstring to_subblock_string() const
        {
            return std::format(L"{:04x}{:04x}", language, codepage);
        }

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

public:
    static UINT get_size(std::wstring_view filename, DWORD flags = 0)
    {
        return ::GetFileVersionInfoSizeExW(flags, filename.data(), nullptr);
    }

    file_version_info(std::wstring_view filename, DWORD flags = 0)
    {
        auto size = get_size(filename, flags);
        m_data.resize(size);
        if (!::GetFileVersionInfoExW(flags, filename.data(), 0, size, m_data.data()))
            m_data.clear();
    }

    file_version_info(const file_version_info& source) = delete;

    constexpr file_version_info(file_version_info&& source)
    {
        m_data = std::move(source.m_data);
    }

    constexpr const std::vector<BYTE>& data_vector() const { return m_data; }

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

    const VS_FIXEDFILEINFO& query_root_block() const
    {
        return *reinterpret_cast<const VS_FIXEDFILEINFO*>(query_value(L"\\").data());
    }

    const std::span<translation> query_translations() const
    {
        auto sp = query_value(L"\\VarFileInfo\\Translation");
        return std::span<translation>(
            reinterpret_cast<translation*>(sp.data()), sp.size() / sizeof(translation));
    }

    std::wstring_view query_string_value(translation translation, std::wstring_view string_name) const
    {
        auto sub_block = std::format(L"\\StringFileInfo\\{}\\{}",
            translation.to_subblock_string(), string_name);
        auto sp = query_value(sub_block);
        return std::wstring_view((WCHAR*)sp.data(), sp.size() / sizeof(WCHAR));
    }

    static auto get_predefined_string_names()
    {
        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> query_predefined_string_values(
        translation translation) const
    {
        auto names = get_predefined_string_names();
        std::unordered_map<std::wstring_view, std::wstring_view> map;
        for (const auto& name : names)
        {
            map.insert_or_assign(name, query_string_value(translation, name));
        }
        return std::move(map);
    }

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

class winver_util final
{
public:
    template <DWORD INIT = 100, DWORD EXPAND = 100, DWORD LIMIT = 500>
    static std::wstring get_language_name(DWORD lang)
    {
        std::vector<WCHAR> buffer;
        for (DWORD size = INIT; size < LIMIT; size += EXPAND)
        {
            buffer.resize(size);
            auto ret = ::VerLanguageNameW(lang, buffer.data(), buffer.size());
            if (ret < size)
                return std::wstring(buffer.data(), buffer.size());
        }
        return {};
    }
};

#include <iostream>

void output_info(std::wstring_view filename, DWORD flags)
{
    file_version_info fileVerInfo(filename, flags);

    const auto& root_block = fileVerInfo.query_root_block();
    auto translations = fileVerInfo.query_translations();

    std::wcout << filename << std::endl << std::endl;
    for (auto translation : translations)
    {
        std::wcout << winver_util::get_language_name(translation) << std::endl;
        auto string_values = fileVerInfo.query_predefined_string_values(translation);
        for (const auto& [key, value] : string_values)
        {
            std::wcout << key << L": " << value << std::endl;
        }
        std::wcout << std::endl;
    }
}

int wmain()
{
    // 日本語の出力設定
    std::wcout.imbue(std::locale("Japanese", std::locale::ctype));

    output_info(L"explorer.exe", 0);
    output_info(L"explorer.exe", FILE_VER_GET_LOCALISED);

    return 0;
}