Enumerating FORMATETC
9 minute read •
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 Methods | Description |
---|---|
Next | Return the next FORMATETC structure in the enumeration. |
Skip | Skip the specified number of FORMATETC structures (i.e. don’t return them). |
Reset | Return the enumeration to the beginning. |
Clone | Return 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).
- The
Next
method is called to return the firstFORMATETC
structure at index zero , and as a side effect advances the enumerator to index 1. - The
Skip
method is called (with an argument of 2, skipping two positions), advancing to the end of the enumeration ( index 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!