Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Embedded Systems

Inside the OLE 2 SDK


MAY95: Inside the OLE 2 SDK

Building OLE 2.0 containers

Ira is a software developer specializing in windows and Motif. He can be contacted through CompuServe at 70711, 2570 or at 301-924-0596.


Object Linking and Embedding (OLE) was originally devised by Microsoft to allow the creation of compound documents. The idea behind OLE 1.0 was to allow container documents to embed objects--pictures, graphs, sound clips and the like--within the document, treating these objects in a black-box fashion. In this manner, the container document need not know the specific file formats of the embedded objects--the container merely interfaces with the OLE API, which loads and passes requests to the object applications. Similarly, client applications (embedded objects) could write to this API, and by doing so instantly gain interoperability with a large number of OLE container applications.

The OLE 2 specification addresses some of the deficiencies of OLE 1, as well as extending the OLE 1 specification to a more generalized methodology for dealing with black-box objects. To accomplish this, OLE 2 treats the interfaces between objects in a more uniform and general way. For instance, OLE 1 used DDE for communication, creating synchronization problems between the client and server tasks. OLE 2 uses lightweight RPCs that rely upon the Windows message queue, eliminating such problems.

In this article, I'll present techniques for building an OLE client application using the Microsoft OLE 2 Software Development Kit (SDK). The application, Collage, allows embedded objects to be placed within the Collage container, then moved, sized, and saved, illustrating the basic techniques for incorporating OLE within your applications.

OLE 2 SDK

The OLE 2 SDK, which is delivered on CD-ROM, comes with both debug and production copies of the OLE 2 DLLs, plus complete documentation in electronic format. Printed documentation is not provided, although that may be ordered sepa-rately. In addition, a wealth of sample code and applications are provided along with various helpful utilities.

The documentation includes Windows help files for OLE, which I found most useful during development. These help files made it easy to find the correct function or interface to call, and to plug in the correct parameters during code development. The SDK additionally provides the complete OLE 2 specification, various useful pieces of background information, and PowerPoint viewer with slides covering many OLE 2 aspects. All in all, the organization and online viewing tools make it much easier to wade through the mountains of documentation.

Microsoft also bundles a number of handy utilities in the SDK. The most useful in the development of Collage were the IDataObject Viewer and Ole2View applications. Ole2View lets you view information about all OLE servers registered on your system, as well as the OLE 2 interfaces supported. The IDataObject Viewer shows information on the IDataObjects passed during clipboard transfers and drag-and-drop transfers. Clipboard transfers are a bit trickier in OLE 2 than in standard Windows. The IDataObject Viewer allows the content of the IDataObjects used for such transfers to be debugged more easily.

Other tools include Docfile Viewer, which lists the elements within a docfile; Automation Test, which tests applications implementing OLE Automation; Class ID Generator, which generates a unique class ID (more on this in a moment); Running Object Table Viewer, which shows information about objects in the Running Object Table, displaying all monikers and "active" Automation objects; LRPC Message Monitor, which displays the messages passed between two applications; OLE 2 Free Module, a debugging tool that can be used for freeing OLE 2.0 DLLs and the modules that loaded them; and Windows Process Status, used for deleting Windows processes from memory. Finally, there's Windows Executable, a tool allowing Windows programs to run from a DOS box. As the documentation points out, this can be particularly useful when a Windows-based tool needs to be launched from a DOS-based make process.

Interfaces

The most basic concept within OLE 2 is the notion of an interface. Almost all operations require either obtaining an interface from the outside world or exposing an interface from your application to the outside world. The standard interfaces are defined within the API as C++ classes, with the member functions defined as pure virtual functions. You create interfaces in the application by deriving inherited classes from the API base class and defining the virtual functions. All of the interface classes in the API inherit from the base interface IUnknown.

