Enumerating FORMATETC


9 minute read  • 

win32

This tutorial will concentrate on implementing a COM object which exposes the IEnumFORMATETC interface. There are two code downloads this time. The first includes a complete implementation of a generic IEnumFORMATETC which you can use in your applications.

The second code download is the full source code to an application called “IDataObject Viewer”. This is an replacement for the PlatformSDK program of the same name. It is basically a demonstration of how to use the IEnumFORMATETC interface rather than write it. More important though, it is a very useful tool for debugging OLE drag and drop code because you can drag any form of IDataObject onto it, and it will display the available formats of data contained within it. Give it a go!

The IEnumFORMATETC interface is quite often overlooked when beginning drag and drop. In some cases it is not necessary, but to be 100% sure that your IDataObject will work under all conditions it is wise to provide a full implementation of this interface.

IEnumFORMATETC MethodsDescription
NextReturn the next FORMATETC structure in the enumeration.
SkipSkip the specified number of FORMATETC structures (i.e. don’t return them).
ResetReturn the enumeration to the beginning.
CloneReturn an identical IEnumFORMATETC interface to the current one, with the exact same underlying state.

The diagram below should help to illustrate the concept of the IEnumFORMATETC interface.

The enumeration contains three items, with the “enumeration index” initially starting at the first item (index zero).

  1. The Next method is called to return the first FORMATETC structure at index zero , and as a side effect advances the enumerator to index 1.
  2. The Skip method is called (with an argument of 2, skipping two positions), advancing to the end of the enumeration ( index 3 ).
  3. The Reset method is called to return the index back to the start ( index zero ).

The IEnumFORMATETC is actually very simple as there are only four methods to implement:

class CEnumFormatEtc : public IEnumFORMATETC
{
public:

    //
    // IUnknown members
    //
    HRESULT __stdcall QueryInterface (REFIID iid, void ** ppvObject);
    ULONG __stdcall AddRef (void);
    ULONG __stdcall Release (void);

    //
    // IEnumFormatEtc members
    //
    HRESULT __stdcall Next (ULONG celt, FORMATETC * rgelt, ULONG * pceltFetched);
    HRESULT __stdcall Skip (ULONG celt); 
    HRESULT __stdcall Reset (void);
    HRESULT __stdcall Clone (IEnumFORMATETC ** ppEnumFormatEtc);

    //
    // Construction / Destruction
    //
    CEnumFormatEtc(FORMATETC *pFormatEtc, int nNumFormats);
    ~CEnumFormatEtc();

private:
    LONG m_lRefCount; // Reference count for this COM interface
    ULONG m_nIndex; // current enumerator index
    ULONG m_nNumFormats; // number of FORMATETC members
    FORMATETC * m_pFormatEtc; // array of FORMATETC objects
};

Constructing an IEnumFORMATETC object

The most complex aspect of the IEnumFORMATETC is creating the object, and implementing the COM methods is really very simple after this. Well, creating one is very easy, because all we need to do is use the C++ operator new to do this:

IEnumFORMATETC *pEnumFormatEtc = new CEnumFormatEtc( fmtetc, numfmts );
CEnumFormatEtc::CFormatEtc(FORMATETC *pFormatEtc, int nNumFormats)
{
    m_lRefCount = 1;

    m_nIndex = 0;
    m_nNumFormats = nNumFormats;
    m_pFormatEtc = new FORMATETC[nNumFormats];

    // make a new copy of each FORMATETC structure
    for(int i = 0; i < nNumFormats; i++)
    {
        DeepCopyFormatEtc(&m_pFormatEtc[i], &pFormatEtc[i]);
    }
}

Let’s break down what this C++ constructor does. It takes two arguments - a pointer to an array of FORMATETC structures, and an integer specifying how many items there are in the array.

The first line initializes the object’s reference count - this is standard across all COM objects and we should be pretty familiar with this, so I won’t cover this any further.

