OLE Data Transfers
8 minute read •
Welcome to the second article in the “OLE Drag and Drop” tutorial series! The purpose of this article is to explore how data is represented and transferred between applications in the OLE environment.
The very heart of OLE data transfers is the IDataObject
COM interface. An IDataObject
provides a method of transferring and accessing data from one application to another. The most common use of OLE data transfers is the Windows clipboard, and of course drag and drop. The IDataObject
is effectively a COM wrapper around one or more items of data.
Before we look at the IDataObject
in any detail, there are two very important data structures with which you must become familar: the FORMATETC
and STGMEDIUM
structures, which are used to describe and store OLE data.
Describing OLE data
The FORMATETC
structure (pronounced “format et cetera”) is used to identify the type of data that an IDataObject
can supply (or receive). It is basically an extension of the standard Windows clipboard formats (CF_TEXT
etc). So in addition to the basic clipboard format, the FORMATETC
structure also describes how the data should be rendered and stored.
typedef struct
{
CLIPFORMAT cfFormat; // Clipboard format
DVTARGETDEVICE *ptd; // (NULL) Target device for rendering
DWORD dwAspect; // (DV_CONTENT) How much detail is required for data rendering
LONG lindex; // (-1) Used when data is split across page boundaries
DWORD tymed; // Storage medium used for data transfer (HGLOBAL, IStream etc)
} FORMATETC;
The members of the FORMATETC
structure are described below.
This value will almost always be -1.
cfFormat
: The clipboard format which is used to identify theFORMATETC
structure. This can either be a built-in format such asCF_TEXT
orCF_BITMAP
, or a custom format registered withRegisterClipboardFormat
.ptd
: Pointer to aDVTARGETDEVICE
structure, which provides information about the device for which the data has been rendered. For normal clipboard operations and drag and drop, this will usually be NULL.dwAspect
: Describes the amount of detail used to render the data. Usually this will beDVASPECT_CONTENT
, meaning “full content”, but could describe a lesser detail such as thumbnail or icon.lindex
: Is only used when data is to be split across page boundaries, and is not used for simple OLE transfers.tymed
: This is the interesting member, because it describes the type of “storage medium” used to hold the data. This member has taken it’s name from the words “Type of Medium” - i.e. ty…med. The value is taken from on of theTYMED_xxx
values defined in windows.h
So with this single data structure, OLE has provided a method to describe to a “consumer” what the data is, and how it is intended to be rendered.
Storing OLE data
The STGMEDIUM
structure (short for STORAGE MEDIUM) provides a container in which to actually hold data - hence the term storage medium.
typedef struct
{
DWORD tymed;
union
{
HBITMAP hBitmap;
HMETAFILEPICT hMetaFilePict;
HENHMETAFILE hEnhMetaFile;
HGLOBAL hGlobal;
LPWSTR lpszFileName;
IStream *pstm;
IStorage *pstg;
};
IUnknown *pUnkForRelease;
} STGMEDIUM;
The structure definition above might look complicated, but there are in effect only three members, because the “unnamed” union collects all of it’s contents as one entity sharing the same space within the STGMEDIUM structure.
tymed
: This must be the same as tymed in theFORMATETC
structure - this member specifies what medium has been used to store the data - i.e. global data (TYMED_HGLOBAL
),IStream
(TYMED_ISTREAM
) etc. The corresponding element in the union is the “handle” to the data.hBitmap
/hGlobal
etc: The actual data. Only one of these will be valid, depending on the value of tymed.pUnkForRelease
: An optional pointer to anIUnknown
interface on which the receipient of the data should callRelease
. When this field is NULL, it is the receipient’s responsibility to release the memory handle. TheReleaseStgMedium
API call is useful here because it takes care of releasing theSTGMEDIUM
’s data contents, so in fact no work is required on our part.
The STGMEDIUM
structure is basically an extension of the traditional Windows HGLOBAL memory handle. Whilst the HGLOBAL is still supported (and is still the most common!), many other types of storage are supported, the most useful being the IStream
and IStorage
generic COM interfaces.
So in conclusion, the FORMATETC
and STGMEDIUM
structures are used in conjunction to describe and store an OLE data entity. The FORMATETC
is usually used to request a specific type of data from an IDataObject
, whilst the STGMEDIUM
structure is used to receive and hold the requested data.
Transferring OLE data
The IDataObject
interface provides a method to transfer data from one application to another. An IDataObject
is very useful for two situations - Clipboard transfers and Drag and Drop. With a careful design, it is therefore possible to implement both clipboard and drag-and-drop support with a single COM object.
The following table lists the IDataObject
member functions in the order they must appear in the interface v-table. The IUnknown
methods (AddRef
, Release
and QueryInterface
) have been removed for brevity.
IDataObject Methods | Description |
---|---|
GetData | Renders the data described in a FORMATETC structure and transfers it through the STGMEDIUM structure. |
GetDataHere | Renders the data described in a FORMATETC structure and transfers it through the STGMEDIUM structure allocated by the caller. |
QueryGetData | Determines whether the data object is capable of rendering the data described in the FORMATETC structure. |
GetCanonicalFormatEtc | Provides a potentially different but logically equivalent FORMATETC structure. |
SetData | Provides the source data object with data described by a FORMATETC structure and an STGMEDIUM structure. |
EnumFormatEtc | Creates and returns a pointer to an IEnumFORMATETC interface to enumerate the FORMATETC objects supported by the data object. |
DAdvise | Creates a connection between a data object and an advise sink so the advise sink can receive notifications of changes in the data object. |
DUnadvise | Destroys a notification previously set up with the DAdvise method. |
EnumDAdvise | Creates and returns a pointer to an interface to enumerate the current advisory connections. |
The table above looks pretty overwhelming, and it gets even worse when we look at the EnumFormatEtc
method and discover that we also have to implement the IEnumFORMATETC
interface as well! Thats a total of thirteen member functions, not including the IUnknown
methods - and we havn’t even begun to look at IDropSource
and IDropTarget
!
Fortunately for simple OLE drag&drop, only the GetData
, QueryGetData
and EnumFormatEtc
members are required so that saves us alot of work.
Accessing the Clipboard using IDataObject
To ease ourselves into the way OLE works, we will begin with a simple program which will access the clipboard using OLE.
WINOLEAPI OleGetClipboard(IDataObject ** ppDataObj);
This single Windows API call is used to retrieve an IDataObject
, which provides a nice interface to cleanly access the Window’s clipboard content. Note that we don’t have to implement the IDataObject
interface in this case, we just need to know how to interface with it. A simple program to access the clipboard contents is shown below:
#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;
}
The OLE API calls are very simple, and it is also straight-forward to programmatically access an 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)
{
// We need to lock the HGLOBAL handle because we can't
// be sure if this is GMEM_FIXED (i.e. normal heap) data or not
char *data = GlobalLock(stgmed.hGlobal);
printf("%s\n", data);
// cleanup
GlobalUnlock(stgmed.hGlobal);
ReleaseStgMedium(&stgmed);
}
}
The code above demonstrates the most common method used to access an IDataObject
. The data is requested using IDataObject::GetData
. We constructed a FORMATETC
object which was used to specify exactly what type of data we wanted - in this case, a standard CF_TEXT
buffer of data, stored as a regular HGLOBAL memory object.
The data is returned into the STGMEDIUM
structure that we provided. Once we lock and display the data it is a simple matter to cleanup and call the standard ReleaseStgMedium
API, to release the data stored inside the STGMEDIUM
structure.
Note that the code sample will only work when there is text selected into the Windows clipboard - that is, if there is no CF_TEXT
stored in the clipboard, the clipboard’s IDataObject::GetData
routine will return a failure code and we won’t print anything.
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.