potisanのプログラミングメモ

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

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