OLE Data Transfers

OLE Drag and Drop

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 the FORMATETC structure. This can either be a built-in format such as CF_TEXT or CF_BITMAP, or a custom format registered with RegisterClipboardFormat.
  • ptd: Pointer to a DVTARGETDEVICE 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 be DVASPECT_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 the TYMED_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 the FORMATETC 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 an IUnknown interface on which the receipient of the data should call Release. When this field is NULL, it is the receipient's responsibility to release the memory handle. The ReleaseStgMedium API call is useful here because it takes care of releasing the STGMEDIUM'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.