[SHELL32][SHELL32_APITEST] Add SHGetAttributesFromDataObject
authorMark Jansen <mark.jansen@reactos.org>
Fri, 23 Jul 2021 18:34:02 +0000 (20:34 +0200)
committerMark Jansen <mark.jansen@reactos.org>
Mon, 15 Nov 2021 19:02:13 +0000 (20:02 +0100)
dll/win32/shell32/CMakeLists.txt
dll/win32/shell32/shldataobject.cpp [new file with mode: 0644]
dll/win32/shell32/stubs.cpp
modules/rostests/apitests/shell32/CMakeLists.txt
modules/rostests/apitests/shell32/SHGetAttributesFromDataObject.cpp [new file with mode: 0644]
modules/rostests/apitests/shell32/testlist.c
sdk/include/psdk/shlobj.h
sdk/include/reactos/shellutils.h

index 4ebe876..2b1f658 100644 (file)
@@ -53,6 +53,7 @@ list(APPEND SOURCE
     droptargets/CexeDropHandler.cpp
     droptargets/CFSDropTarget.cpp
     droptargets/CRecyclerDropTarget.cpp
+    shldataobject.cpp
     shlexec.cpp
     shlfileop.cpp
     shlfolder.cpp
diff --git a/dll/win32/shell32/shldataobject.cpp b/dll/win32/shell32/shldataobject.cpp
new file mode 100644 (file)
index 0000000..4b6d318
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * PROJECT:     shell32
+ * LICENSE:     LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
+ * PURPOSE:     SHGetAttributesFromDataObject implementation
+ * COPYRIGHT:   Copyright 2021 Mark Jansen <mark.jansen@reactos.org>
+ */
+
+
+#include "precomp.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(shell);
+
+
+static CLIPFORMAT g_DataObjectAttributes = 0;
+static const DWORD dwDefaultAttributeMask = SFGAO_CANCOPY | SFGAO_CANMOVE | SFGAO_STORAGE | SFGAO_CANRENAME |
+                                            SFGAO_CANDELETE | SFGAO_READONLY | SFGAO_STREAM | SFGAO_FOLDER;
+
+struct DataObjectAttributes
+{
+    DWORD dwMask;
+    DWORD dwAttributes;
+    UINT cItems;
+};
+
+static_assert(sizeof(DataObjectAttributes) == 0xc, "Unexpected struct size!");
+
+
+static
+HRESULT _BindToObject(PCUIDLIST_ABSOLUTE pidl, CComPtr<IShellFolder>& spFolder)
+{
+    CComPtr<IShellFolder> spDesktop;
+    HRESULT hr = SHGetDesktopFolder(&spDesktop);
+    if (FAILED(hr))
+        return hr;
+
+    return spDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &spFolder));
+}
+
+EXTERN_C
+HRESULT WINAPI SHGetAttributesFromDataObject(IDataObject* pDataObject, DWORD dwAttributeMask, DWORD* pdwAttributes, UINT* pcItems)
+{
+    DWORD dwAttributes = 0;
+    DWORD cItems = 0;
+    HRESULT hr = S_OK;
+
+    TRACE("(%p, 0x%x, %p, %p)\n", pDataObject, dwAttributeMask, pdwAttributes, pcItems);
+
+    if (!g_DataObjectAttributes)
+        g_DataObjectAttributes = (CLIPFORMAT)RegisterClipboardFormatW(L"DataObjectAttributes");
+
+    if (pDataObject)
+    {
+        DataObjectAttributes data = {};
+        if (FAILED(DataObject_GetData(pDataObject, g_DataObjectAttributes, &data, sizeof(data))))
+        {
+            TRACE("No attributes yet, creating new\n");
+            memset(&data, 0, sizeof(data));
+        }
+
+        DWORD dwQueryAttributes = dwAttributeMask | dwDefaultAttributeMask;
+
+        if ((data.dwMask & dwQueryAttributes) != dwQueryAttributes)
+        {
+            CDataObjectHIDA hida(pDataObject);
+            CComPtr<IShellFolder> spFolder;
+
+            if (!FAILED_UNEXPECTEDLY(hr = hida.hr()) &&
+                !FAILED_UNEXPECTEDLY(hr = _BindToObject(HIDA_GetPIDLFolder(hida), spFolder)))
+            {
+                CSimpleArray<PCUIDLIST_RELATIVE> apidl;
+                for (UINT n = 0; n < hida->cidl; ++n)
+                {
+                    apidl.Add(HIDA_GetPIDLItem(hida, n));
+                }
+
+                SFGAOF rgfInOut = dwQueryAttributes;
+                hr = spFolder->GetAttributesOf(apidl.GetSize(), apidl.GetData(), &rgfInOut);
+                if (!FAILED_UNEXPECTEDLY(hr))
+                {
+                    data.dwMask = dwQueryAttributes;
+                    // Only store what we asked for
+                    data.dwAttributes = rgfInOut & dwQueryAttributes;
+                    data.cItems = apidl.GetSize();
+
+                    hr = DataObject_SetData(pDataObject, g_DataObjectAttributes, &data, sizeof(data));
+                    FAILED_UNEXPECTEDLY(hr);
+                }
+            }
+        }
+
+        // Only give the user what they asked for, not everything else we have!
+        dwAttributes = data.dwAttributes & dwAttributeMask;
+        cItems = data.cItems;
+    }
+
+    if (pdwAttributes)
+        *pdwAttributes = dwAttributes;
+
+    if (pcItems)
+        *pcItems = cItems;
+
+    return hr;
+}
index a5061ad..2d49322 100644 (file)
@@ -1302,20 +1302,6 @@ DWORD WINAPI SHGetComputerDisplayNameW(DWORD param1, DWORD param2, DWORD param3,
     return E_FAIL;
 }
 
