OLE for Idiots: D Is for Dragon

Herman Rodent
Microsoft Developer Network Technology Group

Created: September 20, 1993

Click to open or copy the files in the Dragon sample application for this technical article.

Abstract

This article continues the "OLE for Idiots" series with a brief look at the role of the dragon in modern day applications. The article describes how the so-called "Dragon Drop" (drag-and-drop) support was added to the OLE (object linking and embedding) version 2.0 container application, Bucket, developed in the previous article, "OLE for Idiots: C Is for Container."

Dragon Drop is not as painful for you as it is for the dragon—as we shall see.

Readers of my previous articles may be starting to think that the business of dealing with OLE 2.0 has finally got a hold on my few remaining functional brain cells. You're right—it has!

Introduction

The dragon is a much-maligned beastie, having historically been put to the sword for such minor offenses as setting fire to a princess, roaring in a loud and provocative manner in a residential area, and being in possession of bad breath on a Sunday. In case you have not personally been in close proximity to any dragons recently, Figure 1 shows a dragon doing what dragons do at night:

Figure 1. A dragon at work

This article builds on its predecessors, so if you have not read the earlier articles in the "OLE for Idiots" series, now would be a good time to do so.

Drag-and-drop support turns out to be very easy to add to an application, given that Clipboard support has already been implemented. Drag and drop uses the same IDataObject interface for the transfer as the Clipboard and requires only two additional interfaces: IDropSource and IDropTarget. Providing drag-and-drop support in your application is becoming a prerequisite for modern-day Microsoft® Windows™-based applications.

The Bucket sample application (and its predecessors) already had an internal form of drag and drop; this had to be removed and replaced with OLE (object linking and embedding) version 2.0's drag and drop. You need not do this in your own application. You could retain whatever method you currently have for moving objects around within the application and only use the OLE 2.0 drag-and-drop technique for moving things between your application and another one. Using OLE 2.0 drag-and-drop support for both internal and external moves makes things much simpler to implement, however, and this is what I chose to do in the Dragon sample. There is some performance penalty in using OLE 2.0 drag and drop for internal moves, so this might not be what you want to end up with in your own application.

Adding drag-and-drop support is done in two steps. First, code is added to provide drop-target support. This can then be tested by dropping objects dragged across from another application that supports OLE 2.0 functionality. Once the drop-target support is finished, the application is further enhanced to become a source for drag-and-drop operations. The Dragon sample application was developed from Bucket in this way.

Code Notes

I got rather fed up with constantly compiling the OLE 2.0 header files for Bucket, so Dragon's make file creates a precompiled header that is then used by each of the various code modules. This speeds compilation up a lot and, I hope, won't cause you too much grief. If you need to remove it, compare Dragon's make file with Bucket's to see what was changed.

Dragon's code contains two bug fixes that I didn't have time to put back into Bucket. The Clipboard paste operation now correctly maintains the size of the object rather than giving it the default size, and objects that are deleted now have their associated substorage removed from the disk file.

The user interface (UI) for Dragon is not all that it might be. I have tried to keep the code as short as possible and, consequently, the functionality of the UI is a little weird in places.

I became very curious about what was causing response delays as I performed various operations in Dragon, so I modified the debug code to report the system time in milliseconds whenever it printed a message to the debug window. The messages themselves take a finite time to be rendered, so don't get too fanatical about their exact values, but it is interesting to observe how long some operations take to execute. Setting the debug level to 4 from Dragon's Debug menu will turn the timing data on.

Adding Drop-Target Support

Adding drop-target support to the application involves adding a small amount of initialization code and yet another OLE 2.0 interface—IDropTarget. The IDropTarget interface needs to be implemented before we can test the initialization code, so we'll start with the interface and see how it's initialized later.

More Object Stuff