The IUnknown interface consists of three member functions: AddRef, Release, and QueryInterface. All of the interfaces within OLE 2 use reference counting. When first obtaining a pointer to an interface, the user must call the AddRef function to increment the object's reference count. When the user finishes with the interface, the application calls the Release function, causing the reference count to be decremented. When the reference count reaches zero, the object is free to destroy the interface. QueryInterface allows you to obtain an interface pointer for any other interface to the object. Each interface has a unique interface identifier (IID), a 128-bit number assigned by Microsoft. When you pass an IID to the QueryInterface function, a pointer to the desired interface is returned. Therefore, once you find a single interface to an object, all of the other interfaces can be easily obtained. The QueryInterface function, like most of the interface member functions, returns an HRESULT indicating the success or failure of the operation. It also provides a more-detailed status code, giving interface-specific status information. The standard macros FAILED and SUCCEEDED determine whether the operation succeeded or failed; the function GetScode extracts the status code.

Once you have an interface, all of the other interfaces are easy to find. But, how do you get the first interface? Just as each interface has a unique IID number, each object class has its own unique interface identifier, or CLSID. Once the CLSID is known, an OLE API function creates the object and returns an interface pointer. A registration database maintained by Windows lists the CLSIDs for each server. When a server application is installed, it lists its CLSID, the path to its executable, and a human-readable name in this registration database. This database is accessible through the Windows API, and the information in it can be viewed by running REGEDIT using the /V parameter.

Structured Storage

Structured storage, a concept new to OLE 2, allows the creation of a directory-like structure within a file. Under OLE 1, embedded objects were stored within a standard DOS file. Since the container was responsible for managing this file, it could not dynamically shrink or grow as required by the object. To overcome this problem, OLE 1 resorted to storing the object information within global memory. The container was then responsible for saving this information in the appropriate place in its file. With structured storage, each embedded object controls and manages its own storage, which can grow and shrink as necessary, without intervention from the container.

In structured-storage terminology, a "storage" is analogous to a directory and a "stream" represents a file. Structured storage is referenced through the use of interface pointers, using the same scheme as all other OLE 2 interfaces. The function StgCreateDocfile opens the file yielding an IStorage interface pointer. Streams and storages below this root storage are opened using IStorage::OpenStorage or IStorage::OpenStream. These functions in turn yield interface pointers to their respective storages and streams. The Release function closes storages and streams.

The structured-storage model also provides transaction processing. Opening a storage in transaction mode causes all writes into the storage to go to a temporary file. The application must call IStorage's Commit function to write the information permanently in the file. An undo function uses IStorage::Revert, which removes all information written since the last commit. This allows the embedded objects to use their storage for maintaining current data; information is permanently saved only on an explicit Save command from the user.

Implementing a Container

Collage is a minimal application designed to demonstrate container documents; it does not include support for linking or in-place editing. Nevertheless, the application requires approximately 3000 lines of code to implement. The complete application with source code and executables is available electronically; see "Availability" on page 3.

The initial steps in creating an OLE application involve increasing the size of the message queue and initializing the OLE libraries. As OLE 2 utilizes the Windows message queue for communication between objects, the normal queue size of eight messages is generally insufficient for OLE applications. The first step is to increase this size to 96 messages; otherwise, inexplicable crashes may result while running your application. The next standard step is to call OleInitialize to initialize the OLE libraries. These two steps are performed in WinMain before entering the message loop; see Listing One, page 106.

Collage establishes a main window containing the menu bars and decorations and a child window containing the document. This structure makes it easy to add toolbars and/or change the document from a single-document interface to a multiple-document interface. OLE object functionality is embedded in the OleClientObj class. An OLE client object must have at least an IUnknown interface, an IOleClientSite interface, and an IAdvise interface; Collage implements these through the ifUnknown, ifClient, and ifAdvise members of OleClientObj. IUnknown is the most basic OLE interface, consisting of only three member functions. It is defined in the OLE SDK as a class having three pure virtual functions. Collage implements the class by defining a derivative class, ImpIUnknown, using IUnknown as the base class. ImpIUnknown provides functionality for the virtual-member functions. For the most part, the functionality is provided by calling member functions of OleClientObj.

The IOleClientSite interface, implemented as the inherited class ImpIOleClientSite, allows the embedded object to make various requests of the container document. These include requests to scroll the object into view and to show the object in its activated state. The IAdvise interface notifies the client of events of interest happening to the embedded object. These include notifications that the object has been saved, that its appearance or underlying data has changed, or that it has been closed.

