potisanのプログラミングメモ

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

C++ WILでCOMを扱う

C++Windows開発する場合、MicrosoftGitHubでMITライセンス公開するWIL(Windows Implementation Libraries)を使えます。WILはSTLと類似した設計でムーブ(譲渡)や範囲forに対応しており、put()wil::out_paramwil::out_param_ptrによりATLのような読みやすいCOMサポートも提供しています。

以下のコードはSTLとWILを組み合わせてデスクトップ上の項目の表示名を列挙します。

高階関数を多用したコード1

// C++20

// STL
#include <vector>
#include <string>
#include <iostream>
#include <functional>
#include <algorithm>

// Win32 API
#pragma comment(lib, "shlwapi.lib")
#define STRICT
#define STRICT_TYPED_ITEMIDS
#define NOMINMAX
#include <Windows.h>
#include <ShlObj.h>
#include <shlwapi.h>

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

namespace wil_util
{
    // 例外ポリシーのSHGetDesktopFolderラッパー関数
    inline wil::com_ptr<IShellFolder> SHGetDesktopFolder()
    {
        wil::com_ptr<IShellFolder> p;
        THROW_IF_FAILED(SHGetDesktopFolder(&p));
        return std::move(p);
    }

    template <typename T>
    concept com_raw_ptr_callable = requires(T t) { wil::com_raw_ptr(t); };

    // IEnumIDListの要素を列挙しながら与えられた式を繰り返し呼び出します。
    template <typename ValueType, com_raw_ptr_callable IEnumIDListPtr>
    std::vector<ValueType> MakeVectorFromIEnumIDListItems(
        IEnumIDListPtr& obj,
        std::function<ValueType(PITEMID_CHILD)> fn)
    {
        wil::unique_cotaskmem_ptr<ITEMID_CHILD> pidl;
        auto raw_obj = wil::com_raw_ptr<IEnumIDList>(obj);
        std::vector<ValueType> result;
        while (raw_obj->Next(1, wil::out_param_ptr<PITEMID_CHILD*>(pidl), nullptr) == S_OK)
        {
            result.push_back(fn(pidl.get()));
            pidl.reset();
        }
        return std::move(result);
    }
}

int main()
{
    // 日本語出力の準備
    std::wcout.imbue(std::locale("Japanese", std::locale::ctype));

    auto couninit = wil::CoInitializeEx(COINIT_APARTMENTTHREADED);

    auto desktop = wil_util::SHGetDesktopFolder();

    wil::com_ptr<IEnumIDList> enumIdList;
    THROW_IF_FAILED(desktop->EnumObjects(
        nullptr,
        SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDESUPERHIDDEN,
        &enumIdList));

    auto displayNames = wil_util::MakeVectorFromIEnumIDListItems<std::wstring>(
        enumIdList,
        [&desktop](PITEMID_CHILD pidl) -> std::wstring {
            STRRET sr;
            if (SUCCEEDED(desktop->GetDisplayNameOf(pidl, SHGDN_NORMAL, &sr)))
            {
                wil::unique_hlocal_string s;
                if (SUCCEEDED(StrRetToStrW(&sr, pidl, &s)))
                {
                    return s.get();
                }
            }
            return {};
        });

    std::ranges::for_each(displayNames,
        [](const std::wstring& displayName) { std::wcout << displayName << std::endl; });

    return 0;
}

高階関数を多用したコード2

// C++20

// STL
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <functional>

// Win32 API
#pragma comment(lib, "shlwapi.lib")
#define STRICT
#define STRICT_TYPED_ITEMIDS
#define NOMINMAX
#include <Windows.h>
#include <ShlObj.h>
#include <shlwapi.h>

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

namespace wil_util
{
    // 例外ポリシーのSHGetDesktopFolderラッパー関数
    inline wil::com_ptr<IShellFolder> SHGetDesktopFolder()
    {
        wil::com_ptr<IShellFolder> p;
        THROW_IF_FAILED(SHGetDesktopFolder(&p));
        return std::move(p);
    }