-/*
- * Unimplemented
- */
-EXTERN_C HRESULT
-WINAPI
-SHGetAttributesFromDataObject(IDataObject *pdo,
-                              DWORD dwAttributeMask,
-                              DWORD *pdwAttributes,
-                              UINT *pcItems)
-{
-    FIXME("SHGetAttributesFromDataObject() stub\n");
-    return E_NOTIMPL;
-}
-
 /*
  * Unimplemented
  */
index 990e146..7a17503 100644 (file)
@@ -27,6 +27,7 @@ list(APPEND SOURCE
     ShellExecuteW.cpp
     ShellHook.cpp
     ShellState.cpp
+    SHGetAttributesFromDataObject.cpp
     SHLimitInputEdit.cpp
     menu.cpp
     shelltest.cpp)
diff --git a/modules/rostests/apitests/shell32/SHGetAttributesFromDataObject.cpp b/modules/rostests/apitests/shell32/SHGetAttributesFromDataObject.cpp
new file mode 100644 (file)
index 0000000..937045c
--- /dev/null
@@ -0,0 +1,319 @@
+/*
+ * PROJECT:     ReactOS api tests
+ * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE:     Test for SHGetAttributesFromDataObject
+ * COPYRIGHT:   Copyright 2021 Mark Jansen <mark.jansen@reactos.org>
+ */
+
+#include "shelltest.h"
+#include <ndk/rtlfuncs.h>
+#include <stdio.h>
+#include <shellutils.h>
+#include <shlwapi.h>
+
+
+static CLIPFORMAT g_DataObjectAttributes = 0;
+static const DWORD dwDefaultAttributeMask = SFGAO_CANCOPY | SFGAO_CANMOVE | SFGAO_STORAGE | SFGAO_CANRENAME | SFGAO_CANDELETE |
+                                     SFGAO_READONLY | SFGAO_STREAM | SFGAO_FOLDER;
+static_assert(dwDefaultAttributeMask == 0x2044003B, "Unexpected default attribute mask");
+
+
+struct TmpFile
+{
+    WCHAR Buffer[MAX_PATH] = {};
+
+    void Create(LPCWSTR Folder)
+    {
+        GetTempFileNameW(Folder, L"SHG", 0, Buffer);
+    }
+
+    ~TmpFile()
+    {
+        if (Buffer[0])
+        {
+            SetFileAttributesW(Buffer, FILE_ATTRIBUTE_NORMAL);
+            DeleteFileW(Buffer);
+        }
+    }
+};
+
+
+CComPtr<IShellFolder> _BindToObject(PCUIDLIST_ABSOLUTE pidl)
+{
+    CComPtr<IShellFolder> spDesktop, spResult;
+    HRESULT hr = SHGetDesktopFolder(&spDesktop);
+    if (FAILED_UNEXPECTEDLY(hr))
+        return spResult;
+
+    if (FAILED_UNEXPECTEDLY(spDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &spResult))))
+    {
+        spResult.Release();
+    }
+    return spResult;
+}
+
+
+static void ok_attributes_(IDataObject* pDataObject, HRESULT expect_hr, DWORD expect_mask, DWORD expect_attr, UINT expect_items)
+{
+    FORMATETC fmt = { g_DataObjectAttributes, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+    STGMEDIUM medium = {};
+
+    HRESULT hr = pDataObject->GetData(&fmt, &medium);
+    winetest_ok(hr == expect_hr, "Unexpected result from GetData, got 0x%lx, expected 0x%lx\n", hr, expect_hr);
+
+    if (hr == expect_hr && expect_hr == S_OK)
+    {
+        LPVOID blob = GlobalLock(medium.hGlobal);
+        winetest_ok(blob != nullptr, "Failed to lock hGlobal\n");
+        if (blob)
+        {
+            SIZE_T size = GlobalSize(medium.hGlobal);
+            winetest_ok(size == 0xc, "Unexpected size, got %lu, expected 12\n", size);
+            if (size == 0xc)
+            {
+                PDWORD data = (PDWORD)blob;
+                winetest_ok(data[0] == expect_mask, "Unexpected mask, got 0x%lx, expected 0x%lx\n", data[0], expect_mask);
+                winetest_ok(data[1] == expect_attr, "Unexpected attr, got 0x%lx, expected 0x%lx\n", data[1], expect_attr);
+                winetest_ok(data[2] == expect_items, "Unexpected item count, got %lu, expected %u\n", data[2], expect_items);
+            }
+            GlobalUnlock(medium.hGlobal);
+        }
+    }
+
+    if (SUCCEEDED(hr))
+        ReleaseStgMedium(&medium);
+}
+
+
+#define ok_attributes           (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : ok_attributes_
+#define ok_hr_ret(x, y)         ok_hr(x, y); if (x != y) return
+
+static void test_SpecialCases()
+{
+    DWORD dwAttributeMask = 0, dwAttributes = 123;
+    UINT cItems = 123;
+
+    HRESULT hr = SHGetAttributesFromDataObject(nullptr, dwAttributeMask, &dwAttributes, &cItems);
+    ok_hr(hr, S_OK);
+    ok_int(dwAttributes, 0);
+    ok_int(cItems, 0);
+
+    cItems = 123;
+    hr = SHGetAttributesFromDataObject(nullptr, dwAttributeMask, nullptr, &cItems);
+    ok_hr(hr, S_OK);
+    ok_int(cItems, 0);
+
+    dwAttributes = 123;
+    hr = SHGetAttributesFromDataObject(nullptr, dwAttributeMask, &dwAttributes, nullptr);
+    ok_hr(hr, S_OK);
+    ok_int(dwAttributes, 0);
+}
+
+
+static void test_AttributesRegistration()
+{
+    WCHAR Buffer[MAX_PATH] = {};
+
+    GetModuleFileNameW(NULL, Buffer, _countof(Buffer));
+    CComHeapPtr<ITEMIDLIST_ABSOLUTE> spPath(ILCreateFromPathW(Buffer));
+
+    ok(spPath != nullptr, "Unable to create pidl from %S\n", Buffer);
+    if (spPath == nullptr)
+        return;
+
+    SFGAOF attributes = dwDefaultAttributeMask;
+    HRESULT hr;
+    {
+        CComPtr<IShellFolder> spFolder;
+        PCUITEMID_CHILD child;
+        hr = SHBindToParent(spPath, IID_PPV_ARG(IShellFolder, &spFolder), &child);
+        ok_hr_ret(hr, S_OK);
+
+        hr = spFolder->GetAttributesOf(1, &child, &attributes);
+        ok_hr_ret(hr, S_OK);
+
+        attributes &= dwDefaultAttributeMask;
+    }
+
+    CComHeapPtr<ITEMIDLIST> parent(ILClone(spPath));
+    PCIDLIST_RELATIVE child = ILFindLastID(spPath);
+    ILRemoveLastID(parent);
+
+    CComPtr<IDataObject> spDataObject;
+    hr = CIDLData_CreateFromIDArray(parent, 1, &child, &spDataObject);
+    ok_hr_ret(hr, S_OK);
+
+    /* Not registered yet */
+    ok_attributes(spDataObject, DV_E_FORMATETC, 0, 0, 0);
+
+    /* Ask for attributes, without specifying any */
+    DWORD dwAttributeMask = 0, dwAttributes = 0;
+    UINT cItems = 0;
+    hr = SHGetAttributesFromDataObject(spDataObject, dwAttributeMask, &dwAttributes, &cItems);
+    ok_hr(hr, S_OK);
+
+    /* Now there are attributes registered */
+    ok_attributes(spDataObject, S_OK, dwDefaultAttributeMask, attributes, 1);
+
+    // Now add an additional mask value (our exe should have a propsheet!)
+    dwAttributeMask = SFGAO_HASPROPSHEET;
+    dwAttributes = 0;
+    cItems = 0;
+    hr = SHGetAttributesFromDataObject(spDataObject, dwAttributeMask, &dwAttributes, &cItems);
+    ok_hr(hr, S_OK);
+
+    // Observe that this is now also cached
+    ok_attributes(spDataObject, S_OK, dwDefaultAttributeMask | SFGAO_HASPROPSHEET, attributes | SFGAO_HASPROPSHEET, 1);
+}
+
+static void test_MultipleFiles()
+{
+    TmpFile TmpFile1, TmpFile2, TmpFile3;
+
+    CComHeapPtr<ITEMIDLIST> pidl_tmpfolder;
+    CComHeapPtr<ITEMIDLIST> pidl1, pidl2, pidl3;
+
+    ITEMIDLIST* items[3] = {};
+    SFGAOF attributes_first = dwDefaultAttributeMask;
+    SFGAOF attributes2 = dwDefaultAttributeMask;
+    SFGAOF attributes3 = dwDefaultAttributeMask;
+    SFGAOF attributes_last = dwDefaultAttributeMask;
+
+    HRESULT hr;
+    {
+        WCHAR TempFolder[MAX_PATH] = {};
+        GetTempPathW(_countof(TempFolder), TempFolder);
+
+        // Create temp files
+        TmpFile1.Create(TempFolder);
+        TmpFile2.Create(TempFolder);
+        TmpFile3.Create(TempFolder);
+
+        // Last file is read-only
+        SetFileAttributesW(TmpFile3.Buffer, FILE_ATTRIBUTE_READONLY);
+
+        hr = SHParseDisplayName(TempFolder, NULL, &pidl_tmpfolder, NULL, NULL);
+        ok_hr_ret(hr, S_OK);
+
+        CComPtr<IShellFolder> spFolder = _BindToObject(pidl_tmpfolder);
+        ok(!!spFolder, "Unable to bind to tmp folder\n");
+        if (!spFolder)
+            return;
+
+        hr = spFolder->ParseDisplayName(NULL, 0, PathFindFileNameW(TmpFile1.Buffer), NULL, &pidl1, NULL);
+        ok_hr_ret(hr, S_OK);
+
+        hr = spFolder->ParseDisplayName(NULL, 0, PathFindFileNameW(TmpFile2.Buffer), NULL, &pidl2, NULL);
+        ok_hr_ret(hr, S_OK);
+
+        hr = spFolder->ParseDisplayName(NULL, 0, PathFindFileNameW(TmpFile3.Buffer), NULL, &pidl3, NULL);
+        ok_hr_ret(hr, S_OK);
+
+        items[0] = pidl1;
+        items[1] = pidl2;
+        items[2] = pidl3;
+
+        // Query file attributes
+        hr = spFolder->GetAttributesOf(1, items, &attributes_first);
+        ok_hr(hr, S_OK);
+
+        hr = spFolder->GetAttributesOf(2, items, &attributes2);
+        ok_hr(hr, S_OK);
+
+        hr = spFolder->GetAttributesOf(3, items, &attributes3);
+        ok_hr(hr, S_OK);
+
+        hr = spFolder->GetAttributesOf(1, items + 2, &attributes_last);
+        ok_hr(hr, S_OK);
+
+        // Ignore any non-default attributes
+        attributes_first &= dwDefaultAttributeMask;
+        attributes2 &= dwDefaultAttributeMask;
+        attributes3 &= dwDefaultAttributeMask;
+        attributes_last &= dwDefaultAttributeMask;
+    }
+
+    // Only 'single' files have the stream attribute set
+    ok(attributes_first & SFGAO_STREAM, "Expected SFGAO_STREAM on attributes_first (0x%lx)\n", attributes_first);
+    ok(!(attributes2 & SFGAO_STREAM), "Expected no SFGAO_STREAM on attributes2 (0x%lx)\n", attributes2);
+    ok(!(attributes3 & SFGAO_STREAM), "Expected no SFGAO_STREAM on attributes3 (0x%lx)\n", attributes3);
+    ok(attributes_last & SFGAO_STREAM, "Expected SFGAO_STREAM on attributes_last (0x%lx)\n", attributes_last);
+
+    // Only attributes common on all are returned, so only the last has the readonly bit set!
+    ok(!(attributes_first & SFGAO_READONLY), "Expected no SFGAO_READONLY on attributes_first (0x%lx)\n", attributes_first);
+    ok(!(attributes2 & SFGAO_READONLY), "Expected no SFGAO_READONLY on attributes2 (0x%lx)\n", attributes2);
+    ok(!(attributes3 & SFGAO_READONLY), "Expected no SFGAO_READONLY on attributes3 (0x%lx)\n", attributes3);
+    ok(attributes_last & SFGAO_READONLY, "Expected SFGAO_READONLY on attributes_last (0x%lx)\n", attributes_last);
+
+    // The actual tests
+    {
+        // Just the first file
+        CComPtr<IDataObject> spDataObject;
+        hr = CIDLData_CreateFromIDArray(pidl_tmpfolder, 1, items, &spDataObject);
+        ok_hr_ret(hr, S_OK);
+
+        DWORD dwAttributeMask = 0, dwAttributes = 123;
+        UINT cItems = 123;
+        hr = SHGetAttributesFromDataObject(spDataObject, dwAttributeMask, &dwAttributes, &cItems);
+        ok_hr(hr, S_OK);
+        ok_attributes(spDataObject, S_OK, dwDefaultAttributeMask, attributes_first, 1);
+    }
+
+    {
+        // First 2 files
+        CComPtr<IDataObject> spDataObject;
+        hr = CIDLData_CreateFromIDArray(pidl_tmpfolder, 2, items, &spDataObject);
+        ok_hr_ret(hr, S_OK);
+
+        DWORD dwAttributeMask = 0, dwAttributes = 123;
+        UINT cItems = 123;
+        hr = SHGetAttributesFromDataObject(spDataObject, dwAttributeMask, &dwAttributes, &cItems);
+        ok_hr(hr, S_OK);
+        ok_attributes(spDataObject, S_OK, dwDefaultAttributeMask, attributes2, 2);
+    }
+
+    {
+        // All 3 files
+        CComPtr<IDataObject> spDataObject;
+        hr = CIDLData_CreateFromIDArray(pidl_tmpfolder, 3, items, &spDataObject);
+        ok_hr_ret(hr, S_OK);
+
+        DWORD dwAttributeMask = 0, dwAttributes = 123;
+        UINT cItems = 123;
+        hr = SHGetAttributesFromDataObject(spDataObject, dwAttributeMask, &dwAttributes, &cItems);
+        ok_hr(hr, S_OK);
+        ok_attributes(spDataObject, S_OK, dwDefaultAttributeMask, attributes3, 3);
+    }
+
+    {
+        // Only the last file
+        CComPtr<IDataObject> spDataObject;
+        hr = CIDLData_CreateFromIDArray(pidl_tmpfolder, 1, items + 2, &spDataObject);
+        ok_hr_ret(hr, S_OK);
+
+        DWORD dwAttributeMask = 0, dwAttributes = 123;
+        UINT cItems = 123;
+        hr = SHGetAttributesFromDataObject(spDataObject, dwAttributeMask, &dwAttributes, &cItems);
+        ok_hr(hr, S_OK);
+        ok_attributes(spDataObject, S_OK, dwDefaultAttributeMask, attributes_last, 1);
+    }
+}
+
+START_TEST(SHGetAttributesFromDataObject)
+{
+    HRESULT hr;
+
+    hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
+    ok_hr(hr, S_OK);
+    if (!SUCCEEDED(hr))
+        return;
+
+    g_DataObjectAttributes = (CLIPFORMAT)RegisterClipboardFormatW(L"DataObjectAttributes");
+    ok(g_DataObjectAttributes != 0, "Unable to register DataObjectAttributes\n");
+
+    test_SpecialCases();
+    test_AttributesRegistration();
+    test_MultipleFiles();
+
+    CoUninitialize();
+}
index 0121908..7c306fa 100644 (file)
@@ -28,6 +28,7 @@ extern void func_ShellExecuteEx(void);
 extern void func_ShellExecuteW(void);
 extern void func_ShellHook(void);
 extern void func_ShellState(void);
+extern void func_SHGetAttributesFromDataObject(void);
 extern void func_SHLimitInputEdit(void);
 extern void func_SHParseDisplayName(void);
 
@@ -58,6 +59,7 @@ const struct test winetest_testlist[] =
     { "ShellExecuteW", func_ShellExecuteW },
     { "ShellHook", func_ShellHook },
     { "ShellState", func_ShellState },
+    { "SHGetAttributesFromDataObject", func_SHGetAttributesFromDataObject },
     { "SHLimitInputEdit", func_SHLimitInputEdit },
     { "SHParseDisplayName", func_SHParseDisplayName },
     { 0, 0 }
index 422714c..f8be3e7 100644 (file)
@@ -2476,6 +2476,18 @@ SHRunControlPanel(
   _In_ LPCWSTR commandLine,
   _In_opt_ HWND parent);
 
+/****************************************************************************
+ * SHGetAttributesFromDataObject
+ */
+
+HRESULT
+WINAPI
+SHGetAttributesFromDataObject(
+    _In_opt_ IDataObject* pdo,
+    DWORD dwAttributeMask,
+    _Out_opt_ DWORD* pdwAttributes,
+    _Out_opt_ UINT* pcItems);
+
 /****************************************************************************
  * SHOpenWithDialog
  */
index ae8f8dd..2dcbd80 100644 (file)
@@ -559,6 +559,71 @@ static inline PCUIDLIST_RELATIVE HIDA_GetPIDLItem(CIDA const* pida, SIZE_T i)
 
 #ifdef __cplusplus
 
+DECLSPEC_SELECTANY CLIPFORMAT g_cfHIDA = NULL;
+
+// Allow to use the HIDA from an IDataObject without copying it
+struct CDataObjectHIDA
+{
+private:
+    STGMEDIUM m_medium;
+    CIDA* m_cida;
+    HRESULT m_hr;
+
+public:
+    explicit CDataObjectHIDA(IDataObject* pDataObject)
+        : m_cida(nullptr)
+    {
+        m_medium.tymed = TYMED_NULL;
+
+        if (g_cfHIDA == NULL)
+        {
+            g_cfHIDA = (CLIPFORMAT)RegisterClipboardFormatW(CFSTR_SHELLIDLISTW);
+        }
+        FORMATETC fmt = { g_cfHIDA, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+
+        m_hr = pDataObject->GetData(&fmt, &m_medium);
+        if (FAILED(m_hr))
+        {
+            m_medium.tymed = TYMED_NULL;
+            return;
+        }
+
+        m_cida = (CIDA*)::GlobalLock(m_medium.hGlobal);
+        if (m_cida == nullptr)
+        {
+            m_hr = E_UNEXPECTED;
+        }
+    }
+
+    ~CDataObjectHIDA()
+    {
+        if (m_cida)
+            ::GlobalUnlock(m_cida);
+
+        ReleaseStgMedium(&m_medium);
+    }
+
+    HRESULT hr() const
+    {
+        return m_hr;
+    }
+
+    operator bool() const
+    {
+        return m_cida != nullptr;
+    }
+
+    operator const CIDA* () const
+    {
+        return m_cida;
+    }
+
+    const CIDA* operator->() const
+    {
+        return m_cida;
+    }
+};
+
 inline
 HRESULT DataObject_GetData(IDataObject* pDataObject, CLIPFORMAT clipformat, PVOID pBuffer, SIZE_T dwBufferSize)
 {