https://git.reactos.org/?p=reactos.git;a=commitdiff;h=e5ea6041c96a4ab5f65dc…
commit e5ea6041c96a4ab5f65dc19fda2f8c369ea316f7
Author: Mark Jansen <mark.jansen(a)reactos.org>
AuthorDate: Fri Jul 23 20:34:02 2021 +0200
Commit: Mark Jansen <mark.jansen(a)reactos.org>
CommitDate: Mon Nov 15 20:02:13 2021 +0100
[SHELL32][SHELL32_APITEST] Add SHGetAttributesFromDataObject
---
dll/win32/shell32/CMakeLists.txt | 1 +
dll/win32/shell32/shldataobject.cpp | 103 +++++++
dll/win32/shell32/stubs.cpp | 14 -
modules/rostests/apitests/shell32/CMakeLists.txt | 1 +
.../shell32/SHGetAttributesFromDataObject.cpp | 319 +++++++++++++++++++++
modules/rostests/apitests/shell32/testlist.c | 2 +
sdk/include/psdk/shlobj.h | 12 +
sdk/include/reactos/shellutils.h | 65 +++++
8 files changed, 503 insertions(+), 14 deletions(-)
diff --git a/dll/win32/shell32/CMakeLists.txt b/dll/win32/shell32/CMakeLists.txt
index 4ebe876caea..2b1f658bacd 100644
--- a/dll/win32/shell32/CMakeLists.txt
+++ b/dll/win32/shell32/CMakeLists.txt
@@ -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
index 00000000000..4b6d3186271
--- /dev/null
+++ b/dll/win32/shell32/shldataobject.cpp
@@ -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(a)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;
+}
diff --git a/dll/win32/shell32/stubs.cpp b/dll/win32/shell32/stubs.cpp
index a5061ad1a9d..2d49322a184 100644
--- a/dll/win32/shell32/stubs.cpp
+++ b/dll/win32/shell32/stubs.cpp
@@ -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
*/
diff --git a/modules/rostests/apitests/shell32/CMakeLists.txt
b/modules/rostests/apitests/shell32/CMakeLists.txt
index 990e1463d48..7a175032619 100644
--- a/modules/rostests/apitests/shell32/CMakeLists.txt
+++ b/modules/rostests/apitests/shell32/CMakeLists.txt
@@ -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
index 00000000000..937045cdc25
--- /dev/null
+++ b/modules/rostests/apitests/shell32/SHGetAttributesFromDataObject.cpp
@@ -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(a)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();
+}
diff --git a/modules/rostests/apitests/shell32/testlist.c
b/modules/rostests/apitests/shell32/testlist.c
index 01219082ba3..7c306faa1a7 100644
--- a/modules/rostests/apitests/shell32/testlist.c
+++ b/modules/rostests/apitests/shell32/testlist.c
@@ -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 }
diff --git a/sdk/include/psdk/shlobj.h b/sdk/include/psdk/shlobj.h
index 422714c7b9d..f8be3e7d299 100644
--- a/sdk/include/psdk/shlobj.h
+++ b/sdk/include/psdk/shlobj.h
@@ -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
*/
diff --git a/sdk/include/reactos/shellutils.h b/sdk/include/reactos/shellutils.h
index ae8f8dd2519..2dcbd8037b0 100644
--- a/sdk/include/reactos/shellutils.h
+++ b/sdk/include/reactos/shellutils.h
@@ -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)
{