The next set of tasks involves initializing the enumeration’s state. The member variable m_nIndex represents the current position within the enumeration, so it is natural for this to start at zero. Likewise, the m_nNumFormats variable is used to represent the end of the enumeration. With just these two variables we can keep track of the enumeration’s current position and ending position.

The most important step is to allocate a new copy of the FORMATETC array that was passed in as an argument. An array is allocated ( m_pFormatEtc ) which will hold all the structures that will be enumerated. Each enumeration needs to have it’s own private “cache” of FORMATETC structures. The key detail is the way that the FORMATETC structures are copied - here, a new function has been introduced called DeepCopyFormatEtc.

void DeepCopyFormatEtc(FORMATETC *dest, FORMATETC *source)
{
    // copy the source FORMATETC into dest
    *dest = *source;
    if(source->ptd)
    {
        // allocate memory for the DVTARGETDEVICE if necessary
        dest->ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE));

        // copy the contents of the source DVTARGETDEVICE into dest->ptd
        *(dest->ptd) = *(source->ptd);
    }
}

The first line of this function is deceptively simple:

*dest = *source;

This is just shorthand “C” notation for a standard memcpy. In fact, this is almost all that is required because it correctly performs a binary copy of the contents of one FORMATETC structure to another. The problem arises when the source FORMATETC::ptd member has been initialized to point to a DVTARGETDEVICE structure.

Just performing a memcpy of the FORMATETC’s is not enough, because both FORMATETC structure point to the original DVTARGETDEVICE. It is therefore necessary to make our own private copy of this structure.

The documentation for IEnumFORMATETC::Next states that the caller must use CoTaskMemFree API to free the DVTARGETDEVICE structure. This logically implies that the structure must have first been allocated using CoTaskMemAlloc , so this is what the DeepCopy function does - allocates a new DVTARGETDEVICE structure using CoTaskMemAlloc and sets dest->ptd to point to it instead of the original one. Then the source->DVTARGETDEVICE structure is copied across to the new one.

Cleaning up an IEnumFORMATETC object

The C++ destructor for the CEnumFormatEtc class must cleanup any memory allocation that was performed in the constructor.

CEnumFormatEtc::~CEnumFormatEtc()
{
    // first free any DVTARGETDEVICE structures
    for(ULONG i = 0; i < m_nNumFormats; i++)
    {
        if(m_pFormatEtc[i].ptd)
            CoTaskMemFree(m_pFormatEtc[i].ptd);
    }

    // now free the main array
    delete[] m_pFormatEtc;
}

This is basically a simple task of calling CoTaskMemFree to deallocate any of the DVTARGETDEVICE structures that were allocated in the constructor. Once these have been freed, the main m_pFormatEtc array is deallocated.

Replacing SHCreateStdEnumFmtEtc

You may be wondering why we are bothering with this tutorial at all, because the SHCreateStdEnumFmtEtc API call can be used to create a full instantiation of the IEnumFORMATETC interface:

HRESULT SHCreateStdEnumFmtEtc(UINT cfmt, const FORMATETC afmt[], IEnumFORMATETC **ppenumFormatEtc);

Unfortunately this API call only exists on Windows 2000 and above, so unless you are prepared to drop support for any older version of Windows, we still have to implement IEnumFORMATETC. What we will do though, is write a drop-in replacement version of SHCreateStdEnumFmtEtc which we can easily switch from once we decide to support only Windows 2000. Our version will look like this:

HRESULT CreateEnumFormatEtc(UINT cfmt, FORMATETC *afmt, IEnumFORMATETC **ppEnumFormatEtc)
{
    if(cfmt == 0 || afmt == 0 || ppEnumFormatEtc == 0)
        return E_INVALIDARG;

    *ppEnumFormatEtc = new CEnumFormatEtc(afmt, cfmt);

    return (*ppEnumFormatEtc) ? S_OK : E_OUTOFMEMORY;
}

The function is very simple because all the hard work has been done in the CEnumFormatEtc constructor. All we need to do is create a new instance of the class (using the new operator) and return it in the pointer specified as the last parameter. The rest of the code is simply error checking.