If you recall the earlier articles, I implemented an internal data structure for each of the application's embedded objects, and each OLE 2.0 interface was implemented as a part of one of these private data structures. In implementing the drag-and-drop support, I realized that I could no longer implement the OLE 2.0 interfaces required as part of these individual objects, but instead would have to implement the interfaces as part of an object (data structure) at the document level. With this in mind, I created the wonderfully named MYDOCINFO structure, which maintains data related to the current document. (Remember that Dragon only supports a single open document.) Here it is in the final form:

typedef struct _MYDOCINFO FAR *PMYDOCINFO;

typedef struct _MYDOCINFO {
    LONG lRef;                        // Reference count
    POINT ptMouseOffset;              // Mouse offset in object in MMTEXT
    int iWidth;                       // Width in MMTEXT
    int iHeight;                      // Height in MMTEXT
    RECT rcDrag;                      // Drag feedback rectangle
    POINTL ptlPrev;                   // Previous mouse position
    struct _DocIDropTarget {          // IDropTarget interface
        IDropTargetVtbl FAR * lpVtbl; // Pointer to the vtbl
        PMYDOCINFO pDocInfo;          // Pointer to the base info
    } DocIDropTarget;
    struct _DocIDropSource {          // IDropSource interface
        IDropSourceVtbl FAR * lpVtbl; // Pointer to the vtbl
        PMYDOCINFO pDocInfo;          // Pointer to the base info
    } DocIDropSource;
} MYDOCINFO;

The only reason for showing this here is that the IDropTarget and IDropSource interfaces used to implement drag and drop will have this pointers, which we cast to MYDOCINFO pointers (rather than OBJECT pointers), so that we can manipulate the data contained within the structure. Note that the application only supports a single document, so there is only one of these MYDOCINFO structures for the entire application.

The IDropTarget Interface

The IDropTarget interface consists of four member functions: DragEnter, DragOver, DragLeave, and Drop. The first three of these are used to determine what the effect of a drop would be—typically, a move, a copy, or nothing. The application that is the source of the drag-and-drop operation uses the information from these calls to determine what the cursor should look like and thus lets the user have a visual indication of what happens if they release the mouse button. Drop is used to let the application know that the user has released the mouse button, and it's time to do whatever we said we would do if the object was dropped here.

In the simplest implementation of DragEnter, DragOver, and DragLeave, only a return value needs to be set indicating the effect of the drop. In a more practical case, we would also want to draw a rectangle on the window to show exactly where the drop operation would result in the object being placed and how big it would be. Determining the exact drop position is not as trivial as it might seem. You could elect to locate the object so that its top-left corner was directly under the mouse position when the object was dropped. This works okay, except that the feel is all wrong if the user picked up the object by clicking its center.

To correctly position and size the drop feedback rectangle, we need to know the size of the object and where inside the object the mouse was clicked to pick it up. Fortunately for us, when the object was selected for the drag operation, an OBJECTDESCRIPTOR structure was created for it, and this structure contains the mouse offset at the time of selection and the size of the object. The size and position are in HIMETRIC units (0.01 mm) and will need to be converted to whatever you are using. Dragon uses pixels (MM_TEXT mapping mode), so I wrote a function to extract the size and position data from the OBJECTDESCRIPTOR structure and convert the coordinates to pixels. Let's look at how that works before we see how all this fits into the IDropTarget interface:

void GetObjectDescriptorPointAndSize(LPDATAOBJECT pData,
                                     POINT FAR *ppt,
                                     int FAR *lpw,
                                     int FAR *lph)
{
    HGLOBAL hGlobal;
    LPOBJECTDESCRIPTOR lpOD;
    CLIPFORMAT fmt;
    STGMEDIUM stg;
    POINT pt;

    ppt->x = ppt->y = 0;
    fmt = NULL;
    stg.tymed = TYMED_HGLOBAL;
    stg.hGlobal = NULL;
    stg.pUnkForRelease = NULL;
    hGlobal = OleStdFillObjectDescriptorFromData(pData,
                                                 &stg,
                                                 &fmt);

    if (!hGlobal || !(fmt = gcfObjectDescriptor)) {
        return;
    }
    lpOD = (LPOBJECTDESCRIPTOR) GlobalLock(hGlobal);
    if (lpOD) {
        pt.x = (int) lpOD->pointl.x;
        pt.y = (int) lpOD->pointl.y;
        *lpw = (int) lpOD->sizel.cx;
        *lph = (int) lpOD->sizel.cy;
        // Convert HIMETRIC coords to TEXT.
        pt.x = MulDiv(pt.x,
                      iLogPixelsX,
                      2540);
        pt.y = MulDiv(pt.y,
                      iLogPixelsY,
                      2540);
        *lpw = MulDiv(*lpw,
                      iLogPixelsX,
                      2540);
        *lph = MulDiv(*lph,
                      iLogPixelsY,
                      2540);
        GlobalUnlock(hGlobal);
        *ppt = pt;
    }
    GlobalFree(hGlobal);
}

