By slightly modifying the code from the Raymond Chen's article, I got a class for copying a vector to the clipboard as a file:
typedef std::wstring String;
class CFileDataObject : public IDataObject
{
public:
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
IUnknown *punk = NULL;
if (riid == IID_IUnknown)
punk = static_cast<IUnknown*>(this);
else if (riid == IID_IDataObject)
{
punk = static_cast<IDataObject*>(this);
}
*ppv = punk;
if (punk)
{
punk->AddRef();
return S_OK;
}
else return E_NOINTERFACE;
};
STDMETHODIMP_(ULONG) AddRef()
{
return m_cRef;
};
STDMETHODIMP_(ULONG) Release()
{
ULONG cRef = --m_cRef;
if (cRef == 0)
{
delete this;
}
return cRef;
}
// IDataObject
STDMETHODIMP GetData(FORMATETC *pfe, STGMEDIUM *pmed)
{
ZeroMemory(pmed, sizeof(*pmed));
switch (GetDataIndex(pfe))
{
case DATA_FILEGROUPDESCRIPTOR:
{
FILEGROUPDESCRIPTOR fgd;
ZeroMemory(&fgd, sizeof(fgd));
fgd.cItems = 1;
fgd.fgd[0].dwFlags = FD_FILESIZE | FD_WRITESTIME;
fgd.fgd[0].nFileSizeLow = DataSize & 0xFFFFFFFFULL;
fgd.fgd[0].nFileSizeHigh = (DataSize & 0xFFFFFFFF00000000ULL) >> 32;
fgd.fgd[0].ftLastWriteTime.dwLowDateTime = 0x256d4000;
fgd.fgd[0].ftLastWriteTime.dwHighDateTime = 0x01bf53eb;
StringCchCopy(fgd.fgd[0].cFileName,ARRAYSIZE(fgd.fgd[0].cFileName),
m_FileName.c_str());
pmed->tymed = TYMED_HGLOBAL;
return CreateHGlobalFromBlob(&fgd, sizeof(fgd),GMEM_MOVEABLE, &pmed->hGlobal);
}
case DATA_FILECONTENTS:
pmed->tymed = TYMED_HGLOBAL;
pmed->hGlobal = hData;
return S_OK;
}
return DV_E_FORMATETC;
};
STDMETHODIMP GetDataHere(FORMATETC *pfe, STGMEDIUM *pmed)
{
return E_NOTIMPL;
};
STDMETHODIMP QueryGetData(FORMATETC *pfe)
{
return GetDataIndex(pfe) == DATA_INVALID ? S_FALSE : S_OK;
};
STDMETHODIMP GetCanonicalFormatEtc(FORMATETC *pfeIn,FORMATETC *pfeOut)
{
*pfeOut = *pfeIn;
pfeOut->ptd = NULL;
return DATA_S_SAMEFORMATETC;
};
STDMETHODIMP SetData(FORMATETC *pfe, STGMEDIUM *pmed,BOOL fRelease)
{
return E_NOTIMPL;
};
STDMETHODIMP EnumFormatEtc(DWORD dwDirection,LPENUMFORMATETC *ppefe)
{
if (dwDirection == DATADIR_GET)
return SHCreateStdEnumFmtEtc(ARRAYSIZE(m_rgfe), m_rgfe, ppefe);
*ppefe = NULL;
return E_NOTIMPL;
}
STDMETHODIMP DAdvise(FORMATETC *pfe, DWORD grfAdv,IAdviseSink *pAdvSink, DWORD *pdwConnection)
{
return OLE_E_ADVISENOTSUPPORTED;
};
STDMETHODIMP DUnadvise(DWORD dwConnection)
{
return OLE_E_ADVISENOTSUPPORTED;
};
STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *ppefe)
{
return OLE_E_ADVISENOTSUPPORTED;
};
CFileDataObject(const String& FileName, const std::vector<uint8_t>& Data)
: m_cRef(1),m_FileName(FileName)
{
SetFORMATETC(&m_rgfe[DATA_FILEGROUPDESCRIPTOR],
RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR));
SetFORMATETC(&m_rgfe[DATA_FILECONTENTS],
RegisterClipboardFormat(CFSTR_FILECONTENTS),TYMED_HGLOBAL, 0);
HRESULT hres = CreateHGlobalFromBlob(Data.data(), Data.size(), GMEM_MOVEABLE, &hData);
if (!SUCCEEDED(hres)) { /* Some error handling goes here */ };
DataSize = Data.size();
}
private:
enum {
DATA_FILEGROUPDESCRIPTOR,
DATA_FILECONTENTS,
DATA_NUM,
DATA_INVALID = -1,
};
int GetDataIndex(const FORMATETC *pfe)
{
for (int i = 0; i < ARRAYSIZE(m_rgfe); i )
{
if (pfe->cfFormat == m_rgfe[i].cfFormat && (pfe->tymed & m_rgfe[i].tymed) &&
pfe->dwAspect == m_rgfe[i].dwAspect && pfe->lindex == m_rgfe[i].lindex)
return i;
}
return DATA_INVALID;
}
private:
ULONG m_cRef;
FORMATETC m_rgfe[DATA_NUM];
String m_FileName;
HGLOBAL hData = 0;
size_t DataSize = 0;
};
Usage:
std::vector<uint8_t> FileData;
// Filling the vector with some data
IDataObject* fdo = new CFileDataObject(FileName, FileData);
if (fdo)
{
HRESULT hres = OleSetClipboard(fdo);
fdo->Release();
}
The class serves its purpose, but there is one detail: the file can only be pasted only once. All further attempts to paste the file fail. Is this normal or is there something wrong with the class?
CodePudding user response:
Your GetData() is returning the original hData object for DATA_FILECONTENTS instead of a copy. Since the pmed->pUnkForRelease field is being returned as NULL, the caller of GetData() will free the returned HGLOBAL when done using it.
https://learn.microsoft.com/en-us/windows/win32/api/objidl/ns-objidl-ustgmedium-r1
pUnkForReleasePointer to an interface instance that allows the sending process to control the way the storage is released when the receiving process calls the
ReleaseStgMediumfunction. IfpUnkForReleaseis NULL,ReleaseStgMediumuses default procedures to release the storage; otherwise,ReleaseStgMediumuses the specifiedIUnknowninterface.
https://learn.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-releasestgmedium
The provider indicates that the receiver of the medium is responsible for freeing the medium by specifying NULL for the
punkForReleasestructure member. Then the receiver callsReleaseStgMedium, which makes a call as described in the following table depending on the type of storage medium being freed.
Medium ReleaseStgMedium Action TYMED_HGLOBAL Calls the GlobalFreefunction on the handle.
So, to keep that from happening, set pmed->pUnkForRelease to an AddRef'ed IUnknown that is responsible for freeing hData when noone is using it anymore. In your case, you can use the this pointer for your CFileDataObject object for that IUnknown interface.
When the original provider of the medium is responsible for freeing the medium, the provider calls
ReleaseStgMedium, specifying the medium and the appropriateIUnknownpointer as thepunkForReleasestructure member. Depending on the type of storage medium being freed, one of the following actions is taken, followed by a call to theIUnknown::Releasemethod on the specifiedIUnknownpointer.
Medium ReleaseStgMedium Action TYMED_HGLOBAL None.
Otherwise, another option is to implement DATA_FILECONTENTS as an IStream instead of an HGLOBAL, where the IStream accesses the original vector data. This option is even described in the Shell Clipboard Formats documentation:
CFSTR_FILECONTENTS
This format identifier is used with the
CFSTR_FILEDESCRIPTORformat to transfer data as if it were a file, regardless of how it is actually stored. The data consists of anSTGMEDIUMstructure that represents the contents of one file. The file is normally represented as a stream object, which avoids having to place the contents of the file in memory. In that case, the tymed member of theSTGMEDIUMstructure is set toTYMED_ISTREAM, and the file is represented by anIStreaminterface. The file can also be a storage or global memory object (TYMED_ISTORAGEorTYMED_HGLOBAL). The associatedCFSTR_FILEDESCRIPTORformat contains aFILEDESCRIPTORstructure for each file that specifies the file's name and attributes.