    template <typename T>
    concept com_raw_ptr_callable = requires(T t) { wil::com_raw_ptr(t); };

    // IEnumIDListの要素を列挙しながら与えられた式を繰り返し呼び出します。
    template <com_raw_ptr_callable IEnumIDListPtr>
    void ForEachIEnumIDListItems(
        IEnumIDListPtr& obj,
        std::function<void(PITEMID_CHILD)> fn)
    {
        wil::unique_cotaskmem_ptr<ITEMID_CHILD> pidl;
        auto raw_obj = wil::com_raw_ptr<IEnumIDList>(obj);
        while (raw_obj->Next(1, wil::out_param_ptr<PITEMID_CHILD*>(pidl), nullptr) == S_OK)
        {
            fn(pidl.get());
            pidl.reset();
        }
    }
}

int main()
{
    // 日本語出力の準備
    std::wcout.imbue(std::locale("Japanese", std::locale::ctype));

    auto couninit = wil::CoInitializeEx(COINIT_APARTMENTTHREADED);

    auto desktop = wil_util::SHGetDesktopFolder();

    wil::com_ptr<IEnumIDList> enumIdList;
    THROW_IF_FAILED(desktop->EnumObjects(
        nullptr,
        SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDESUPERHIDDEN,
        &enumIdList));

    std::vector<std::wstring> displayNames;
    wil_util::ForEachIEnumIDListItems(enumIdList,
        [&](PITEMID_CHILD pidl) -> void {
            STRRET sr;
            if (SUCCEEDED(desktop->GetDisplayNameOf(pidl, SHGDN_NORMAL, &sr)))
            {
                wil::unique_hlocal_string s;
                if (SUCCEEDED(StrRetToStrW(&sr, pidl, &s)))
                {
                    displayNames.emplace_back(s.get());
                }
            }
        });

    std::ranges::for_each(displayNames,
        [](const std::wstring& displayName) { std::wcout << displayName << std::endl; });

    return 0;
}

素直なコード

// C++20

// STL
#include <vector>
#include <string>
#include <iostream>

// Win32 API
#pragma comment(lib, "shlwapi.lib")
#define STRICT
#define STRICT_TYPED_ITEMIDS
#define NOMINMAX
#include <Windows.h>
#include <ShlObj.h>
#include <shlwapi.h>

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

int main()
{
    // 日本語出力の準備
    std::wcout.imbue(std::locale("Japanese", std::locale::ctype));

    auto couninit = wil::CoInitializeEx(COINIT_APARTMENTTHREADED);

    wil::com_ptr<IShellFolder> desktop;
    THROW_IF_FAILED(SHGetDesktopFolder(&desktop));

    wil::com_ptr<IEnumIDList> enumIdList;
    THROW_IF_FAILED(desktop->EnumObjects(
        nullptr,
        SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDESUPERHIDDEN,
        &enumIdList));

    std::vector<wil::unique_cotaskmem_ptr<ITEMID_CHILD>> pidls;
    for (decltype(pidls)::value_type pidl; enumIdList->Next(1, wil::out_param(pidl), nullptr) == S_OK;)
    {
        pidls.push_back(std::move(pidl));
    }

    std::vector<std::wstring> displayNames;
    for (const auto& pidl : pidls)
    {
        STRRET sr;
        if (SUCCEEDED(desktop->GetDisplayNameOf(pidl.get(), SHGDN_NORMAL, &sr)))
        {
            wil::unique_hlocal_string s;
            if (SUCCEEDED(StrRetToStrW(&sr, pidl.get(), &s)))
            {
                displayNames.emplace_back(s.get());
            }
        }
    }

    for (const auto& displayName : displayNames)
    {
        std::wcout << displayName << std::endl;
    }

    return 0;
}