potisanのプログラミングメモ

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

C++20&Win API Win APIで文字列をA/W変換する

Win APIMultiByteToWideChar関数、WideCharToMultiByte関数でstringwstringを相互変換するコードです。テストはしていません。

C++20からは標準ライブラリのコンバーターが非推奨なので自作するか外部ライブラリを使用する必要があります。

#include <bit>
#include <string>

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

namespace stdw32
{
    std::wstring to_wstring(std::string_view s, DWORD flags = 0)
    {
        auto ret = ::MultiByteToWideChar(CP_ACP, 0, std::bit_cast<LPCCH>(s.data()),
            s.size(), nullptr, 0);
        if (ret == 0) return {};
        std::wstring buf(ret, L'\0');
        ret = ::MultiByteToWideChar(CP_ACP, 0, std::bit_cast<LPCCH>(s.data()),
            s.size(), buf.data(), ret);
        return std::move(buf);
    }

    std::wstring to_wstring(std::u8string_view s, DWORD flags = 0)
    {
        auto ret = ::MultiByteToWideChar(CP_UTF8, 0, std::bit_cast<LPCCH>(s.data()),
            s.size(), nullptr, 0);
        if (ret == 0) return {};
        std::wstring buf(ret, L'\0');
        ret = ::MultiByteToWideChar(CP_UTF8, 0, std::bit_cast<LPCCH>(s.data()),
            s.size(), buf.data(), ret);
        if (ret == 0) return {};
        return std::move(buf);
    }

    std::string to_string(
        std::wstring_view s,
        DWORD flags = 0,
        std::string_view defChar = {},
        LPBOOL usedDefChar = nullptr)
    {
        auto ret = ::WideCharToMultiByte(CP_ACP, 0, s.data(), s.size(),
            nullptr, 0, defChar.data(), usedDefChar);
        if (ret == 0) return {};
        std::string buf(ret, L'\0');
        ret = ::WideCharToMultiByte(CP_ACP, 0, s.data(), s.size(),
            buf.data(), ret, defChar.data(), usedDefChar);
        if (ret == 0) return {};
        return std::move(buf);
    }

    std::u8string to_u8string(
        std::wstring_view s,
        DWORD flags = 0,
        std::string_view defChar = {},
        LPBOOL usedDefChar = nullptr)
    {
        auto ret = ::WideCharToMultiByte(CP_UTF8, 0, s.data(), s.size(),
            nullptr, 0, defChar.data(), usedDefChar);
        if (ret == 0) return {};
        std::u8string buf(ret, L'\0');
        ret = ::WideCharToMultiByte(CP_UTF8, 0, s.data(), s.size(),
            std::bit_cast<char*>(buf.data()), ret, defChar.data(), usedDefChar);
        if (ret == 0) return {};
        return std::move(buf);
    }
}

int main()
{
    auto s1{ stdw32::to_wstring("abcあ🍎") };
    auto s2{ stdw32::to_wstring(u8"abcあ🍎") };
    auto s3{ stdw32::to_string(L"abcあ🍎") };
    auto s4{ stdw32::to_u8string(L"abcあ🍎") };

    return 0;
}

HTML5&JS ファイルを選択して内容を読み込む

HTML5からはINPUT要素にtype="file"が用意され、JavaScriptFile APIBlobTypedArrayと併せてファイル内容を読み込むことができます。以下に簡単なサンプルコードを示します。

ファイルインプットからFileList、Fileを取得する。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Sample</title>
<script>
function f1()
{
  // IDを動的に指定する場合はdocument.getElementById()メソッドを使います。
   const files = file1.files;
   console.log(files);
}
</script>
</head>
<body>
<div style="padding-bottom:.5em;">ファイルを選択するとブラウザの開発者コンソールに情報を表示します。</div>
<div><label>ファイルの選択:<input type="file" id="file1" onchange="f1();"></label></div>
</body>
</html>

ファイルインプットからFileの情報を取得する。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Sample</title>
<script>
function f1()
{
   const files = Array.from(file1.files);
   for (let file of files)
   {
       console.log({
           name:file.name,
           lastModified:Date(file.lastModified),
           size:file.size,
           type:file.type
       });
   }
}
</script>
</head>
<body>
<div style="padding-bottom:.5em;">ファイルを選択するとブラウザの開発者コンソールに情報を表示します。</div>
<div><label>ファイルの選択:<input type="file" id="file1" onchange="f1();" multiple></label></div>
</body>
</html>

