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.
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.
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.
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.
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.
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.
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.
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.
Click here for OLE Drag and Drop - Part 3
Please send any comments or suggestions to: james@catch22.net
Last modified: 16 February 2005 20:02:25