Chapter 10 Uniform Data Transfer and Notifications

Have you ever flown up in an airplane and thought about all the little houses down there? If you look at them really close, they're just little piles of people's stuff.

Comedian George Carlin

In this book, we've used the general definition of an object as an entity that manages data as well as functionality to manipulate that data in some way. Programming languages such as C++ allow an object's client to call its member functions and to possibly access its data directly. On the other hand, objects in COM and OLE are always manipulated through interface pointers, and those interfaces have nothing in them except functions. Still, OLE and COM objects, like objects in any programming language, have data—the object's stuff—that may be useful to the client. The question is, will that object let you look at its stuff?

In OLE, the answer concerns how you (playing the role of the client) want to look at that stuff. Do you want to see formatted data structures, graphical representations of that data, or individual fields or properties? Perhaps you really don't care about the actual data at all and would rather see it as an unstructured blob that you can store without knowing its internal format. Much of the rest of this book concerns the various ways to get at an object's stuff, as well as some of its functionality, through a variety of interfaces. In this chapter, we are concerned particularly with formatted data structures that contain any number of individual fields. Chapter 11 looks at the graphical side of things, and Chapters 12 and 13 explore two user interface mechanisms for data exchange, the clipboard and OLE Drag and Drop. Part IV (Chapters 13 through 15) looks into OLE Automation and the idea of individual properties, and Part V (Chapters 17 through 23) details OLE Documents, through which a client generally sees an object's data as unstructured blobs.

A data object, or the source of data, is any object that implements the interface named IDataObject. Through this interface, OLE handles all exchanges of formatted data structures between that data source and its client, which we call the consumer in this context. All sources of structured data, as far as consumers are concerned, are polymorphic through this interface, regardless of how the consumer comes to obtain an IDataObject pointer. There are several ways to obtain such a pointer: the consumer can query for one, it can receive it from a drag-and-drop operation, or it can ask for one from OLE's clipboard APIs.

Some objects might implement IDataObject as their primary interface, representing their overall purpose for existence. For example, a stand-alone, in-process data object can be a great way to expose the information collected from data acquisition hardware that's attached to your computer (via serial port, parallel port, custom board, and so on). Usually this hardware comes with some complex custom API in a DLL or offers its data only through cumbersome DDE (Dynamic Data Exchange) protocols. With a data object, the vendor could ship a component DLL so that any interested client would need only to call CoCreateInstance with the right CLSID (easily retrieved from the registry using a ProgID that can be entered into a consumer at run time) and ask for IDataObject in return. Through that interface, the consumer could enumerate the available data and retrieve whatever structures it wants. The result? Fewer API functions for everyone to design, implement, deploy, and learn. Being a component, such a DLL could be replaced and updated at will, without endangering existing clients. This is Plug and Play not only for the hardware, but also for the software!

You can see that a data object can either be very specialized or be a generic expression of the contents of some other data store, such as the clipboard, or the contents of some other, much richer, object, such as an OLE control. (IDataObject is one of a control's requisite interfaces.) Whenever formatted data structures need to be exchanged, IDataObject is there.

In this chapter, we'll define data objects and explain how they behave—that is, we'll define the semantics of IDataObject. All of the functionality embodied in the Windows clipboard and DDE APIs (even OLE 1, a data transfer API of sorts) is brought together in IDataObject. Regardless of the way a consumer obtains such a pointer, it can always treat it polymorphically, which is exactly why I coined the term (or moniker, we might say) Uniform Data Transfer. This term means that the way a consumer uses an IDataObject pointer is entirely removed from the way it obtains that pointer. The method of obtaining the pointer is called the transfer protocol. Mechanisms such as the OLE Clipboard and OLE Drag and Drop are protocols because they are concerned only with communicating information about the data object along with the IDataObject pointer—they are not concerned with the actual data. The source and the consumer use a protocol to agree on the data format and exchange rules; data transfer is the actual exchange of real, tangible bits.

Up to this time, the Windows API has intertwined the act of transfer with each protocol (clipboard, DDE, and so on). Uniform Data Transfer ends this shotgun marriage and simplifies and homogenizes the way in which both sources and consumers deal with data exchange. In addition, some features (such as dynamic data change notifications) have previously been supported only for one protocol. The other part of Uniform means that these features are enabled for all protocols because they exist as part of IDataObject.

Another problem with the existing Windows API in this area is that data formats are limited to a single WORD clipboard format (a CF_* value) that describes only the layout of the bits in the data structure. This data structure must also reside in global memory—no standards exist for data exchange using any other storage medium. To counter this problem, OLE introduces two new data structures, FORMATETC and STGMEDIUM, that enable far more descriptive formats and a choice of storage mediums, in which the WORD format and the HGLOBAL medium form a small subset. These structures are the first topic in this chapter. This topic is followed by a look at the IDataObject interface and its member functions. Three of these member functions, along with an interface named IAdviseSink (the original sink, predating connection points), allow the consumer to receive notifications when the data changes in the source. Through IAdviseSink, the consumer can either have new data sent with each notification or simply be notified that data changed so that it can request an update later.

These notifications have some parallels with the old (and rather worn-out) DDE protocol. This chapter will also take a look at these parallels, how the OLE model differs from DDE, and how to reconcile those differences. We'll look at a real-world situation in which a DDE-oriented design was replaced with a more efficient design based on OLE. Along with the exploration of the other methods of later chapters, we'll see that OLE offers a complete architecture for data exchange between source and consumer, all of which is centered on, or employs in some fashion, the single IDataObject interface. So no matter how high you are flying, no matter how complex the code, Uniform Data Transfer and the IDataObject interface let you see every object down below as just a little pile of stuff.