The OleStdFillObjectDescriptorFromData function (from the OLE2UI library) is used to fill a chunk of global memory with the object descriptor information. If the operation is successful, the memory handle returned by OleStdFillObjectDescriptorFromData is locked, so we can get at the data within it. The sizel field contains the size of the object in HIMETRIC units and the pointl field contains the mouse offset, also in HIMETRIC units. Both bits of information are converted to pixels before being returned.

Now that we can find out just how big an object is and where the mouse is located within it, let's look at implementing the member functions of the IDropTarget interface:

STDMETHODIMP DropTarget_DragEnter(LPDROPTARGET pThis,
                                  LPDATAOBJECT pData,
                                  DWORD dwKeyState,
                                  POINTL pt,
                                  LPDWORD pdwEffect)
{
    PMYDOCINFO pDocInfo;
    POINT ptMouse;

    // Get a pointer to our doc info structure.
    pDocInfo = ((struct _DocIDropTarget FAR *)pThis)->pDocInfo;

    if (dwKeyState & MK_CONTROL) {
        *pdwEffect = DROPEFFECT_COPY;   // Copy if Ctrl is down.
    } else {
        *pdwEffect = DROPEFFECT_MOVE;   // Else it's a move.
    }

    // Get the mouse position in our client area.
    pDocInfo->ptlPrev = pt;
    ptMouse.x = (int) pt.x;
    ptMouse.y = (int) pt.y;
    ScreenToClient(ghwndMain, &ptMouse); // In util.c
    // Set the drag feedback rectangle size to the size of the 
    // object and draw the rectangle
    GetObjectDescriptorPointAndSize(pData,
                                    &(pDocInfo->ptMouseOffset),
                                    &(pDocInfo->iWidth),
                                    &(pDocInfo->iHeight));

    pDocInfo->rcDrag.top = ptMouse.y - pDocInfo->ptMouseOffset.y;
    pDocInfo->rcDrag.left = ptMouse.x - pDocInfo->ptMouseOffset.x;
    pDocInfo->rcDrag.bottom = pDocInfo->rcDrag.top
                              + pDocInfo->iHeight;
    pDocInfo->rcDrag.right = pDocInfo->rcDrag.left
                             + pDocInfo->iWidth;
    DrawBand(ghwndMain, &(pDocInfo->rcDrag));

    return ResultFromScode(S_OK);
}

First of all, we do the (now familiar) act on the this pointer to get a pointer to the base object, which in this case is a MYDOCINFO structure. We will use this structure to store various bits of data we need during the drag-and-drop operation. The dwKeyState parameter is used to test for shift and ctrl keys, which (by convention) control whether an object is to be copied or moved as a result of the operation. Dragon does the default thing here and copies only if the ctrl key is down.

The mouse position is provided in screen coordinates (not client coordinates, as some versions of the OLE 2.0 documents suggest). The screen coordinates are converted to client area coordinates.

The object is interrogated to find where the mouse was when it was selected and how big the object is. This data is used to determine the size and position of the visible feedback rectangle, which is then drawn on the client area as a visual guide for the user. The feedback rectangle's size and position is kept so that it can be erased when it needs to be moved to a new position.