There are three constructors for creating an object. The first creates a new object; the second reads in an existing object from disk; and the third is used in paste operations. When a completely new object is created from scratch, the first thing Collage does is to allocate a substorage for it. It then writes in basic information such as size, position, and object number. This information is written in a stream located in the object's substorage. Since this substorage belongs to the object and the server is free to use this substorage in any way it sees fit, it must be clearly denoted as belonging to the client. By convention, this is done by beginning the stream name with a byte containing \3.

Once these preliminaries are complete, Collage invokes the server to create the object using the API function OleCreate. Given a CLSID, OleCreate returns an interface pointer to the object. For reasons never totally specified by Microsoft, OleSetContainedObject must be called (probably to do some cleanup left over from OleCreate). An advise loop is set up by calling the advise function on the OleObject interface. At this point, the object is merely an initialized empty object of the proper type. The next step is to present it to the user for editing. This is done by activating the object.

The IOleClientSite interface allows the container to know when the object is open for edit. It also lets the container know when it wants to do an update through the SaveObject function. The container saves the object using the OleSave function and commits the object's storage. The first time an object is updated, the container asks for the object's desired size. After that, only the user can resize the object.

The file can be saved simply by committing the root storage of the file, which causes all changes to be permanently written to disk. Implementing the "Save As" function is only a little more difficult. First, each object must be notified to release its private storage using the IOleObject::HandsOffStorage function. Then the root storage is copied to the "Save As" root storage and committed to capture all changes made in the document. Finally, each object's storage is opened, and the object is notified of its new storage using the SaveCompleted function. If you're accustomed to conventional file I/O, all of this may at first seem a bit strange. However, it is both cleaner and easier.

Clipboard Transfers

Data Transfer is performed under OLE 2 using IDataObject objects. This applies both to clipboard operations and drag-and-drop operations. The IDataObject provides a uniform method of transferring data between applications and affords a richer, more flexible environment than the standard Windows clipboard transfer.

The IDataObject uses a FORMATETC data structure to describe the particular formats it is capable of rendering. The FORMATETC not only describes a particular clipboard format but also identifies how the data will be passed. IDataObjects may pass data by a variety of methods, including global memory, structured storage objects, bitmaps, metafiles, and text. Due to the use of the IDataObject, embedded objects may be passed stored on disk in a structured file format without consuming large chunks of global memory.

An object-descriptor format is also defined for the IDataObject that allows an object's description to be passed to the application using the object. When another application actually requests the data, the GetData or GetDataHere members are called and the data is rendered. The OLE 2 libraries provide the necessary interfaces so that non-OLE and OLE 1 applications can interact with the clipboard in the usual way without using IDataObjects.

When the application closes, the IDataObject interface (supported by the application code) can no longer be used. The function OleFlushClipboard is provided to remove the IDataObject from the clipboard, leaving the conventional clipboard formats such as bitmaps, metafiles, and so on.

Conclusion

OLE 2 is based on the notion of interfaces. The hardest part of OLE is learning the various interfaces and how they interrelate. OLE 2 is a very powerful tool but its API is large and complex. This article explains some of the basics of developing OLE applications. Other aspects, such as OLE automation and OLE controls, follow the same methodology as compound documents. As OLE becomes more popular, it will become necessary for all Windows applications to provide at least some OLE functionality.

The OLE 2 SDK possesses a wealth of information. However, it is almost as large as the entire Windows API, so it is no small undertaking to master it. Collage, a minimal application designed to demonstrate container documents, does not include support for linking or in-place editing and requires 3000 lines of code. To provide this functionality in an application requires considerable work; application frameworks and other program development tools that can take much of the grunt work out of developing OLE 2 applications are still sorely needed.

For More Information

OLE 2 Software Development Kit 2.01

Microsoft

One Microsoft Way

Redmond, WA 98052-6399

800-227-4679

Listing One


#include <windows.h>
#include <ole2.h>
#include <initguid.h>
#include <oleguid.h>
#include <coguid.h>
#include "mainwin.h"
#include "wdres.h"
#include "gdiobj.h"
#include "debug.h"

int bDebug = 0;
FILE    *debug = NULL;
char    * MainWindowClass = "MainWindow";
char    * TextWindowClass = "TextWindow";

