potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。

C++20&Win API ドロップされたOLEデータオブジェクトに含まれるShellItemを列挙する

ドロップされたOLEデータオブジェクトに含まれるShellItem(ファイル、フォルダ、特殊フォルダ等)を列挙するサンプルコードです。

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

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

auto MY_WINDOW_CLASS_NAME{ L"Project1" };
auto MY_WINDOW_WINDOW_NAME{ L"Project1" };

LRESULT CALLBACK MyWndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam);

int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int nCmdShow)
{
    if (HRESULT hr = OleInitialize(nullptr); FAILED(hr))
        return hr;

    WNDCLASSEX wce{
        sizeof(wce),
        0,
        MyWndProc,
        0,
        0,
        nullptr,
        LoadIcon(nullptr, IDI_APPLICATION),
        LoadCursor(nullptr, IDC_ARROW),
        reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1),
        nullptr,
        MY_WINDOW_CLASS_NAME,
        LoadIcon(nullptr, IDI_APPLICATION) };
    if (!RegisterClassEx(&wce))
        return 0;

    auto hWnd{ CreateWindowEx(
        WS_EX_OVERLAPPEDWINDOW,
        MY_WINDOW_CLASS_NAME,
        MY_WINDOW_WINDOW_NAME,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        nullptr,
        nullptr,
        nullptr,
        nullptr) };
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    OleUninitialize();

    return static_cast<int>(msg.wParam);
}

template<DWORD INIT = 256, DWORD EXPANDING = 256>
std::wstring GetClipboardFormatNameW(CLIPFORMAT cf)
{
    std::wstring buffer;
    for (DWORD size = INIT;; size += EXPANDING)
    {
        buffer.resize(size);
        auto copied{ GetClipboardFormatNameW(cf, buffer.data(), size) };
        if (copied != buffer.size() - 1)
        {
            buffer.resize(copied);
            buffer.shrink_to_fit();
            return std::move(buffer);
        }
    }
}

std::wstring GetShellItemDisplayName(IShellItem* pShellItem, SIGDN sigdnName)
{
    LPWSTR pszName;
    if (SUCCEEDED(pShellItem->GetDisplayName(sigdnName, &pszName)))
    {
        std::wstring s{ pszName };
        ::CoTaskMemFree(pszName);
        return std::move(s);
    }
    return {};
}

// https://docs.microsoft.com/ja-jp/windows/desktop/api/oleidl/nn-oleidl-idroptarget
class CMyDropTarget : public IDropTarget
{
private:
    ULONG m_ref;
    HWND m_hwndEdit;

    CMyDropTarget() {}

public:
    constexpr CMyDropTarget(HWND hwndEdit)
        : m_ref(1), m_hwndEdit(hwndEdit)
    {
    }

    HRESULT QueryInterface(REFIID riid, void** ppvObject)
    {
        if (IsEqualIID(riid, __uuidof(IUnknown)))
        {
            *ppvObject = static_cast<IUnknown*>(this);
        }
        else if (IsEqualIID(riid, __uuidof(IDropTarget)))
        {
            *ppvObject = static_cast<IDropTarget*>(this);
        }
        else
        {
            return E_NOINTERFACE;
        }
        AddRef();
        return S_OK;
    }

    ULONG AddRef()
    {
        return InterlockedIncrement(&m_ref);
    }

    ULONG Release(void)
    {
        if (InterlockedDecrement(&m_ref) == 0)
        {
            delete this;
            return 0;
        }
        return m_ref;
    }

    HRESULT STDMETHODCALLTYPE DragEnter(
        IDataObject* pDataObj,
        DWORD grfKeyState,
        POINTL pt,
        DWORD* pdwEffect)
    {
        *pdwEffect = DROPEFFECT_LINK;
        return S_OK;
    }

    HRESULT DragOver(
        DWORD grfKeyState,
        POINTL pt,
        DWORD* pdwEffect)
    {
        return S_OK;
    }

    HRESULT DragLeave()
    {
        return S_OK;
    }