ファイルインプットで選択したファイルの先頭5バイトを読み込む(FileReader.readAsArrayBuffer()

FileReader.readAsArrayBuffer()メソッドでファイルデータの先頭5バイトを読み込みます。なお、FileBlobを継承しているため、新しいAPIBlob.arrayBuffer()メソッド(MDN)を使うこともできます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Sample</title>
<script>
function f1()
{
   const files = Array.from(file1.files);
   // type="file"のinputにmultiple属性を付けていないのでファイルは常に1つです。
   if (files.length != 1)
       return;

   // ファイルリーダーを作成します。
   const reader = new FileReader();
   // ファイルデータ読み込み完了時の処理を指定します。
   reader.onloadend = (e) => {
       const data = new Uint8Array(reader.result.slice(0, 5));
       console.log(data);
   };
   // ファイルデータを読み込みます。
   reader.readAsArrayBuffer(files[0]);
}
</script>
</head>
<body>
<div style="padding-bottom:.5em;">ファイルを選択するとブラウザの開発者コンソールに情報を表示します。</div>
<div><label>ファイルの選択:<input type="file" id="file1" onchange="f1();"></label></div>
</body>
</html>

C++20&Win API&WIL コンポーネントカテゴリマネージャーを使う

CLSID_StdComponentCategoriesMgrICatInformationインターフェイスを使ってコンポーネントカテゴリの情報を取得するコードです。カテゴリの概要や所属するクラスを列挙できます。

カテゴリの概要を列挙する

#include <string>

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

// WIL
#include <wil/com.h>

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    auto coinit{ wil::CoInitializeEx(COINIT_APARTMENTTHREADED) };

    auto catInfo{wil::CoCreateInstance<ICatInformation>(CLSID_StdComponentCategoriesMgr)};
    wil::com_ptr<IEnumCATEGORYINFO> enumCatInfo;
    catInfo->EnumCategories(0, enumCatInfo.put());

    CATEGORYINFO info;
    ULONG fetched;
    std::wstring output;
    while (enumCatInfo->Next(1, &info, &fetched) == S_OK)
    {
        output += info.szDescription;
        output += L"\r\n";
    }

    ::MessageBoxW(nullptr, output.c_str(), L"", MB_OK);

    return 0;
}

DeskBandカテゴリのクラスのCLSID・概要を列挙する

#include <bit>
#include <format>
#include <string>

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

// WIL
#include <wil/com.h>
#include <wil/resource.h>

std::wstring CLSIDToWString(const CLSID& clsid)
{
    std::wstring s(38, '\0');
    if (FAILED(::StringFromGUID2(clsid, s.data(), 39)))
        return {};
    return std::move(s);
}

std::wstring RegQueryStringValue(
    HKEY hkey,
    std::wstring_view valueName)
{
    DWORD type;
    DWORD size;
    if (::RegQueryValueEx(hkey, valueName.data(), nullptr, &type,
        nullptr, &size) != ERROR_SUCCESS)
    {
        return {};
    }
    if (type != REG_SZ && type != REG_EXPAND_SZ)
    {
        return {};
    }

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

std::wstring RegQueryStringValue(
    HKEY hkey,
    std::wstring_view subKey,
    DWORD samDesired,
    std::wstring_view valueName)
{
    wil::unique_hkey hkeySub;
    if (::RegOpenKeyExW(hkey, subKey.data(), 0, samDesired, hkeySub.put()) != ERROR_SUCCESS)
        return {};
    return RegQueryStringValue(hkeySub.get(), valueName);
}

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    auto coinit{ wil::CoInitializeEx(COINIT_APARTMENTTHREADED) };

    auto catInfo{ wil::CoCreateInstance<ICatInformation>(CLSID_StdComponentCategoriesMgr) };
    wil::com_ptr<IEnumCLSID> enumClsid;
    THROW_IF_FAILED(catInfo->EnumClassesOfCategories(1, &CATID_DeskBand, 0, nullptr, &enumClsid));

    CLSID clsid;
    ULONG fetched;
    std::wstring output;
    while (enumClsid->Next(1, &clsid, &fetched) == S_OK)
    {
        wil::unique_hkey clsidKey;
        auto clsidStr{ CLSIDToWString(clsid) };
        auto subKeyName{ std::format(L"CLSID\\{}", clsidStr) };
        output += std::format(L"{} {}\r\n",
            clsidStr,
            RegQueryStringValue(HKEY_CLASSES_ROOT, subKeyName, KEY_READ | KEY_WOW64_64KEY, L""));
    }

    ::MessageBoxW(nullptr, output.c_str(), L"", MB_OK);

    return 0;
}

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

C++20&Win API ファイルにUTF-16文字列を出力する

ファイルにUTF-16文字列(std::wstring_viewが対応するデータ)を出力するにはstd::ofstreamをバイナリモードで使用します。次のようなコードを準備しておくと便利だと思います。

#include <fstream>
#include <filesystem>

// 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();
}

std::wofstreamstd::codecvtを組み合わせても出力できるようですが、std::codecvtC++17で非推奨(参考:cpprefjp)なことに注意してください。std::codecvtが未指定の場合、出力時にUTF-16からマルチバイト文字列へ強制変換されます。writeの代わりに<<演算子も使えますが、長さを指定できるのはwriteです。