本章注重于實現一個暴露IEnumFORMATETC接口的COM對象,這里有兩部分代碼可以下載。第一包含一個完整的通用的IEnumFORMATETC實現,你可以將它用到你的程序中。另一部分代碼是一個叫做IDataObject Viewer的所有代碼。這是PlatformSDK同名程序的替代品,它是一個怎么樣使用IEnumFORMATETC接口的基本介紹,而不是寫這個接口。更重要的是,它在調式OLE拖放代碼是非常有用,你可以拖動任何格式的IDataObject到它上面,它會顯示顯示數據包含的可用格式。
IEnumFORMATETC接口在開始拖放時經常不會注意到,在許多情況下它是不必要的,但為了你的IDataObject可以在所有條件下保證100%工作,提供該接口的完整實現是必要的。
IEnumFORMATETC 方法 |
描述 |
Next |
返回枚舉中的下一個FORMATETC結構體 |
Skip |
跳過指定數量的FORMATETC structures (例如,不返回他們). |
Reset |
返回枚舉的開始狀態 |
Clone |
返回與當前結構相同的IEnumFORMATETC 接口, 并且有相同的低層狀態 |
下圖應該可以能夠幫助你描述IEnumFORMATETC接口:

枚舉包含3項,枚舉索引初始化在第一項(索引是0)。
1. Next方法在索引0時返回第一個FORMATETC結構,并且枚舉指針指向索引1。
2. Skip方法以參數2來調用,跳過兩個位置,到達枚舉的尾部(索引3)。
3. Reset方法返回到索引的開始(索引0)。
IEnumFORMATETC實際上非常簡單,僅僅需要實現四個方法:
class CEnumFormatEtc : public IEnumFORMATETC
{
public:
HRESULT __stdcall QueryInterface (REFIID iid, void ** ppvObject);
ULONG __stdcall AddRef (void);
ULONG __stdcall Release (void);
HRESULT __stdcall Next (ULONG celt, FORMATETC * rgelt, ULONG * pceltFetched);
HRESULT __stdcall Skip (ULONG celt);
HRESULT __stdcall Reset (void);
HRESULT __stdcall Clone (IEnumFORMATETC ** ppEnumFormatEtc);
CEnumFormatEtc(FORMATETC *pFormatEtc, int nNumFormats);
~CEnumFormatEtc();
private:
LONG m_lRefCount;
ULONG m_nIndex;
ULONG m_nNumFormats;
FORMATETC * m_pFormatEtc;
};
構造一個IEnumFORMATETC對象
IEnumFORMATETC最復雜的事情是創建對象,在這時候實現COM方法真的非常簡單,好了,創建一個對象是非茶館內容易的,因為我所需要的就是使用C++操作符new來做這件事情:
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];
for(int i = 0; i < nNumFormats; i++)
{
DeepCopyFormatEtc (&m_pFormatEtc[i], &pFormatEtc[i]);
}
}
我們來看以下這個C++構造函數做了什么,它有兩個參數:一個指向FORMATETC結構的數組,另外一個是表示數組中有多少元素的整數。
第一行初始化對象引用記數,這是所有COM對象的標準,我們應該非常熟悉它,因此我這里不在做更多的介紹。
下一步就是初始化枚舉狀態,成員變量m_nIndex表示枚舉中的當前位置,因此它以0開始是很自然的,同樣,m_nNumFormats變量用來表示枚舉的結尾,有了這兩個變量,我們可以跟蹤枚舉當前的位置和結束位置。
最重要的一步是分配參數中的FORMATETC結構體的一個新數組副本。一個數據被分配(m_pFormatEtc)其保存所有要被枚舉的結構體,每個枚舉需要有自己的私有FORMATETC結構的緩存,關鍵細節是復制FORMATETC結構的方法,這里,我們引入一個叫DeepCopyFormatEtc新的函數。
void DeepCopyFormatEtc(FORMATETC *dest, FORMATETC *source)
{
*dest = *source;
if(source->ptd)
{
dest->ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE));
*(dest->ptd) = *(source->ptd);
}
}
函數的第一行非常簡單:
這個實際上是一個標準的C函數memcpy。實際上,這幾乎在所有的情況都是需要的,因為他能正確的執行一個二進制的結構體到結構體的復制,問題是當源FORMATETC::ptd成員的已經被初始化為指向一個DVTAGETDEVIDE結構,就不能正確復制了。
僅僅在FORMATETC上執行memcpy是不夠的,因為兩個FORMATETC結構體都指向原來的DVTARGETDEVICE;因此我們私有的結構體復制函數是需要的。
IEnumFORMATETC::Next文檔聲明調用這使用CoTaskMemFree這個API來釋放DVTARGETDEVICE結構體,邏輯上意味著這個結構必須首先已經使用CoTaskMemAlloc來分配了棵,因此這就是深度復制所做的,使用CoTaskMemAlloc來分配一個新的DVTARGETDEVICE結構體,并且設置dest->ptd指向原來的,那么source->DVTARGETDEVICE結構體就被復制到新的指針上了。
清理一個IEnumFORMATETC對象
CEnumFormatEtc類的C++析構函數必須清理所有在構造函數分配的內存:
CEnumFormatEtc::~CEnumFormatEtc()
{
for(ULONG i = 0; i < m_nNumFormats; i++)
{
if(m_pFormatEtc[i].ptd)
CoTaskMemFree(m_pFormatEtc[i].ptd);
}
delete[] m_pFormatEtc;
}
這是一個簡單的任務,調用CoTaskMemFree來釋放所有在構造函數中分配的DVTAGETDEVICE結構體,一旦這些已經釋放完了,m_pFormatEtc數組也應該被釋放。
取代SHCreateStdEnumFmtEtc
你可能會問,在該指南中,我們為什么會一直這么煩心來,因為SHCreateStdEnumFmtEtc API調用可以用來創建IEnumFORMATETC接口,但不幸的是,它只能在WINDOS2000以上的版本使用,看原型:
HRESULT SHCreateStdEnumFmtEtc(UINT cfmt, const FORMATETC afmt[], IEnumFORMATETC **ppenumFormatEtc);
因此如果你不準備向下兼容老的版本Windows的拖放操作,否則我們總是需要實現IEnumFORMATETC。我們需要做的就是寫一個SHCreateStdEnumFmtEtc的drop-in替代版本,我們可以在僅僅支持windows2000的時候很容易切換,我們的版本是這樣的:
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;
}
函數非常簡單,因為所有的工作都在CEnumFormatEtc構造函數中調用,我們需要做的就是創建一個類的實例,然后以最后一個參數返回;其余的代碼是錯誤檢查。
使用API是也很簡單:
FORMATETC fmtetc = {CF_TEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
IEnumFORMATETC *pEnumFormatEtc;
CreateEnumFormatEtc (1, &fmtetc, &pEnumFormatEtc);
這似乎是枚舉一些簡單FORMATETC結構的許多工作,但這是值得的,因為我們的COM枚舉器現在真正的獨立了,剩下的接口就非常簡單了。
IEnumFORMATETC::Reset
這個成員非常簡單,設置枚舉到開始的位置:
HRESULT CEnumFormatEtc::Reset (void)
{
m_nIndex = 0;
return S_OK;
}
上面的實現可以自解釋。
IEnumFORMATETC::Skip
該實現直接向前移動,簡直不需要解釋:
HRESULT IEnumFORMATETC::Skip (ULONG celt)
{
m_nIndex += celt;
return (m_nIndex <= m_nNumFormats) ? S_OK : S_FALSE;
}
該函數僅僅向前移動枚舉指定單元。注意,盡管這里沒有保證索引在枚舉范圍內,但一個返回值用來指示是否前進的太多。
IEnumFORMATETC::Clone
Clone函數起先看起來優點神秘;盡管我很少看到這個函數調用,它實際上很容易實現:
HRESULT IEnumFORMATETC::Clone(IEnumFORMATETC **ppEnumFormatEtc)
{
HRESULT hResult;
hResult = CreateEnumFormatEtc(m_nNumFormats, m_pFormatEtc, ppEnumFormatEtc);
if(hResult == S_OK)
{
((CEnumFormatEtc *)*ppEnumFormatEtc)->m_nIndex = m_nIndex;
}
return hResult;
}
上面代碼很簡單地創建了一個IEnumFORMATETC接口的實例,使用我們前面寫的CreateEnumFormatEtc函數;使用當前的枚舉內部狀態,因此結果就是復制接口的當前內部狀態。
在if從句中的轉型看起來有點復雜,其用來保留枚舉的索引位置,轉型是必須的,因為IEnumFORMATETC接口并可以訪問內部變量,然而,我們知道ppEnumFormatEtc實際上就是一個CEnumFormatEtc,所以這個轉換能安全的執行。轉換操作看起來復雜的原因是我們必須引用ppEnumFormatEtc參數來訪問指向IEnumFORMATETC的指針。
IEnumFORMATETC::Next
Next成員函數比其他的稍微棘手一點:
HRESULT CEnumFormatEtc::Next(ULONG celt, FORMATETC *pFormatEtc, ULONG *pceltFetched)
{
ULONG copied = 0;
while (m_nIndex < m_nNumFormats && copied < celt)
{
DeepCopyFormatEtc (&pFormatEtc [copied], &m_pFormatEtc [m_nIndex]);
copied++;
m_nIndex++;
}
if(pceltFetched != 0)
*pceltFetched = copied;
return (copied == celt) ? S_OK : S_FALSE;
}
這個函數看起來有點復雜,但可以被分解成三個重要的操作;主要的部分是while循環部分,它負責復制FORMATETC結構(使用深度復制程序),循環僅僅復制范圍內的元素到提供的緩沖區中。
第二部分是返回實際復制的相數,且返回一個錯誤值來指示是否所有需要復制的元素都被復制了。
最后一部分就是錯誤代碼來指示復制指定數量的項數的操作成功和失敗。