OLE Drap/Drop(4)
本章注重于实现一个暴露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:
//
// 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
};
构造一个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];
// make a new copy of each FORMATETC structure
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)
{
// 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);
}
}
函数的第一行非常简单:
*dest = *source;
这个实际上是一个标准的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()
{
// 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;
}
这是一个简单的任务,调用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;
// 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;
}
上面代码很简单地创建了一个IEnumFORMATETC接口的实例,使用我们前面写的CreateEnumFormatEtc函数;使用当前的枚举内部状态,因此结果就是复制接口的当前内部状态。
在if从句中的转型看起来有点复杂,其用来保留枚举的索引位置,转型是必须的,因为IEnumFORMATETC接口并可以访问内部变量,然而,我们知道ppEnumFormatEtc实际上就是一个CEnumFormatEtc,所以这个转换能安全的执行。转换操作看起来复杂的原因是我们必须引用ppEnumFormatEtc参数来访问指向IEnumFORMATETC的指针。
IEnumFORMATETC::Next
Next成员函数比其他的稍微棘手一点:
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;
}
这个函数看起来有点复杂,但可以被分解成三个重要的操作;主要的部分是while循环部分,它负责复制FORMATETC结构(使用深度复制程序),循环仅仅复制范围内的元素到提供的缓冲区中。
第二部分是返回实际复制的相数,且返回一个错误值来指示是否所有需要复制的元素都被复制了。
最后一部分就是错误代码来指示复制指定数量的项数的操作成功和失败