I should point out that this piece of code is still rather simplistic because it makes the wild assumption that no matter what sort of object it is, it's going to be okay to drop it on our application. This is not always going to be true, and production code would do well to interrogate the data object further to see if it's reasonable for it to be dropped.

Once the mouse has entered our drop-target zone, we only need to keep track of its movements, which is where the next function comes in:

STDMETHODIMP DropTarget_DragOver(LPDROPTARGET pThis,
                                 DWORD dwKeyState,
                                 POINTL pt,
                                 LPDWORD pdwEffect)
{
    PMYDOCINFO pDocInfo;
    POINT ptMouse;

    // Get a pointer to our doc info structure.
    pDocInfo = ((struct _DocIDropTarget FAR *)pThis)->pDocInfo;
    if (dwKeyState & MK_CONTROL) {
        *pdwEffect = DROPEFFECT_COPY;   // Copy if Ctrl is down.
    } else {
        *pdwEffect = DROPEFFECT_MOVE;   // Else it's a move.
    }
    // See if the mouse position has changed since last time,
    // and if it has, redraw the feedback rectangle.
    if ((pt.x != pDocInfo->ptlPrev.x) 
    || (pt.y != pDocInfo->ptlPrev.y)) {
        pDocInfo->ptlPrev = pt;
        // Erase the feedback rectangle.
        DrawBand(ghwndMain, &(pDocInfo->rcDrag));
        // Get the mouse position in our client area.
        ptMouse.x = (int) pt.x;
        ptMouse.y = (int) pt.y;
        ScreenToClient(ghwndMain, &ptMouse); // in util.c
        // Calculate the new feedback rectangle position.
        pDocInfo->rcDrag.top = ptMouse.y - pDocInfo->ptMouseOffset.y;
        pDocInfo->rcDrag.left = ptMouse.x - pDocInfo->ptMouseOffset.x;
        pDocInfo->rcDrag.bottom = pDocInfo->rcDrag.top
                                  + pDocInfo->iHeight;
        pDocInfo->rcDrag.right = pDocInfo->rcDrag.left
                                 + pDocInfo->iWidth;
    
        // Draw the new position.
        DrawBand(ghwndMain, &(pDocInfo->rcDrag));
    }
    return ResultFromScode(S_OK);
}

The this pointer is used to gain access to the MYDOCINFO structure, and the dwKeyState parameter is used to determine the effect of the drop operation. If the mouse is in the same position it was last time, nothing more needs to be done. Note that OLE 2.0 sends zillions of these messages to your application even if the mouse is not moving—let's eat those useful MIPS (millions of instructions per second)! If the mouse has moved, the old feedback rectangle is erased, the position is recalculated, and a new one is drawn.

If the mouse leaves the target area without any drop occurring, we need to clean up:

STDMETHODIMP DropTarget_DragLeave(LPDROPTARGET pThis)
{
    PMYDOCINFO pDocInfo;

    // Get a pointer to our doc info structure.
    pDocInfo = ((struct _DocIDropTarget FAR *)pThis)->pDocInfo;
    // Erase the feedback rectangle.
    DrawBand(ghwndMain, &(pDocInfo->rcDrag));
    return ResultFromScode(S_OK);
}

All that happens in the code above is that the feedback rectangle is erased.

So there's only one more function to take care of—the one that handles an actual drop in the target area:

STDMETHODIMP DropTarget_Drop(LPDROPTARGET pThis,
                             LPDATAOBJECT pData,
                             DWORD dwKeyState,
                             POINTL pt,
                             LPDWORD pdwEffect)
{
    POBJECT pObj;
    POINT ptOff, ptMouse;
    int w, h;

    // Start by being pessimistic.
    *pdwEffect = DROPEFFECT_NONE;
    // Get the mouse position in our client area.
    ptMouse.x = (int) pt.x;
    ptMouse.y = (int) pt.y;
    ScreenToClient(ghwndMain, &ptMouse); // In util.c
    // Get the mouse offset vector and the object size.
    GetObjectDescriptorPointAndSize(pData, &ptOff, &w, &h); // In util.c
    // Create an OLE object from the source data.
    pObj = CreateObj(ptMouse.x - ptOff.x,
                     ptMouse.y - ptOff.y,
                     w,
                     h,
                     NULL,
                     pData,
                     NULL,
                     FROM_DATA);

    // Add it to our internal object list and make it the
    // currently selected object.
    AppendObj(pObj);
    SelectObj(pObj, TRUE);
    // Draw the final embedded form.
    InvalidateRect(ghwndMain,
                   &(pObj->rc),
                   TRUE);
    // Determine the effect of the drop.
    if (dwKeyState & MK_CONTROL) {
        *pdwEffect = DROPEFFECT_COPY;   // Copy if Ctrl is down.
    } else {
        *pdwEffect = DROPEFFECT_MOVE;   // Else it's a move.
    }
    return ResultFromScode(S_OK);
}

A new embedded object is created from the data transfer object, appended to the application's internal list of objects, and made visible. The state of the ctrl key determines whether the result of the drag operation is to move or copy.

I have not shown the AddRef, Release, and QueryInterface functions here, but you must implement these, too. In fact, it's important that the QueryInterface function be implemented so that OLE 2.0 can call it for an IDropTarget interface. Quite why OLE 2.0 would ask an IDropTarget interface for a pointer to an IDropTarget interface is a mystery to me, but it does, so your code must handle this correctly. Please look at the source code in IFACE.C to see how these functions are implemented.

The Initialization Code

Now that the IDropTarget interface is implemented we can go on to include the initialization code and test the work so far. Here's the code necessary to claim that the application does drag and drop:

RegisterDragDrop(ghwndMain, 
                 (LPDROPTARGET) &(gDocInfo.DocIDropTarget));
CoLockObjectExternal((LPUNKNOWN) &(gDocInfo.DocIDropTarget), TRUE, FALSE);

The RegisterDragDrop function is called with the window handle of our main window and a pointer to the IDropTarget interface in our MYDOCINFO structure. Note again that Dragon only supports one document, so this data structure is implemented simply as a global. Now the only thing left to do is call this week's "Friday afternoon" function—CoLockObjectExternal. I have no idea what exactly is going on here, except that if you don't call this function, you will only get one DragEnter call and nothing more. The OLE 2.0 guys say it is some complex problem that is related to keeping the other application (the object source) around. As I say, this is one of those "Friday afternoon" features, and we just have to live with it.

Testing

Now it's time to compile and play. What you need: your drop-target application and an application that supports OLE 2.0 and thus provides a drag-source object. The OLE 2.0 SDK contains some applications that you can use, and by the time you read this, there will be an assortment of commercial applications using OLE 2.0 that you could also use. I used several applications for testing, including some highly secret Microsoft internal beta applications that I could tell you about, but then I'd have to kill you by canceling your Developer Network membership, causing you to go insane and die in a horrible and nasty way. Enough said.

Run your application and the test application of your choice. Create an object in the test application, and try dragging it over onto your own application. If you try this with Dragon, you can turn the debugging information level up to 3 or 4 and monitor in some detail what is happening as you drag the object onto Dragon's window.

Adding Drag-Source Support

It's hard to know if the source of a drag operation should be called drag source or drop source. Seems all the same to me, and "drag and drop" starts with drag, so I call it drag source. (When little things like this become important in your life, you know it's time for a VACATION.)

The drag-source code consists of an IDropSource interface and some code to call DoDragDrop to start the operation.

The IDropSource Interface

As OLE 2.0 interfaces go, this one's easy. It has only two member functions: GiveFeedback and QueryContinueDrag, and both of these are trivial.

The GiveFeedback function is used to determine what the mouse cursor should look like when it needs to inform the user of a zone where no drops are allowed, a move operation, or a copy operation. OLE 2.0 provides a default set of cursors, so we can pretty much punt using this code:

STDMETHODIMP DropSource_GiveFeedback(LPDROPSOURCE pThis,
                                     DWORD dwEffect)
{
    return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
}