Using this API is really simple:

FORMATETC fmtetc = { CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
IEnumFORMATETC *pEnumFormatEtc; 

CreateEnumFormatEtc(1, &fmtetc, &pEnumFormatEtc);

This may seem to be alot of work just to enumerate some simple FORMATETC structures, but it is worth it because our COM enumerator will now be truly stand-alone, and the rest of the interface is now very simple to implement.

IEnumFORMATETC::Reset

The purpose of this member is really simple - start the enumeration again from the beginning.

HRESULT CEnumFormatEtc::Reset(void)
{
    m_nIndex = 0;
    return S_OK;
}

The implementation above should be self-explanatory.

IEnumFORMATETC::Skip

Again the implementation is so straight-forward practically no explanation is required.

HRESULT CEnumFormatEtc::Skip(ULONG celt)
{
    m_nIndex += celt;
    return (m_nIndex <= m_nNumFormats) ? S_OK : S_FALSE;
}

The function merely advances the enumeration by the specified number of units. Note that although no attempt is made to keep the index within the range of the enumeration, the error value returned does indicate whether or not the enumeration has been advanced too far.

IEnumFORMATETC::Clone

The clone function may seem a little mysterious at first. Although I have very rarely seen this function called it is quite simple to implement so it doesn’t hurt to do it anyway.

HRESULT CEnumFormatEtc::Clone(IEnumFORMATETC **ppEnumFormatEtc)
{
    HRESULT hResult;

    // make a duplicate enumerator
    hResult = CreateEnumFormatEtc(m_nNumFormats, m_pFormatEtc, ppEnumFormatEtc);

    if(hResult == S_OK)
    {
        // manually set the index state
        ((CEnumFormatEtc *)*ppEnumFormatEtc)->m_nIndex = m_nIndex;
    }

    return hResult;
}

The code above simply creates a new instance of the IEnumFORMATETC interface, using the CreateEnumFormatEtc function we wrote earlier. The current enumeration’s internal state is used, so the effect is a duplicate interface with the same internal state.

The complicated looking cast inside the “if-clause” is used to preserve the index position of the enumeration. The cast is necessary because the IEnumFORMATETC interface has no accessible internal variables. However, we know that the ppEnumFormatEtc is really a CEnumFormatEtc, so this is a safe cast to perform. The cast operation looks more complicated than it is because we also have to dereference the ppEnumFormatEtc parameter, in order to access the pointer-to-IEnumFORMATETC that was stored there.

IEnumFORMATETC::Next

The Next member function is a little more involved than the others.

HRESULT CEnumFormatEtc::Next(ULONG celt, FORMATETC *pFormatEtc, ULONG *pceltFetched)
{
    ULONG copied = 0;

    // copy the FORMATETC structures into the caller's buffer
    while(m_nIndex < m_nNumFormats && copied < celt) 
    {
        DeepCopyFormatEtc(&pFormatEtc[copied], &m_pFormatEtc[m_nIndex]);
        copied++;
        m_nIndex++;
    }

    // store result
    if(pceltFetched != 0) 
        *pceltFetched = copied;

    // did we copy all that was requested?
    return (copied == celt) ? S_OK : S_FALSE;
}

The function looks quite complicated but can be broken down into three major operations. The major portion of the code is the while-loop which is responsible for copying FORMATETC structures (using the deep-copy routine). The loop is structured in such a way that only “in-range” elements are copied into the supplied buffer.

The second part of the code returns the actual number of items copied, and returns an error value indicating whether or not all the requested items were copied.

The final section merely returns an error value indicating success or failure to copy the requested number of items.

Coming up in Part 5 - IDropSource

Well that’s it for the IEnumFORMATETC interface. What we have now is a complete, stand-alone implementation, and a very handy CreateEnumFormatEtc API to easily create enumerator interfaces.

The next part of this tutorial series will be the IDropSource interface. It’s been a long time coming but hopefully it will be worth the wait!

Don’t forget to check out the IDataObject Viewer utility also!