    HRESULT Drop(
        IDataObject* pDataObj,
        DWORD grfKeyState,
        POINTL pt,
        DWORD* pdwEffect)
    {
        std::vector<CLIPFORMAT> cfs;

        FORMATETC fmtetc{ static_cast<FORMATETC>(RegisterClipboardFormat(CFSTR_SHELLIDLIST)) };
        if (!pDataObj->QueryGetData(&fmtetc))
            return S_OK;

        IShellItemArray* pShellItems{ nullptr };
        if (SUCCEEDED(SHCreateShellItemArrayFromDataObject(pDataObj,
            IID_IShellItemArray, (void**)&pShellItems)))
        {
            DWORD c;
            if (SUCCEEDED(pShellItems->GetCount(&c)))
            {
                std::wstring ss;
                ss.append(std::format(
                    L"SIGDN_NORMALDISPLAY, "
                    "SIGDN_PARENTRELATIVEPARSING,"
                    "SIGDN_DESKTOPABSOLUTEPARSING, "
                    "SIGDN_PARENTRELATIVEEDITING, "
                    "SIGDN_DESKTOPABSOLUTEEDITING, "
                    "SIGDN_FILESYSPATH, "
                    "SIGDN_URL, "
                    "SIGDN_PARENTRELATIVEFORADDRESSBAR, "
                    "SIGDN_PARENTRELATIVE, "
                    "SIGDN_PARENTRELATIVEFORUI\r\n"));

                for (UINT i = 0; i < c; i++)
                {
                    IShellItem* pShellItem{ nullptr };
                    if (SUCCEEDED(pShellItems->GetItemAt(i, &pShellItem)))
                    {
                        ss.append(std::format(L"{}, {}, {}, {}, {}, {}, {}, {}, {}, {}\r\n",
                            GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_NORMALDISPLAY),
                            GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_PARENTRELATIVEPARSING),
                            GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_DESKTOPABSOLUTEPARSING),
                            GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_PARENTRELATIVEEDITING),
                            GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_DESKTOPABSOLUTEEDITING),
                            GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_FILESYSPATH),
                            GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_URL),
                            GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_PARENTRELATIVEFORADDRESSBAR),
                            GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_PARENTRELATIVE),
                            GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_PARENTRELATIVEFORUI)));
                    }
                    pShellItem->Release();
                }
                SetWindowText(m_hwndEdit, ss.c_str());
            }
            pShellItems->Release();
        }

        *pdwEffect = DROPEFFECT_LINK;
        return S_OK;
    }

    constexpr HWND GetEditWindowHandle() const
    {
        return m_hwndEdit;
    }
};

CMyDropTarget* pDropTarget{ nullptr };

LRESULT CALLBACK MyWndProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uiMsg)
    {
    case WM_CREATE:
        {
            // データ表示用のテキストボックス作成
            auto hwndEdit1{ CreateWindowEx(
                WS_EX_CLIENTEDGE,
                L"edit",
                L"ファイルやURLをドロップしてください。",
                WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | ES_MULTILINE,
                0, 0, 0, 0,
                hWnd,
                nullptr,
                nullptr,
                nullptr) };
            SendMessage(hwndEdit1, WM_SETFONT,
                std::bit_cast<WPARAM>(GetStockObject(DEFAULT_GUI_FONT)),
                MAKELPARAM(FALSE, 0));
            // ドロップ対象として登録
            pDropTarget = new CMyDropTarget(hwndEdit1);
            RegisterDragDrop(hWnd, pDropTarget);
            pDropTarget->Release();
            return 0;
        }
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_QUIT:
        RevokeDragDrop(hWnd);
        return 0;
    case WM_SIZE:
        MoveWindow(
            pDropTarget->GetEditWindowHandle(),
            0, 0,
            LOWORD(lParam), HIWORD(lParam),
            TRUE);
        return 0;
    }

    return DefWindowProc(hWnd, uiMsg, wParam, lParam);
}