HCURSOR hPlain;
HCURSOR hVertArrow;
HCURSOR hHorizArrow;
HCURSOR hSwArrow;
HCURSOR hNwArrow;
HCURSOR hWaitCursor;
HBITMAP hMarkFore;
HBITMAP hMarkBack;
UINT    cfNative;
UINT    cfEmbedSource;
UINT    cfEmbeddedObject;
UINT    cfObjectDescriptor;
int pascal WinMain (HINSTANCE hInstance,HINSTANCE hPrevInstance,
                                               LPSTR lpCmndLine,int nCmndShow)
{
    int         nMsg;
    WNDCLASS    wndClass;
    MainWindow  *Main;
    HACCEL      hAccel;
    MSG         msg;
    nMsg = 96;
    while (!SetMessageQueue (nMsg)&& (nMsg > 8))nMsg -= 8;
        if (bDebug)
        debug = fopen("debug.txt","w+");
        FPRINTF (debug,"Starting Collage\n");
    if (hPrevInstance == NULL) {
        wndClass.style = 0;
        wndClass.lpfnWndProc = BasicWindow::Dispatch;
        wndClass.cbClsExtra = 0;
        wndClass.cbWndExtra = sizeof (long);
        wndClass.hInstance = hInstance;
        wndClass.hIcon = LoadIcon (hInstance,MAKEINTRESOURCE(ICON_1));
        wndClass.hCursor = LoadCursor (NULL,IDC_ARROW);
        wndClass.hbrBackground = (HBRUSH)GetStockObject (WHITE_BRUSH);
        wndClass.lpszMenuName = MAKEINTRESOURCE(MAIN_MENU);
        wndClass.lpszClassName = MainWindowClass;

        RegisterClass (&wndClass);

        wndClass.style = CS_DBLCLKS;
        wndClass.lpfnWndProc = BasicWindow::Dispatch;
        wndClass.cbClsExtra = 0;
        wndClass.cbWndExtra = sizeof(long);
        wndClass.hInstance = hInstance;
        wndClass.hIcon = NULL;
        wndClass.hCursor = (HCURSOR) LoadCursor (NULL, IDC_CROSS);
        wndClass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
        wndClass.lpszMenuName = NULL;
        wndClass.lpszClassName = TextWindowClass;
        RegisterClass (&wndClass);
     }
     if (FAILED(OleInitialize(NULL))) {
        MessageBox (NULL,"Error...","Cannot initialize OLE 2 LIbrary",
                                               MB_OK|MB_APPLMODAL|MB_ICONSTOP);
        return (0);
     }
    hAccel = LoadAccelerators (hInstance,MAKEINTRESOURCE(ACC_MAIN));
    hPlain = LoadCursor(NULL, IDC_CROSS);
    hVertArrow = LoadCursor(NULL, IDC_SIZENS);
    hHorizArrow = LoadCursor(NULL, IDC_SIZEWE);
    hNwArrow = LoadCursor(NULL, IDC_SIZENWSE);
    hSwArrow = LoadCursor(NULL, IDC_SIZENESW);
    hWaitCursor = LoadCursor(NULL, IDC_WAIT);
    hMarkFore = LoadBitmap(hInstance,MAKEINTRESOURCE(IBM_MARKFORE));
    hMarkBack = LoadBitmap(hInstance,MAKEINTRESOURCE(IBM_MARKBACK));
        cfNative = RegisterClipboardFormat("Native");
    cfEmbedSource = RegisterClipboardFormat("EmbedSource");
    cfObjectDescriptor = RegisterClipboardFormat("Object Descriptor");
    cfEmbeddedObject = RegisterClipboardFormat("Embedded Object");
     Main = new MainWindow (hInstance,"Collage",nCmndShow);
     Main -> Create();
     while (GetMessage (&msg, NULL, 0, 0)) {
        if (!TranslateAccelerator(Main->GetWindow(),hAccel,&msg)) {
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }
     }
     OleUninitialize();
    DeleteObject(hMarkFore);
    DeleteObject(hMarkBack);
    DestroyCursor(hPlain);
    DestroyCursor(hVertArrow);
    DestroyCursor(hHorizArrow);
    DestroyCursor(hNwArrow);
    DestroyCursor(hSwArrow);
    DestroyCursor(hWaitCursor);
     FPRINTF(debug,"Ending Collage\n");
     if (bDebug) fclose(debug);
     return 0;
}     End Listing




Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.