Home > Software engineering >  Copying a virtual file to the clipboard
Copying a virtual file to the clipboard

Time:09-28

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

pUnkForRelease

Pointer to an interface instance that allows the sending process to control the way the storage is released when the receiving process calls the ReleaseStgMedium function. If pUnkForRelease is NULL, ReleaseStgMedium uses default procedures to release the storage; otherwise, ReleaseStgMedium uses the specified IUnknown interface.

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 punkForRelease structure member. Then the receiver calls ReleaseStgMedium, 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 GlobalFree function 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 appropriate IUnknown pointer as the punkForRelease structure member. Depending on the type of storage medium being freed, one of the following actions is taken, followed by a call to the IUnknown::Release method on the specified IUnknown  pointer.

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_FILEDESCRIPTOR format to transfer data as if it were a file, regardless of how it is actually stored. The data consists of an STGMEDIUM structure 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 the STGMEDIUM structure is set to TYMED_ISTREAM, and the file is represented by an IStream interface. The file can also be a storage or global memory object (TYMED_ISTORAGE or TYMED_HGLOBAL). The associated CFSTR_FILEDESCRIPTOR format contains a FILEDESCRIPTOR structure for each file that specifies the file's name and attributes.

  • Related