The QueryContinueDrag function is only slightly more complex:

STDMETHODIMP DropSource_QueryContinueDrag(LPDROPSOURCE pThis,
                                          BOOL fEscapePressed,
                                          DWORD dwKeyState)
{
    // If esc key has been pressed, just cancel.
    if (fEscapePressed) return ResultFromScode(DRAGDROP_S_CANCEL);
    // If mouse left button is up, do the drop.
    if (!(dwKeyState & MK_LBUTTON)) {
        return ResultFromScode(DRAGDROP_S_DROP);
    }
    return NOERROR; // Carry on.
}

The operation is terminated if either the esc key is pressed or the mouse button is released. If the mouse button is released, a drop has occurred.

Getting the Drag-and-Drop Operation Started

A drag operation is started when the user selects an object with the mouse and attempts to move it with the mouse button down. You have lots of choices here, and what you do depends a lot on how the rest of your code deals with object movement inside the application's window. For Dragon, I chose to implement any move as a drag-and-drop operation. This meant I didn't have to determine whether the user wanted only to shuffle the object around a bit locally or to take it off to a different application. This is one of those "try it on for size" issues. What worked for me in my sample may not work for you in your application.

Dragon uses the mouse-down event in an object to initiate a drag operation, provided the mouse isn't in the resize border. Here's the code that gets it all to happen:

case WM_LBUTTONDOWN:
    // See if we hit an object, and if we did, then 
    // bring the object to the top, select it,
    // and set up the rubber band for a drag or resize operation.
    pDragObj = HitTestObj(LOWORD(lParam), HIWORD(lParam));
    if (pDragObj) {
        SelectObj(pDragObj, TRUE);
        BringObjToTop(pDragObj);
        rcBand = pDragObj->rc;
        dx = LOWORD(lParam) - pDragObj->rc.left;
        dy = HIWORD(lParam) - pDragObj->rc.top;
        // If the hit isn't in the size border, then don't 
        // capture the mouse; start an OLE drag-drop instead.
         if (SizeBorderHitTestObj(LOWORD(lParam), HIWORD(lParam)) 
            == SB_HIT_NONE) {
            // Do the drag-and-drop thing.
            // We need to set up the mouse offset
            // position in HIMETRIC units.
            pDragObj->iXOffset = MulDiv(dx,
                                        2540,
                                        iLogPixelsX);
            pDragObj->iYOffset = MulDiv(dy,
                                        2540,
                                        iLogPixelsY);
            hResult = DoDragDrop((LPDATAOBJECT)&(pDragObj->ObjIDataObject),
                           (LPDROPSOURCE) &(gDocInfo.DocIDropSource),
                           DROPEFFECT_MOVE | DROPEFFECT_COPY,
                           &dwEffect);
            if (GetScode(hResult) == DRAGDROP_S_DROP) {
                if (dwEffect & DROPEFFECT_MOVE) {
                    // Delete the original.
                    DeleteObj(pDragObj);
                }
                InvalidateRect(hWnd, NULL, TRUE);
            }
        } else {
            pDragObj->iXOffset = 0;
            pDragObj->iYOffset = 0;
            DrawBand(hWnd, &rcBand);
            SetCapture(hWnd);
            bCaptured = TRUE;
        }
    }
    // Drop through to WM_MOUSEMOVE.
    ...

Each time the mouse button goes down, a test is made to see if the mouse hit is inside the resizing border. If it is, the mouse position is converted to HIMETRIC units and the DoDragDrop function is called to begin the operation. This call only returns when the operation is completed, so the code following a call to DoDragDrop simply determines whether a drop took place, and if so, whether the operation was a move or a copy. If it was a move, the original object is deleted, and the display is redrawn to reflect the change. The rest of the code is concerned with drawing the rubber band for resizing operations and is unchanged from previous versions of the application.

The 20-20 Hindsight Department

