potisanのプログラミングメモ

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

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