potisanのプログラミングメモ

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

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

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

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

#include <vector>
#include <string>
#include <sstream>
#include <iomanip>

const TCHAR MY_WINDOW_CLASS_NAME[] = TEXT("Project1");
const TCHAR MY_WINDOW_WINDOW_NAME[] = TEXT("Project1");

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

int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int nCmdShow)
{
    if (FAILED(OleInitialize(nullptr)))
        return -1;

    WNDCLASSEX wce;
    wce.cbSize = sizeof(wce);
    wce.style = 0;
    wce.lpfnWndProc = MyWndProc;
    wce.cbClsExtra = 0;
    wce.cbWndExtra = 0;
    wce.hInstance = nullptr;
    wce.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
    wce.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wce.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
    wce.lpszMenuName = nullptr;
    wce.lpszClassName = MY_WINDOW_CLASS_NAME;
    wce.hIconSm = 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 msg.wParam;
}

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

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 std::wstring();
}

// 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:
    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 = { 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::wstringstream ss;
                // TODO:ここで表示する形式を選択してください。
                ss
                    << "SIGDN_NORMALDISPLAY" << TEXT(", ")
                    << "SIGDN_PARENTRELATIVEPARSING" << TEXT(", ")
                    << "SIGDN_DESKTOPABSOLUTEPARSING" << TEXT(", ")
                    << "SIGDN_PARENTRELATIVEEDITING" << TEXT(", ")
                    << "SIGDN_DESKTOPABSOLUTEEDITING" << TEXT(", ")
                    << "SIGDN_FILESYSPATH" << TEXT(", ")
                    << "SIGDN_URL" << TEXT(", ")
                    << "SIGDN_PARENTRELATIVEFORADDRESSBAR" << TEXT(", ")
                    << "SIGDN_PARENTRELATIVE" << TEXT(", ")
                    << "SIGDN_PARENTRELATIVEFORUI" << TEXT("\r\n");

                for (auto i = 0; i < c; i++)
                {
                    IShellItem *pShellItem = nullptr;
                    if (SUCCEEDED(pShellItems->GetItemAt(i, &pShellItem)))
                    {
                        // TODO:ここで表示する形式を選択してください。
                        ss
                            << GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_NORMALDISPLAY) << TEXT(", ")
                            << GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_PARENTRELATIVEPARSING) << TEXT(", ")
                            << GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_DESKTOPABSOLUTEPARSING) << TEXT(", ")
                            << GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_PARENTRELATIVEEDITING) << TEXT(", ")
                            << GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_DESKTOPABSOLUTEEDITING) << TEXT(", ")
                            << GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_FILESYSPATH) << TEXT(", ")
                            << GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_URL) << TEXT(", ")
                            << GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_PARENTRELATIVEFORADDRESSBAR) << TEXT(", ")
                            << GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_PARENTRELATIVE) << TEXT(", ")
                            << GetShellItemDisplayName(pShellItem, SIGDN::SIGDN_PARENTRELATIVEFORUI) << TEXT(", ")
                            << TEXT("\r\n");
                    }
                    pShellItem->Release();
                }
                SetWindowText(m_hwndEdit, ss.str().c_str());
            }
            pShellItems->Release();
        }

        *pdwEffect = DROPEFFECT_LINK;
        return S_OK;
    }

    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,
            TEXT("edit"),
            TEXT("ファイルやURLをドロップしてください。"),
            WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | ES_MULTILINE,
            0, 0, 0, 0,
            hWnd,
            nullptr,
            nullptr,
            nullptr);
        SendMessage(hwndEdit1, WM_SETFONT,
            (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);
}

出力例

デスクトップのPCをドロップした場合

SIGDN_NORMALDISPLAY, SIGDN_PARENTRELATIVEPARSING, SIGDN_DESKTOPABSOLUTEPARSING, SIGDN_PARENTRELATIVEEDITING, SIGDN_DESKTOPABSOLUTEEDITING, SIGDN_FILESYSPATH, SIGDN_URL, SIGDN_PARENTRELATIVEFORADDRESSBAR, SIGDN_PARENTRELATIVE, SIGDN_PARENTRELATIVEFORUI
コントロール パネル, ::{5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0}, ::{5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0}, コントロール パネル, コントロール パネル, , , コントロール パネル, コントロール パネル, コントロール パネル, 
PC, ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}, ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}, PC, PC, , , PC, PC, PC,