potisanのプログラミングメモ

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

C++20&Win API&WIL IShellItemで既知フォルダのフォルダIDと名前を列挙する

IShellItemインターフェイスで既知フォルダ(Known Folder)のフォルダID(識別子)と名前を列挙するコードです。実行するとデバッグウィンドウに既知フォルダの数だけ「フォルダID: 名前」を出力します。

#include <format>
#include <ranges>
#include <vector>

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

#include "wil/com.h"

// レンジからベクトルを作成します。
template <std::ranges::range Range>
inline constexpr std::vector<std::ranges::range_value_t<Range>> to_vector(const Range& r)
{
    return std::vector(std::ranges::cbegin(r), std::ranges::cend(r));
}

// KNOWNFOLDER_DEFINITION構造体のWILリソースラッパーです。
using unique_knownfolder_definition = wil::unique_struct<
    KNOWNFOLDER_DEFINITION,
    decltype(&FreeKnownFolderDefinitionFields),
    FreeKnownFolderDefinitionFields>;

// wil::com_raw_ptrの型を返します。
template <typename T>
using com_raw_ptr_t = decltype(wil::com_raw_ptr(std::declval<T>()));

// 型Tの変数にwil::com_raw_ptrを適用した戻り値がInterface*型か判定します。
template<typename T, typename Interface>
concept com_raw_ptr_of = std::convertible_to<com_raw_ptr_t<T>, Interface*>;

// IKnownFolderManager::GetFolderIdsを呼び出します。
template<com_raw_ptr_of<IKnownFolderManager> T>
wil::unique_cotaskmem_array_ptr<GUID> GetFolderIds(T& pmanager)
{
    GUID* pfolderIds;
    UINT folderIdCount;
    THROW_IF_FAILED(wil::com_raw_ptr(pmanager)->GetFolderIds(&pfolderIds, &folderIdCount));
    return wil::unique_cotaskmem_array_ptr<GUID>(pfolderIds, folderIdCount);
}

// GUIDをブラケットで挟まれた文字列表現に変換します。
// 失敗時は空の文字列を返します。
inline std::wstring GUIDToWString(const GUID& guid)
{
    const auto size{ std::size(L"{00000000-0000-0000-0000-000000000000}") };
    std::wstring s(size - 1, L'\0');
    return (StringFromGUID2(guid, s.data(), static_cast<int>(size) + 1) != 0)
        ? std::move(s) : std::wstring();
}

int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
{
    // COMを初期化する。
    auto coinit{ wil::CoInitializeEx(COINIT_APARTMENTTHREADED) };

    // KnownFolderManagerのインスタンスを作成する。
    auto manager{ wil::CoCreateInstance<KnownFolderManager, IKnownFolderManager>() };

    // 特殊フォルダのフォルダIDと名前を列挙する。
    wil::unique_cotaskmem_array_ptr<GUID> folderIds{ GetFolderIds(manager) };
    auto view{ folderIds | std::ranges::views::transform(
        [&manager](const auto& folderId)
        {
            wil::com_ptr<IKnownFolder> folder;
            THROW_IF_FAILED(manager->GetFolder(folderId, folder.put()));

            unique_knownfolder_definition definition;
            THROW_IF_FAILED(folder->GetFolderDefinition(&definition));

            return std::make_tuple(folderId, std::wstring(definition.pszName));
        }
    ) };

    // 確認用にデバッガーへ出力します。
    // デバッグ用にstd::vectorにします。
    auto infos{ to_vector(view) };
    for (const auto& info : infos)
    {
        auto s{ std::format(L"{}: {}\n",
            GUIDToWString(std::get<GUID>(info)),
            std::get<std::wstring>(info)) };
        OutputDebugStringW(s.c_str());
    }

    return 0;
}