When I write the sample applications that go with my articles, I keep lots of notes, and as the article progresses, I try very hard to put all the wisdom from the notes into the article. Some of the notes are kind of hard to fit into the article, either because they are personal jokes or because they have nothing to do with the main subject. So I decided that rather than try to shoehorn them in where they didn't fit, I'd include a sort of "Things I learned along the way" section.

Object Reference Counting

Much is made in the OLE 2.0 documentation of reference counting, including a comment that it can be rather tricky to implement correctly. (They might as well have been referring to OLE 2.0 in general. <grin>) When I began my OLE 2.0 work, I didn't understand why I needed reference counts on objects and, consequently, did not implement them. Then came the time when I needed reference counts to make freed objects in the Clipboard go away correctly, so then I implemented counts on the application's main local object structure (OBJECT). I had a lot of trouble understanding why each interface, as well as the object the interface was implemented in, needed a count; since the interface was a static member of the main object, it could not be deleted independently of its parent object. Then one day, I saw the light. I had been doing it all wrong. Now, this sort of thinking can lead to SERIOUS MENTAL ILLNESS if you have just published earlier articles that are now known to contain LIES.

So how does one (a) go back in time and fix the articles already published and (b) deal with reference counts correctly? This situation is similar to the way physics is taught in school. During any given school year, we learn that such and such a rule applies to a particular physical event. We are told that this rule is TRUE and can NEVER BE BROKEN. The next year, physics class starts with a new teacher who politely tells us that this year we will learn that all of last year's work was just an APPROXIMATION and that we will learn the TRUE STORY this year. This never ends, of course, because we never do really find out the absolute truth, and even if we did, there's a good chance we wouldn't understand it. (Try to envisage teaching quantum mechanics to a 5-year-old.)

So in this article, we will learn the ABSOLUTE TRUTH about reference counting. <grin>

What we really want to do is always allocate object memory dynamically; never create static data objects. Each data object that implements an OLE 2.0 interface has a pointer to that interface, and the interface object itself is allocated dynamically. Now reference counts start to make sense. Consider an object with one interface. When the object is created, memory is allocated, and as a part of its initialization, the interface object is created. The pointer to the interface is saved. When the main object initialization is complete, the main object returns a pointer to itself after internally calling its own AddRef function. Now the main object has a reference count and so does the interface. When the user of the object is finished with it, the object's Release function is called. If this causes the reference count to become zero, the object deletes itself. As a part of the deletion process, it calls Release on the interface pointer, which, in turn, causes the interface reference count to fall to zero and the interface object to delete itself.

All this allocating memory and initialization seems like a lot of code if you are a C programmer (as I am), but becomes a great deal less trouble if you are working in C++ because the dynamic creation of objects is as simple as using the new operator. The destructor for the object can be used to take care of the tidy-up code when the object is deleted. So today's wisdom is that OLE 2.0 is better implemented in C++ than in C because C++ makes managing the objects so much easier and, consequently, should lead to more robust implementations.

Applications, Documents, and Items

None of my sample applications support more than one document open at any given time, so I never differentiate in the code between the application and the current document. As an example, File Open means simply "open a file," not "open a file in a particular document window." Because I make no distinction between the application and its document, some of my OLE 2.0 implementation might be a bit hard to follow if the code you are writing is for an application with a multiple-document interface (MDI).

Since creating the samples for this series, I have come to believe that the application code should be organized separately for the application frame, the document(s), and the items within the document(s). OLE 2.0 is organized very much along the same lines and has interfaces that are relevant to the application frame, the document, or an item, but rarely more than any one of these. Once again, working in C++ might help here because you can create a class for the application framework, the document, and so on. This is how the Microsoft Foundation classes work.

Summary

Drag and drop is easy to implement if OLE Clipboard support has already been implemented. OLE 2.0's drag-and-drop feature greatly enhances the user interface for an application and in many ways makes the Clipboard redundant.

Implementing OLE 2.0 support in C is possible but a chore. It's easier to do it in C++, but it's even easier to have someone else do it. The Microsoft Foundation Class Library version 2.5 will include OLE 2.0 support to a level way beyond what has been covered in these four articles.