OLE Drag and Drop (2) OLE 数据传输
欢迎来到OLE拖放指南第二部分;本部分的目的在于解释在OLE环境中,程序之间怎么样表示和传输数据。
OLE数据传输的核心是IDataObject COM接口,一个IDataObject提供从一个程序到另一个程序传输和访问数据的方法。最通用的OLE数据传输是窗口粘贴板,当然也有拖放。IDataObject是一到多个数据的有效的COM包装。
在我们调查IDataObject任何细节之前,两个重要的数据结构你必须熟悉:FORMATETC和STGMEDIUM接口,他们用来描述和存储OLE数据。
描述 OLE 数据
FORMATETC接口(发音“format et cetera”)用来表示IDataObject提供(或接收)的数据类型,是标准window粘贴板格式(CF_TEXT等)的扩展,因此除了基本的粘贴板格式之外,还包含了数据怎么样rendered和存储。
typedef struct
{
CLIPFORMAT cfFormat; // 粘贴板格式
DVTARGETDEVICE *ptd; // (NULL) rendering的目标设备
DWORD dwAspect; // (DV_CONTENT) rendering的详细程度
LONG lindex; // (-1) 在数据通过页面边界分割的时候使用
DWORD tymed; // 用于数据传输的存储媒体(HGLOBAL,IStream)
} FORMATETC;
FORMATETC 结构的成员如下描述:
- cfFormat:粘贴板格式,用来表示FORMATETC结构。可以是内建的格式(例如:CF_TEXT或CF_BITMAP)或者用RegisterClipboardFormat注册的自定义格式。
- Ptd:指向DVTARGETDEVICE结构,提供已经rendered数据的设备信息。正常的粘贴板操作和拖放操作都是NULL。
- dwAspect:描述用户怎么样render数据的大量细节。通常这个是DVASPECT_CONTECT,表示全内容,但也可以描述较少的信息,例如:图标。
- Lindex:仅仅在当数据通过页面边界被分割的时候使用,它不用于简单的OLE传输,因此该值几乎总是-1。
- Typemed:这是一个有趣的成员;因为其描述了用于存储数据的存储媒体类型。该成员名字自词组“Type of Medium”;该值在window.h中定义的TYMED_XXX等值。
因此有了这个数据结构,OLE 已经提供了一个描述消费者什么样的数据已经怎么样 render 这个数据。
存储 OLE 数据
结构体 STGMEDIUM(STORAGE MEDIUM 的缩写) 提供一个用来存储数据的容器,因此叫存储媒体:
typedef struct
{
DWORD tymed;
union
{
HBITMAP hBitmap;
HMETAFILEPICT hMetaFilePict;
HENHMETAFILE hEnhMetaFile;
HGLOBAL hGlobal;
LPWSTR lpszFileName;
IStream *pstm;
IStorage *pstg;
};
IUnknown *pUnkForRelease;
} STGMEDIUM;
这个结构定义看起来比较复杂,但是有用的仅仅三个成员,因为未命名联合合并了所有内容作为一个实体共享同样的存储空间。
- tymed:这个成员必须和FORMATETC结构相同,这个成员指定已经存储的媒体类型,例如,全局数据(TYMED_HGLOBAL),IStream(TYPED_ISTREAM)等等。相应的联合中的元素是数据的句柄。
- hBitmap/hGlobal等:实际的数据,仅仅他们中的一个是有效的,这依赖于tymed的值。
- pUnkForRelease:一个可选的指针,指向IUnknown接口,数据的接收方应该调用其Release方法。当这个字段是NULL时,接收方有责任释放内存句柄。ReleaseStgMedium API调用在这里非常有用,它负责释放STGMEDIUMS的数据内容,因此实际上我们不需要做什么。
STGMEDIUM 结构是传统的 windows HGLOBAL 内存句柄的扩展,同时支持 HGLOBAL(且一直是最常用的),同时支持许多其他的类型,最有用的是 IStream 和 IStorage 通用 COM 接口。
总之,结构体 FORMATETC 和 STGMEDIUM 一起用来描述和存储 OLE 数据实体的。 FORMATETC 通常用来从 IDataObject 请求指定类型的数据,同时 STGMEDIUM 结构用来接收和保存请求的数据。
传输 OLE 数据
IDataObject 接口提供了从一个程序到另一个程序传递数据的方法,IDataObject 在两个情况下非常有用:粘贴板和拖放。如果设计精细,可以用一个 COM 对象来同时实现粘贴板和拖放操作。
下面的表列出了IDataObject成员函数,按照他们在接口虚表中出现的顺序。为了简化的原因,IUnknown方法(AddRef,Release和QueryInterface)没有列出。
| IDataObject 方法 | 描述 |
|---|---|
GetData |
Render 在 FORMATETC 结构体中描述的数据,并通过 STGMEDIUM 结构体来传递数据 |
GetDataHere |
Render 在 FORMATETC 结构体中的数据,并通过调用者分配的 STGMEDIUM 结构体传输数据 |
QueryGetData |
判断数据对象是否可以 render 在 FORMATETC 结构中描述的数据 |
GetCanonicalFormatEtc |
提供一个潜在不同的但逻辑上相同的 FORMATETC 结构体. |
SetData |
提供一个通过 FORMATECT 结构和 STGMEDIUM 结构描述的源数据对象. |
EnumFormatEtc |
创建并返回一个 IEnumFORMATETC 接口的指针来枚举数据对象支持的 FORMATETC 对象. |
DAdvise |
创建一个在数据对象和通知接收器之间的连接,因此通知接收器能接收到数据对象中通知的改变. |
DUnadvise |
销毁一个前面使用 DAdvise 方法安装的通知. |
EnumDAdvise |
创建和返回一个指向枚举当前通知连接的接口指针. |
这个表看起来很漂亮,我们也看到 EnumFormatEtc 方法并发现我们不得不同时实现 IEnumFORMATETC 接口,它有13个成员函数,不包括IUnknown方法,到这里我们还没有考虑 IDropSource 和 IDropTarget。
庆幸的是,为了简化OLE拖放,我们仅仅需要实现 GetData、QueryGetData和EnumFormatEtc,因此这节省了我们许多工作。
使用 IDataObject 来访问粘贴板
为了使在我们的OLE旅程中放松一下,下面我们来看一个简单的通过OLE来访问粘贴板的例子:
WINOLEAPI OleGetClipboard(IDataObject ** ppDataObj);
这个简单的Windows API调用用来返回一个IDataObject,它提供用来一个干净地访问WINDOWS粘贴板内容的好接口。注意,我们在本例中不需要实现IDataObject接口,我们仅仅需要知道接口怎么样工作的,一个简单的访问粘贴板内容的程序如下:
#include <windows.h>
int main(void)
{
IDataObject *pDataObject;
// Initialize COM and OLE
if(OleInitialize(0) != S_OK)
return 0;
// Access the data on the clipboard
if(OleGetClipboard(&pDataObject) == S_OK)
{
// access the IDataObject using a separate function
DisplayDataObject (pDataObject);
pDataObject->Release();
}
// Cleanup
OleUninitialize();
return 0;
}
OLE API调用非常简单,且它是直接来访问IDataObject对象:
void DisplayDataObject(IDataObject *pDataObject)
{
FORMATETC fmtetc = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stgmed;
// ask the IDataObject for some CF_TEXT data, stored as a HGLOBAL
if(pDataObject->GetData(&fmtetc, &stgmed) == S_OK)
{
// 我们必须锁定HGLOBAL句柄,因为我们不能确信这是否是一个GEM_FIXED数据
char *data = GlobalLock(stgmed.hGlobal);
printf("%s\n", data);
// cleanup
GlobalUnlock(stgmed.hGlobal);
ReleaseStgMedium(&stgmed);
}
}
上面的代码演示了最常用的访问IDataObject的方法,数据通过调用IDataObject::GetData来请求,我们构造一个FORMATETC对象,它指定了我们想要访问的数据的类型,在这个例子中,标准的CF_TEXT数据缓冲区以HGLOBAL内存对象来存储。
数据返回到我们提供的STGMEDIUM结构体中,一旦我们锁定并显示数据,清理和调用标准的ReleaseStgMedium API来释放存储在STGMEDIUM结构中的数据就简单了。
注意,代码中仅仅当文本被选择到粘贴板的时候才工作,也就是说,如果没有CF_TEXT被存储到粘贴板,粘贴板的IDataObject::GetData程序调用会失败,我们什么也不打印。
Coming up in Part 3 – Implementing IDataObject
OK, so we still havn’t actually performed any drag and drop, or even implemented a single COM interface yet. All this is going to change in Part 3 of the tutorial, where we will implement our very own IDataObject and store it on the Windows clipboard. Once we’ve accomplished this (no mean feat!) we will be ready to start dragging and dropping to our heart’s content.

近期评论