OLE Server Implementation Guide

Kraig Brockschmidt

Created: May 6, 1992

ABSTRACT

The objective of this document is to help applications programmers add object linking and embedding (OLE) server capabilities to new or existing applications. This guide provides OLE technical background information, suggestions for preparing an application to become an OLE server, and step-by-step instructions on where to add code, what OLE functions to call, and what specific actions to perform.

INTRODUCTION

The objective of this document is to help applications programmers add object linking and embedding (OLE) server capabilities to new or existing applications in the MicrosoftÒ WindowsÔ graphical environment. A classic problem in implementing OLE is that you must write considerable code before testing anything. The step-by-step implementation section provides checkpoints where you may compile and test the OLE code you just added. This incremental approach gives you a clearer picture of what the code is actually doing and allows you to debug a small part of code easily. The nature of OLE makes debugging difficult as it is; if you try to test all the OLE-specific code at once, determining where the bug really is and fixing it can take considerable time. In any case, you are always free to take your own approach.

OLE is a protocol that complements, not replaces, dynamic data exchange (DDE) and standard Clipboard data exchange. It is also a protocol that sits easily on top of an existing application. If you are planning to write a server application and have not yet done so, write the non-OLE application first, and then follow the steps in this guide to enable OLE. “Integrating OLE” into an application is not necessary because OLE affects only a few specific parts.

This guide should enable you to add basic OLE support to a suitable server application within about a week, depending on the complexity of your application. The sample server demonstrates the steps described in this guide and contains code that you can immediately transplant to your application.

Microsoft Windows Programming Knowledge Required

This document assumes a working knowledge of the areas in Microsoft Windows listed below. You will need to understand all areas except atoms and DDE. If you are unfamiliar with an area, please consult one of the listed references.

Area Reason for understanding the area

Atoms Atoms provide a convenient method for storing variable-length strings in a single integer, especially for structures.
Bitmaps All OLE servers are required to produce a graphic representation of their data. A bitmap is one format that a server provides for this purpose. (See Metafiles for another representation format.) OLE server implementation does not require knowledge of device-independent bitmaps (DIBs).
Callback functions MakeProcInstance is required for initializing function tables.
Clipboard I/O OLE servers that can run stand-alone must provide several non-OLE and OLE-specific data formats on the Clipboard.
DDE Because OLE version 1.x works off the DDE protocol, a knowledge of DDE may help you understand how the OLE protocol works.
Dynamic memory allocation The application must allocate memory for the Clipboard formats and possibly for OLE-specific structures.
Dynamic menu changes User interface changes for OLE servers include modifying menu items for embedded objects with the ModifyMenu function.
File I/O Any server that supports linking (as opposed to embedding) must save information in a file.
Metafiles A metafile is a required graphic representation of the server’s data. (See Bitmaps for another representation format.)
Mapping modes The OLE libraries express all dimensions in MM_HIMETRIC units; your application may need to convert these units to another mapping mode.
Message loops The OLE version 1.x libraries depend on DDE messages, so the application must process messages to allow OLE to function. This may impact background processing.

References

Petzold, Charles. Programming Windows: The Microsoft Guide to Writing Applications for Windows, 2nd ed. Redmond: Microsoft Press, 1990.

Richter, Jeffrey. Windows 3: A Developer’s Guide. Redwood City: M&T Publishing, 1991.

Wilton, Richard. Windows 3 Developer’s Workshop. Redmond: Microsoft Press, 1991.

Yao, Paul and Peter Norton. Peter Norton’s Windows 3.0 Power Programming Techniques. New York: Bantam Books, 1990.

Conventions

1.In-line code, taken from the sample server included with this guide, is presented in a fixed-pitch font:

if (OLE_WAIT_FOR_RELEASE==os)

{

pOLE->oleDoc.fRelease=FALSE;

FOLEReleaseWait(&pOLE->oleDoc.fRelease, pOLE->oleSvr.lh);

}

2.Information of special importance is provided in notes.

3.Definitions of terms such as Server and Native used in this guide are in the Glossary at the end of this document.

Sample Server: SCHMOO

Accompanying this implementation guide is a sample OLE server called Schmoo, named after the Li’l Abner character who reminds us of magnetic flux and electric fields in college physics classes. Schmoo is a name that is not likely to be duplicated on your system, so it will not interfere with other servers.

Schmoo is a single-document application that is capable of running multiple instances. It allows you to edit, save, and load a Schmoo figure consisting of a bunch of dots connected with lines. Although the Schmoo figure is somewhat uninteresting1, Schmoo demonstrates the OLE server protocol and serves as a model for discussions in this document. In fact, the in-line code in this guide was taken directly from Schmoo.

Source code structure

Schmoo has a fair number of source files that isolate the OLE-specific code, leaving only a few parts of mostly non-OLE files touched by OLE. This isolation demonstrates how OLE can simply sit on top of an existing application, requiring few new files and minor additions to the existing application code.

Two types of source files exist: those with the OLE prefix (for example, OLEINIT.C) and those without the prefix. The OLE files contain, as you might guess, OLE-specific code. Some files, like OLEINIT.C and OLECLIP.C, correspond to non-OLE files like INIT.C and CLIP.C. The source files and their contents are listed below.

File Contents

Non-OLE Files:  

COMMDLG.C Support for File Open and File Save As common dialogs.
MISC.C Functions that do not have any other home.
POLYLINE.C Polyline window procedure and functions to generate bitmaps or metafiles of the image in the window. All data editing and creation is handled here.
POLYLINE.H Prototypes and definitions for POLYLINE.C.
SCHMOO.C Main window procedure.
SCHMOO.H Prototypes and definitions for non-OLE functions.

Files Dealing with OLE:  

CLIP.C Pre-OLE Clipboard support; calls OLECLIP.C functions for OLE Clipboard formats.
EXIT.C Pre-OLE cleanup function; calls OLEEXIT.C for OLE cleanup.
FILE.C Pre-OLE file I/O functions, modified to include OLE application programming interface (API) calls for File New, File Open, File Save, and File Save As.
INIT.C Pre-OLE initialization function; calls OLEINST.C and OLEINIT.C to perform OLE-specific installation and initialization tasks.

OLE-Specific Counterparts:  

OLECLIP.C Functions to place OLE data on the Clipboard.
OLEEXIT.C OLE cleanup functions, including calls to OLEVTBL.C to de-initialize method tables.
OLEINST.C Functions to install the server in the registration database.
OLEINIT.C OLE initialization functions, including calls to OLEVTBL.C to initialize method tables.
OLEMISC.C OLE functions without a home, for example, functions for modifying menus, notifying the client, and providing a message-processing loop.
OLEVTBL.C Functions to initialize and free method tables for the server, documents, and objects.

OLE-Specific Code:  

OLEDOC.C OLESERVERDOCVTBL methods.
OLEOBJ.C OLEOBJECTVTBL methods.
OLESVR.C OLESERVERVTBL methods.
OLEGLOBL.C Declarations of OLE-specific global data.
OLEGLOBL.H External declarations of OLE-specific global data and prototypes of OLE-specific functions.
OLEINST.H Prototypes for functions in OLEINST.C that deal with the registration database. These are not placed in OLEGLOBL.H to facilitate the removal of the installation code to a separate setup program, if you have one.

Schmoo contains various functions that you can easily extract and adapt for use in your server application

Isolation of global data and strings

Because you will be using global variables extensively in the OLE protocol, OLE and non-OLE global variables have been isolated into two separate structures. The only true global variables are pointers (pGlob and pOLE) to these two structures and a pointer to the rgpsz string array (see below). pGlob points to application globals unrelated to OLE except for the fOLE flag that indicates that the application is running as a linked or an embedded server instead of stand-alone. pOLE points to OLE-specific globals.

The separation of these unrelated globals provides an easy method for identifying the use of such globals in code. Any time a global is used, it must be referenced off one of the pointers, as in pGlob->fOLE or pOLE->fEmbed. This clearly marks the use of a global (as opposed to a local) variable.

rgpsz is an array allocated in HLoadAppStrings (INIT.C). The application’s string table is read into local memory so that all strings can be referenced by a pointer using an index into rgpsz, such as rgpsz[IDS_CLASSSCHMOO].

OLE TECHNICAL BACKGROUND

The following sections provide background information on OLE components and concepts:

OLESVR.DLL, OLESVR.LIB, and OLE.H

SHELL.DLL, SHELL.LIB, and SHELLAPI.H

Library redistribution and installation

OLE communication routes

OLE data structures and application-specific variations

Miniservers vs. full servers

Single-document servers vs. multiple-document servers

Clipboard formats and conventions

OLESVR.DLL, OLESVR.LIB, and OLE.H

The OLESVR library contains 10 functions that enable a server to register itself with OLESVR and to inform OLESVR of changes to objects and documents. OLESVR.DLL contains the functions, and OLESVR.LIB is the import library to which you link your server application.

Function When used Description

OleRegisterServer Initialization Registers the specified server with the library. Information registered includes the class name, the instance, and whether the server supports single or multiple instances.
OleRegisterServerDoc Document creation Registers a document with the server library.
OleRenameServerDoc Document save as Renames the specified document.
OleRevertServerDoc Document reload Reverts a document to a previously saved state without closing the document.
OleSavedServerDoc Document save Informs the library that a document has been saved. Calling this function is equivalent to sending the OLE_SAVED notification.
OleRevokeServer Server shutdown Revokes access to the specified server, closing any documents and terminating communication with client applications. This function may return OLE_WAIT_FOR_RELEASE, requiring the server to wait for OLESVR to call the Release callback function.
OleRevokeServerDoc Document close Revokes access to the specified document. This function may return OLE_WAIT_FOR_RELEASE, requiring the server to wait for OLESVR to call the Release callback function.
OleRevokeObject Object close/
delete
Revokes access to the specified object.
OleBlockServer Any time Queues requests to the server until the server calls the OleUnblockServer function.
OleUnblockServer Any time Processes a request from a queue created by calling the OleBlockServer function.

OLE.H

OLE.H is the standard include file for all OLE applications (clients and servers alike) that defines structures such as OLESERVER, OLESERVERDOC, OLEOBJECT, and OLESERVERVTBL. Before the #include <ole.h> statement in your source files, #define SERVERONLY (or define it on the C compiler command line with DSERVERONLY). This prevents OLE.H from including additional fields used strictly for object handler dynamic link libraries (DLLs)2 in the OLEOBJECTVTBL structure. A server application does not use these fields.

SHELL.DLL, SHELL.LIB, and SHELLAPI.H

SHELL.DLL contains functions to manipulate the registration database.3 SHELL.LIB is the import library to which you link your application. The SHELLAPI.H include file contains prototypes for these functions.

Function Description

RegCloseKey Closes a key with the given handle.
RegCreateKey Creates a key with the given name; generates a key handle.
RegDeleteKey Deletes a key with the given handle and subkey name.
RegEnumKey Enumerates subkeys of a specified key into a string.
RegOpenKey Opens a key with the given name and provides a key handle.
RegQueryValue Retrieves the text string for a specified key.
RegSetValue Sets the text string (value) for a specified key.

An OLE server uses the RegCreateKey, RegSetValue, and RegCloseKey functions to install itself in the system registration database so that client applications are aware that the server is available. If an error occurs, the application can delete a key with RegDeleteKey. RegQueryKey can be used to detect whether the server is already registered. RegOpenKey and RegEnumKey are not used in most OLE server applications.

Library Redistribution and Installation

You may ship various components that your server application uses if you intend to target Windows version 3.0 systems that may not have these libraries installed. Redistribution requires no royalties to Microsoft Corporation but does require that your application version-check each component before copying it to a user’s hard disk drive, possibly replacing an existing version of the library. If the user currently has the same or a newer library installed, do not copy the version shipped with your application. This document does not describe version checking; please consult the Windows version 3.1 Software Development Kit (SDK) Programmer’s Reference manuals for more information on versioning functions.

The obvious library you might ship is OLESVR.DLL. If you ship OLESVR.DLL, you must also ship the matching OLECLI.DLL, to ensure compatibility between the two libraries, and SHELL.DLL because server applications must use the registration database. In addition, if you supply a REG.DAT file for your server (described in later sections), include REGLOAD.EXE to facilitate merging your registration file with the user’s. Finally, to provide version-checking capabilities, ship VER.DLL that contains the versioning functions.

OLE Communication Routes

Communication between a client application, a server application, and the OLE libraries, OLECLI.DLL and OLESVR.DLL, takes place on several different levels (see Figure 1):

The client calls API functions in OLECLI.DLL.

OLECLI.DLL sends notifications to the client through the CallBack function in an OLECLIENTVTBL structure.

The server calls API functions in OLESVR.DLL.

OLESVR.DLL calls the exported server methods to request various actions on the server, document, or object level.

The server sends notifications to the client through the CallBack pointer in the OLECLIENTVTBL structure provided by OLESVR. OLESVR intercepts these calls and may not always pass the notification on to the client.

OLECLI.DLL and OLESVR.DLL communicate through DDE messages.

Figure 1.

An OLE server application in this model calls OLESVR.DLL functions and calls the CallBack function in the client application directly to notify it of changes. For example, when an OLE server opens because of a linked object in a client application, the server must notify the client through CallBack whenever that document or object changes. When the client receives this notification, it requests the updated data from the server by calling a function in OLECLI. OLESVR receives the request from OLECLI and calls methods in the server to retrieve the data. OLESVR sends the data back to OLECLI and eventually back to the client. The server sends notifications to the CallBack function, which resides in OLESVR. OLESVR filters notifications before passing them to OLECLI, which then passes them to the client application.

A server’s methods are specified through the OLESERVERVTBL, OLESERVERDOCVTBL, and OLEOBJECTVTBL structures. These are the only points where OLESVR requests actions in the server.

Note:

Although the OLE version 1.0 server library, OLESVR.DLL, uses DDE commands to communicate with the client library, a server application should not depend on this. Future versions of the OLE libraries may not use the DDE mechanism. The OLE libraries hide this mechanism beneath a set of function calls and allow it to change and improve without requiring changes to the application. Concentrate on the OLE protocol; avoid focusing on DDE.

OLE Data Structures and Application-Specific Variations

Eight data structures defined in OLE.H are of interest to an OLE server application.

Data structure Contents as defined in OLE.H

OLECLIENT A single LPOLECLIENTVTBL
OLECLIENTVTBL A single far pointer, CallBack, to the client’s notification procedure
OLEOBJECT A single LPOLEOBJECTVTBL
OLEOBJECTVTBL Far pointers to object methods
OLESERVER A single LPOLESERVERVTBL
OLESERVERVTBL Far pointers to server methods
OLESERVERDOC A single LPOLESERVERDOCVTBL
OLESERVERDOCVTBL Far pointers to document methods

These data structures are quite limited as defined in OLE.H: Each structure contains a single pointer to a VTBL that contains pointers to various callback functions.

To fully utilize these structures, define application-specific modifications to each structure in your own server, adding any fields that you want to manipulate at the server, document, or object level. The key point to remember is that each structure must always have an LPOLE*VTBL type that stores a pointer to the appropriate VTBL first.

The Schmoo server defines its own modifications to these structures. For example, SCHMOOSERVER contains the LPOLESERVERVTBL field and additional fields that essentially act as global variables. Documents and objects are treated in the same manner; additional fields are added to the structures to act as globals within an object or a document.

Methods

Many OLE server responsibilities involve the methods that handle requests from OLESVR.

OLESERVERVTBL OLESERVERDOCVTBL OLEOBJECTVTBL

Create Close DoVerb
CreateFromTemplate GetObject EnumFormats
Edit Execute GetData
Execute Release QueryProtocol
Exit Save Release
Open SetColorScheme SetBounds
Release SetHostNames SetColorScheme
SetData
SetTargetDevice
Show

Note:

In the remainder of this document, the method names will be prefixed with “Server,” “Doc,” and “Obj” (for example, ServerRelease, DocRelease, ObjRelease) to distinguish server, document, and object methods from one another.

Each method is simply a callback function defined in, and exported from, the server application. The responsibilities of each method are discussed in the “Step-by-Step OLE Server” section.

Relationship between servers, documents, and objects

OLE structures represent a loose hierarchy. The OLESERVER, OLESERVERDOC, and OLEOBJECT structures are independent of one another; that is, an OLESERVER structure does not necessarily contain an OLESERVERDOC structure (although you might want to include it there).

In the most abstract sense, a server is simply the top-level structure of a particular object class, and each server deals with only a single object class. In this sense, a server can contain any number of documents, and each document can contain any number of objects of that class. This relationship is contained in the server and document methods. The server ServerCreate, ServerCreateFromTemplate, and ServerOpen methods allocate and initialize documents. The document DocGetObject method allocates and initializes objects.

Server, Document, and Object are abstract names that roughly correspond to parts of an application:

The server is really the main application window, like the frame windows in the multiple document interface (MDI). A single application can, however, support multiple object classes, in which case it is called a multiple server.4

A server contains any number of documents. A document is generally equivalent to a window containing that document.

A document contains the objects, which may exist as child windows of the document window or may simply be separate editable pieces of the document, such as a range of cells in a larger spreadsheet.

Global variables and OLE data structures

Because OLE servers are required to provide a number of separate callback functions (methods), you will probably maintain global variables that will be visible between methods; these are the only variables that need to be truly global. Other data need only be visible to all server methods, to all document methods, or to all object methods, but not among all three methods. This data is best placed inside an application-defined OLE structure to replace the OLESERVER, OLESERVERDOC, and OLEOBJECT structures that are defined in OLE.H and that contain only a pointer to their VTBL.

You will need a method to track the current document (for MDI servers) and the current object (for example, the object allocated on the last ServerOpen or DocGetObject method).

Use of pointers to OLE structures by OLESVR.DLL

OLE structures are continually referenced by pointers in all server methods and in all OLESVR API calls. Using pointers in methods allows you to define variable-length structures and makes these methods correspond to C++ methods that receive pointers to the object they manipulate. As a direct result of pointer use, OLE does not work in Windows (version 3.0) Real mode. If you have an application that currently operates under Real mode, adding OLE will eliminate that capability.

The use of pointers requires that you allocate memory and keep it locked until freed; this is a Great Evil in Real mode. However, because far pointers in Windows Standard mode and Enhanced mode contain local descriptor table (LDT) selectors instead of physical segment values, memory can move without requiring the selector (or the pointer) to change. Therefore, you can allocate and lock a structure to pass a pointer to OLESVR and leave that memory locked until it must be freed.

Note:

Make the best use of local and global memory for OLE structures:

\sgmlansi149Local Memory (LocalAlloc): Allocate as LMEM_FIXED or LPTR (WINDOWS.H defines LPTR as LMEM_FIXED | LMEM_ZEROINIT). Do not use LMEM_MOVEABLE followed by a LocalLock because it creates a sandbar in the high area of the local heap. Allocating LMEM_FIXED allocates from the bottom of the stack, which is the best place for locked memory to reside.

\sgmlansi149Global Memory (GlobalAlloc): Allocate as GMEM_MOVEABLE followed by a GlobalLock. The largest concern with global memory is how much of it resides in conventional memory below the 1-MB line. Allocating GMEM_FIXED automatically places that memory as low as possible in the global heap, whereas GMEM_MOVEABLE allocates from the top. Because the memory allocated as GMEM_MOVEABLE can physically move after GlobalLock, you create no sandbars.

Miniservers vs. Full Servers

Miniservers are servers used only for embedding objects. They cannot run stand-alone; they cannot save or open files independently; and they have a simplified user interface. Examples of miniservers are Microsoft WordArt (for font effects) and Microsoft Draw (for editing metafiles), both included with products such as Microsoft Word for Windows version 2.0 and Microsoft Publisher version 1.0. Miniservers work best for generating small visual objects (as in WordArt and Draw) or where there is little point in providing the ability to save and open files independently.

The native data generated by a miniserver should be small; this will minimize the effect on a client document containing many embedded objects. For example, a miniserver bitmap editor may consistently generate 50K objects, so a client document containing 20 such bitmaps rapidly exceeds 1 MB. On the other hand, a metafile from Microsoft Draw is relatively small, perhaps 200 bytes for a simple image, because metafiles are descriptions of graphics operations instead of being pixel-by-pixel dumps of graphics images. If your server creates large objects, support linking with a full server so that the user can separate the data into multiple linked files instead of using a single huge file with many embedded objects.

Full servers are applications that can run stand-alone. They can create, open, and save files; they can link or embed objects; and they may be OLE clients themselves. Microsoft Excel is a good example of a full-server application. Because full servers usually have menus, they must undergo minor user interface changes to edit embedded objects.

If you are converting an existing application to become an OLE server, your application most likely reads and writes its own files already. In this case, it should become a full server, because linking support adds little code to the application and requires no user interface changes. If an application supports linking, it must also support embedding—the user should decide which to use.

Single-Document Servers vs. Multiple-Document Servers

A server application can be either a single-document (SDI) server or a multiple-document (MDI) server. When OLESVR initiates object editing, it starts another instance of an SDI server but directs an MDI server to create a new window for editing that object. MDI applications were created to optimize the workspace for a set of documents. As OLE moves away from application-centric computing to document-centric computing, the need for MDI diminishes. Microsoft therefore encourages the use of a single-document interface for OLE servers.

Note:

Be careful not to confuse single-document and multiple-document servers with single-instance and multiple-instance servers. The words “document” and “instance” have nothing to do with each other. A single-document server is not necessarily multiple-instance, and a multiple-document server is not necessarily single-instance. As discussed in later sections, this affects your choice of parameters for OleRegisterServer—the last parameter can be OLE_MULTI or OLE_SINGLE. OLE_MULTI stands for multiple-instance (not MDI); OLE_SINGLE stands for single-instance (not SDI). Be careful not to confuse the two.

Clipboard Formats and Conventions

OLE servers provide data to OLESVR (and indirectly to the client) in standard Clipboard formats:

Native: The server’s raw data structures

OwnerLink: Information about the server, used for embedding an object

ObjectLink: Information about a file the server has saved, used for linking an object

CF_METAFILEPICT: A continuously scalable presentation for the client

CF_BITMAP: A roughly scalable presentation for the client

Native, OwnerLink, and ObjectLink are formats defined in the OLE protocol; all OLE applications must register these formats with RegisterClipboardFormat, which returns the same integer value in all applications.5 These three formats describe a linked or an embedded object within a client document, allowing the OLE libraries to launch the correct server application when the user activates an object in the document. These formats are described in more detail in the next sections.

The CF_METAFILEPICT and CF_BITMAP formats provide the client application with some visual representation of the embedded or linked data; this image is all that the client shows for the object. The server application must provide the CF_METAFILEPICT format at the minimum.

Server applications are responsible for placing all of these data formats on the Clipboard in order of decreasing fidelity; that is, the data containing the most information is placed first. Given the contents of all formats, OLE applications follow a standard ordering:

1.Application-specific data

2.Native

3.OwnerLink

4.CF_METAFILEPICT

5.CF_BITMAP

6.ObjectLink

7.Any other data

Ordering is only important from Native to CF_BITMAP in that Native and OwnerLink must precede ObjectLink, and CF_METAFILEPICT must precede CF_BITMAP.

Note:

Because the Clipboard must be able to access and pass the data to other applications, always allocate the memory using GlobalAlloc with GMEM_DDESHARE | GMEM_MOVEABLE. GMEM_DDESHARE ensures that other applications can use the memory.

Native, OwnerLink, and ObjectLink formats

Format name Contents

Native Application-specific data structure that is understood only by the server application that created it. This format must allow the server to completely recreate the object. OLE libraries and client applications treat native data as a stream of raw bytes.
OwnerLink Describes an embedded object, using a sequence of three null-terminated strings in memory:

String 1: Object class name

String 2: Document name

String 3: Object’s individual name, assigned by the server application

Each string follows the preceding string’s terminating null character, and the sequence is terminated by two NULLs. The object class name is the registered class of objects that the server handles. The document name and object name are used strictly to identify the object within the server or document in which it resides. The current implementation of OLE does not use the OwnerLink document name.


ObjectLink Identical to OwnerLink in OLE version 1.x, except that it describes a linked object:

String 1: Object class name

String 2: Full path to the document file

String 3: Object’s individual name, assigned by the server application

The object class name is the registered class of objects that the server handles. The document name must contain a path to the linked file; this allows OLESVR to pass the file name back to the server when a client edits a linked object. The object name specifies the object in the document to select when the document is loaded.


PREPARING AN APPLICATION TO BECOME AN OLE SERVER

With enough time and effort, any application that produces some data can become an OLE server. Before adding OLE support code, you can make small changes in your existing application to simplify your experience with OLE. The suggested changes below allow you to focus on OLE specifics without becoming distracted by other non-OLE code changes. These suggestions are not required, but they can make OLE easier to implement:

Include version numbers in data structures and in files

Isolate data

Create metafiles and bitmaps

Clean up Clipboard I/O

Isolate utility functions

Include Version Numbers in Data Structures and in Files

Include version numbers in private data structures and in files. With OLE, a client application may embed data from one version of a server and later request a newer version to edit the same data. Without version numbers, the server may malfunction when manipulating an old data structure. If the structures contain version numbers, a newer server can convert the old data to a new structure and an older server can notify the user that a newer version of the application is necessary to edit the data.

You should also consider placing a unique identifier in the data structure that you can publish, so that other applications can provide utilities to convert the data to their own formats.

Isolate Data

Consider reorganizing your existing global and static data to isolate non-OLE data from OLE-specific data that you might add later. OLE is a protocol that sits on top of a server application to allow other applications access to the server’s data; in other words, it is not “integrated” significantly with the application. Future OLE changes are independent of Windows system upgrades, so be prepared to revise your application’s OLE code without changing the remainder of the application.

The following subsections discuss data isolation in more detail. An additional suggestion is to add a global variable such as fOLE to indicate whether the server was started stand-alone or for linking and embedding. In File menu commands such as File Save, you will need to distinguish the two cases to perform slightly different operations.

Window-specific data

OLE is based on a loose object-oriented model in which a server contains documents and documents contain objects. To facilitate isolating object, document, and server data, you can structure your application to have a server window that contains document windows and document windows that contain object windows, where each window maintains a private data structure visible only to that window. This simplifies data management, reduces the number of global variables used to track the number of objects or open files, and may reduce the number of variable-length arrays of data structures.

For each server, document, and object window, register the window class with sizeof(HANDLE) in the cbWndExtra field of WNDCLASS, and define a single data structure containing the data specific to that class. Allocate memory for this structure when the window receives a WM_CREATE message, and free that memory when the window receives a WM_DESTROY message. Store the handle to this memory in the window’s extra bytes. Whenever you enter the window procedure later, you can simply retrieve this handle and gain access to the entire data structure.6 Note, however, that the Schmoo sample server does not use this method.

Application-specific (global) data

Most applications maintain some global (application-visible) data, either for performance reasons or to save development time. Some of this data is manipulated only through WinMain or through the main window procedure, and some data is necessary for all function levels. Separate the data that is manipulated only at the highest level into one data structure, and place the data manipulated at all levels in another structure (or simply leave it alone).

The data that is manipulated only at the high levels can be folded into the application’s OLESERVER structure and shared with the main window procedure and with the OLESERVERVTBL methods. If another module needs that data, simply pass a pointer to this structure. This provides structure to global variables, and referencing the variables off a pointer visibly identifies that data as belonging to the global data block. For example, the Schmoo server has a single global pGlob that points to a data structure containing non-OLE global variables. A global variable appearing in the source code always has “pGlob->” as a prefix, quickly identifying it as global.

Document-specific and object-specific data

Information that is global within documents or document windows can also be isolated into a data structure or attached to each document window. Isolating the data now will make it easier to fold this data into an OLESERVERDOC structure that holds OLE-specific data for each document. If you isolate this data now and always reference it from a pointer, you can change the location of the structure easily by renaming the pointer.

You can isolate object-specific data to be folded into your application-defined OLEOBJECT structure similarly.

Create Metafiles and Bitmaps

OLE requires the server to provide a graphic representation of its native data in a metafile and possibly as a bitmap. Before adding any OLE code, add the capability to generate these images in the form of a metafile handle (HMF) or a bitmap handle (HBITMAP). As discussed later in this guide, isolate the functions used to create these images so that they can be called at any time. You will need these handles when copying or cutting data to the Clipboard, and the OLESVR will call the ObjGetData method in your OLEOBJECTVTBL to request a handle to the same image.7

When you create a metafile, use the MM_ANISOTROPIC mapping mode so that the metafile will scale properly in the Clipboard viewer and in other applications (for example, in OLE clients). If your metafile contains text and you are targeting Windows version 3.1, try to use TrueTypeÒ fonts—they will provide the best presentation no matter how the metafile is scaled.

Clean Up Clipboard I/O

If you are converting your application to operate as a full OLE server, now is a great time to add Clipboard I/O capabilities.8 If you already have some Clipboard interaction, verify that you manipulate your application’s private data, CF_METAFILEPICT, and CF_BITMAP. Later, you will need to add the ability to copy and cut the OLE-specific Native and OwnerLink formats. You will probably use your application’s private data as the Native format.

As you want a single function to generate a metafile image of your server’s private data, include a function to create and return a global memory handle to a METAFILEPICT structure to send to the Clipboard as the CF_METAFILEPICT format. In the METAFILEPICT structure, specify the extents of the metafile in MM_HIMETRIC units; OLE uses these units exclusively to specify dimensions. Note that the metafile must still be drawn in the MM_ANISOTROPIC mode, but with HIMETRIC units.

Isolate Utility Functions

One of the best ways to simplify OLE implementation is to reorganize functions and code fragments in your application to make them callable from OLE methods. If you spend time reorganizing before dealing heavily with OLE, you can focus on the OLE protocol instead of becoming bogged down in moving code and updating include files.

The following sections suggest 13 areas in which you can isolate code:

1.Application installation (first instance only)

2.Application initialization (all instances)

3.Exit and cleanup procedure

4.Background processing schedulers

5.Drawing to an arbitrary DC

6.Clipboard format builders

7.MM_HIMETRIC to MM_TEXT (or other) conversions

8.Setting private data dynamically

9.Manipulating the dirty flag

10.File I/O

11.Changing the window title on File Open and File Save As

12.Window sizing by user or by program

13.DDE Execute string parsing and dispatching (optional)

Application installation (first instance only)

Some applications contain code that executes only if that application instance is the only instance running. For example, the first instance may establish a systemwide network connection that all subsequent instances of that application also use.

During installation, an OLE server may want to verify that it exists in the system registration database and possibly register itself if it does not. This is necessary only for the first instance of the application started on any particular machine. By isolating your existing installation code, you can create a definite place to insert the code dealing with the registration database.

Application initialization (all instances)

OLE affects application startup significantly because the server must handle several responsibilities, such as registering Clipboard formats and parsing the command line, at that time. Isolating existing initialization code creates obvious places to insert this OLE-specific code. If all initialization code resides in one function call that you make from WinMain, that function can simply return FALSE in the case of an error so that WinMain can terminate the application.

One of the complexities of initialization is parsing the command line to detect an embedding flag. To simplify this step, consider creating a function to parse the command line into separate parameters. In the Schmoo sample server, a function called HListParse in INIT.C allocates an array of pointers, parses the command line, and stores a pointer to each parameter in the command line that is separated by white space. The array pointers reference each parameter individually, so you can simply walk through that array to find switches, flags, and file names. The OLE server checks only the first item in the array for the embedding flag, because the flag is either first on the command line or not there at all.

If the flag is found, the nCmdShow parameter passed to WinMain must change. Therefore, you should include a method in your initialization function to return the modified nCmdShow. You can accomplish this by passing a pointer to nCmdShow, so the initialization function can modify it or leave it alone. In this case, WinMain does not have to change because nCmdShow is modified behind the scenes.

Exit and cleanup procedure

Applications generally include code to clean up items such as allocations and graphics device interface (GDI) objects that were created in the application initialization code. Isolate the exit procedure to create a place for OLE-specific cleanup tasks in the same way that you separate initialization code.

Background processing schedulers

Some applications that perform background processing modify the message loop in WinMain to detect when there are no messages to process (by using PeekMessage) and then perform a step from a background process before checking for messages again. OLE requires a server to enter a separate message loop when certain operations require the server to wait until an object, a document, or the server is released. The message loop is necessary for processing DDE messages between OLESVR and OLECLI while synchronizing a sequence of calls in the server.

Like any other message loop, this OLE wait loop may have idle time during which the application can perform some background task. If you isolate the code for performing a step of this task, you can call the code from any message loop in the application with the same results.

Drawing to an arbitrary DC

An OLE server may be required to produce an image on the screen (for its own display), to create a metafile or a bitmap (for providing an image to a client application), or to send an image to a printer at any time. Simplify this requirement by modifying the drawing code your application uses for its objects to work with an arbitrary device context (DC), whether it is a screen DC, a metafile DC, a memory DC containing a bitmap, or a printer DC.

For example, the Schmoo server creates a window called Polyline that edits the object. The Polyline window’s paint routine normally draws the image to the screen when the window receives a WM_PAINT message. If we enable the routine to draw to any DC, it can be called through a repaint request (WM_PAINT), through a redraw request (WM_LBUTTONDOWN), or from code used to generate a bitmap or a metafile.

Clipboard format builders

An OLE server may be asked at any time (through the ObjGetData method) to provide a handle to data in any single Clipboard format, including Native, OwnerLink, CF_METAFILEPICT, and CF_BITMAP. To simplify your ObjGetData method later, create functions like HGetMetafilePict and HGetBitmap (as in Schmoo’s CLIP.C) that return handles for existing formats (prior to OLE) that are readily usable for Clipboard I/O or for handling the ObjGetData method.

MM_HIMETRIC to MM_TEXT (or other) conversions

OLE expresses rectangles, including those provided in the DocSetDocDimensions method and in metafiles that the server copies to the Clipboard, and other dimensional quantities in MM_HIMETRIC units. If your application does not deal in MM_HIMETRIC already, create two functions that convert MM_HIMETRIC to and from the mapping mode (such as MM_TEXT) that you normally use. The application can then continue to deal in its usual mapping mode and need only convert units when exchanging dimensions of an object with OLESVR.

The Schmoo server contains two functions in MISC.C to convert rectangles between MM_HIMETRIC and MM_TEXT units. RectConvertToHiMetric converts from MM_TEXT to MM_HIMETRIC (using DPtoLP), and RectDeviceConvert handles the opposite conversion (using LPtoDP). These functions are readily usable in your application.

Setting private data dynamically

Applications that cut or copy private data to the Clipboard are also capable of pasting that data back. However, the code to perform this paste may be buried inside a WM_COMMAND processing switch. OLESVR uses the ObjSetData method to perform the same operation as paste. For this reason, you should isolate the code your application uses for adding a private data structure to the current document. When you receive the paste request, get the data from the Clipboard and pass it to this function. When the SetData method receives a handle, you can again simply pass the handle to this function, making SetData trivial.

Manipulating the dirty flag

Several different operations may cause changes in the server document that the user is editing. Your application may currently set a global flag in all of these cases. Isolate the code to change that flag into a separate function, and call that function from each case.

When you add OLE linking support, you must notify the client application that the data has changed. Creating a separate function provides a single place to add the client notification call and eliminates the need to find and modify (and possibly miss) all the cases in which you change the dirty flag.

File I/O

OLE affects most File menu commands and may require loading or saving a file outside the context of those commands. Therefore, isolate the functions used for reading and writing data structures to a file. For example, the server CreateFromTemplate method requires that you open and read the contents of a file to use as the initial contents of a new document in the server. You may have functions that create a new document from a file already, but CreateFromTemplate requires that you use the file for initial data only and that you do not keep the file open.

You should also create a single function that prompts the user to save changes in a document before creating a new document, opening a new file, or exiting the application. Normally, this code displays a message box with Yes, No, and Cancel options. Depending on the user’s choice, the function either saves the file, continues with the new, open, or exit operation, or cancels the operation. When an OLE server is started to edit an embedded object, the text in this message box changes. Centralizing the code now simplifies the changes required for OLE.

Changing the window title on File Open and File Save As

Most applications that load files display the loaded file name with the application name in the title bar of the application. When a user opens a different file or renames the current file with Save As, the file name in the title bar generally changes.

In addition to these file-name changes, the user interface for OLE servers requires a different string in the title bar if the server is editing an embedded object. Isolate the code for manipulating the title bar by creating a function that takes the main window handle, a pointer to a string containing the document name (or file name), and a pointer to a string containing an object name. In a stand-alone (or an OLE linking) operation, pass a NULL as the object name and have this function create the string:

<Application Name> — <File Name>

If the function receives a non-NULL object name, use it to indicate that the server is editing an embedded object, and change the title bar to the string:

<Application Name> — <Object Name> in <File Name>

OLESVR provides the object name and the file name for embedded objects through the DocSetHostNames method.

Window sizing by user or by program

The Schmoo server application resizes the document window and scales its image when the main window changes in size. Resizing can occur either through user action (when the window receives a WM_SIZE message) or through a call to SetWindowPos. If your application handles only the user action case, OLE will change that.

The DocSetDocDimensions method simply instructs a document to resize to a given rectangle. Schmoo resizes several windows to fit this new document size. If your existing code for WM_SIZE resizes other windows based on a document size, isolate that code into a separate function that you can call from DocSetDocDimensions.

Note:

If your application saves the dimensions of a document or an object in a file, window sizing should make the document or object “dirty.” The dirty flag that you currently use for file save prompts is also used with OLE to prompt the user to update the object in a client document. Resizing the object in the server should be reflected in the client document; therefore, users who resize an object must be prompted to update the object if they try to close the server when the object is dirty.

DDE Execute string parsing and dispatching (optional)

If your application currently supports DDE Execute commands and your OLE server supports the StdExecute protocol, you will receive DDE Execute strings not only through DDE messages but also through the ServerExecute and DocExecute methods when those commands come through OLESVR. Isolate any existing code for parsing an Execute string so that you can call it when your application receives a WM_DDE_EXECUTE message or from either Execute method. You can also isolate any code that actually executes the commands after parsing, so you can call it from these separate locations.

HINTS FOR DEBUGGING AN OLE SERVER

Debugging is a major challenge for developers of OLE servers. The following sections provide suggestions to make debugging easier.

Start the Server in the Debugger

One of the greatest difficulties in debugging is breaking execution when OLESVR launches the server application. Two ways to deal with this are:

If your debugger supports debugging multiple instances of the same application, start one instance of the server as stand-alone in the debugger. Set a breakpoint on WinMain, and let that instance run. When OLESVR launches another instance, you will break on WinMain and be able to debug this new instance from there. The disadvantage is that you must watch which instance you are running at the time.

Microsoft CodeViewÒ for Windows allows you to debug two applications by loading one as an application and specifying the other as a DLL. To debug an OLE server, use a suitable client (preferably a sample client with sources) as the main application, and specify your server as a DLL.

When CodeView starts, it loads the symbols for both applications, letting you set breakpoints anywhere in the server application, even if it has not yet started. When OLESVR launches your application, you can break on entry to WinMain to debug installation code and initialization functions. When your server terminates, you are still running the debugger on the client, so you can debug the server again, from the start, by relaunching it. There is no need to watch which instance of the server is running unless you want to debug multiple instances.

Set Breakpoints on Entry to All Methods

To learn what is happening with your methods, set a breakpoint at the beginning of each method. Any time an OLE operation is carried out, you can debug each method as it is used. You will also see the sequence of calls to your methods and gain an understanding of the calling structure and order.

STEP-BY-STEP OLE SERVER

This section takes you through the code additions and changes that will create an OLE server from an existing application. The discussion assumes that the application can operate stand-alone, can independently load, modify, and save documents in files, and is not based on the MDI model; special notes about MDI are given when necessary.

The incremental approach provides checkpoints at which you can compile and test your code; each checkpoint is marked by a gear symbol:

Your server may not be fully functional at these points, but you can ensure that certain elements work perfectly. This incremental testing approach will make your life with OLE simpler. Some sections reference parts of the Schmoo server code to demonstrate how you can implement the functionality described. Some of the Schmoo code is designed to be reusable in your application with a small amount of overhead.

The implementation consists of the following steps:

Step Description

1.Define OLE data structures Modifying your include files to contain the necessary OLE structures.
2.Install the server in the registration
database
Registering the server with the system registration database as a server for a particular object.
3.Place data on the Clipboard Clearing existing data, retrieving handles to all necessary formats (Native, OwnerLink, ObjectLink, metafile, and bitmap), and placing those formats on the Clipboard in the right order.
4.Implement skeleton (stub) methods Creating the necessary source files and function stubs for OLE server methods.
5.Initialize the application and VTBLs Registering OLE Clipboard formats, allocating an OLESERVER structure, initializing VTBLs, registering the server with OLESVR, parsing the command line, and creating an initial document.
6.User interface: Change window
titles and menus
Handling simple user interface requirements.
7.Implement basic methods Making the server capable of embedding. (This is the longest section to work through.)
8.Handle simple shutdown Revoking the server and waiting for release.
9.File menu commands: New, Open,
Import, Save As, and Save/Update
Allocating, initializing, and registering new documents, notifying the client of changes, and handling a switch from embedding mode to stand-alone mode. Notifying the client and renaming documents.
10.Close objects, documents, and the
server
Updating the object if necessary, informing OLESVR that the document is closing, revoking the server and documents, and freeing the VTBLs and other structures.
11.Implement optional methods and
OLE functions
Implementing optional methods and blocking OLESVR requests.

Step 1. Define OLE Data Structures

Pointers to the OLESERVER, OLESERVERDOC, and OLEOBJECT structures pass information about the server, about documents, and about objects between the server and OLESVR. The definitions of these structures in OLE.H include only a single pointer to a method callback table:

typedef struct _OLESERVER

{

LPOLESERVERVTBL lpvtbl;

} OLESERVER;

typedef struct _OLESERVERDOC

{

LPOLESERVERDOCVTBL lpvtbl;

} OLESERVERDOC;

typedef struct _OLEOBJECT

{

LPOLEOBJECTVTBL lpvtbl;

} OLEOBJECT;

To effectively use these structures, define your own structures, keeping the lpvtbl field first and adding any other data. Name these structures as you want; Schmoo names them SCHMOOOBJECT, SCHMOODOC, and SCHMOOSERVER:

typedef struct

{

LPOLESERVERVTBL pvtbl; // standard

LHSERVER lh; // required by OleRegisterServer

BOOL fRelease; // flag to watch if we need to wait

BOOL fEmbed; // TRUE if we're embedding only

BOOL fLink; // TRUE if we're linking only

WORD wCmdShow; // OLE-modified window show command

HWND hWnd; // main application window

HANDLE hMem; // memory handle to this structure

LPSCHMOODOC pDoc; // last document allocated

} SCHMOOSERVER;

typedef struct

{

LPOLESERVERDOCVTBL pvtbl; // standard

LHSERVERDOC lh; // required by OleRegisterServerDoc

BOOL fRelease; // flag to watch if we need to wait

ATOM aObject; // name of the object

ATOM aClient; // name of the connected client

HANDLE hMem; // memory handle to this structure

LPSCHMOOOBJECT pObj; // last object allocated

} SCHMOODOC;

typedef struct

{

LPOLEOBJECTVTBL pvtbl; // standard

BOOL fRelease; // flag to watch if we need to wait

LPOLECLIENT pClient; // necessary for notifications

HANDLE hMem; // memory handle to this structure

} SCHMOOOBJECT;

Each structure contains an fRelease flag that the server watches when a Revoke function returns OLE_WAIT_FOR_RELEASE. If we call OleRevokeServer, OleRevokeServerDoc, or OleRevokeObject, we must watch the fRelease flag in SCHMOOSERVER, SCHMOODOC, and SCHMOOOBJECT respectively. See the “Step 10. Close Objects, Documents, and the Server” section for more information.

Each structure uses atoms to store strings—for example, document or client names. Atoms provide a convenient storage method for strings without requiring you to make assumptions about string length. To use the string, you have to allocate string space and call GetAtomName, but that has little impact on where an OLE server actually uses the strings.

Finally, the lh fields in the SCHMOOSERVER and SCHMOODOC structures are handles generated by OleRegisterServer and OleRegisterServerDoc that are required in other OLE calls. Storing a handle in these structures associates that handle with the structure, so whenever you have a structure pointer, you have the correct handle for that item.

Schmoo also defines a few additional types (not included in OLE.H) to eliminate the use of ugly typecasting (such as LPOLEOBJECT FAR *). The first three types are used in various methods. LPVOIDPROC is the type of the ObjQueryProtocol function and is defined with a typedef to make other code cleaner:

typedef LPOLESERVER FAR *LPLPOLESERVER;

typedef LPOLESERVERDOC FAR *LPLPOLESERVERDOC;

typedef LPOLEOBJECT FAR *LPLPOLEOBJECT;

typedef LPVOID (FAR PASCAL *LPVOIDPROC) (LPOLEOBJECT, LPSTR);

Step 2. Install the Server in the Registration Database

An OLE server must register itself with the system registration database to specify the objects it can edit. The database contains key/value pairs consisting of simple strings. OLE does not use the registration database exclusively; this database also stores associations between file extensions and executables.

The next section describes how a server registers itself as the server for a single object and how a single application can be the server for multiple objects. The application must repeat the registration process for each object by calling functions in SHELL.DLL (see the “OLE Technical Background” section).

An alternate method for registering the server is to ship your application with a APPNAME.DAT file and the REGLOAD.EXE utility. To generate the APPNAME.DAT file, start the REGEDIT.EXE application shipped with Windows version 3.1 with –v on the command line to enable its editing mode. When you install your application, use REGLOAD.EXE to merge this APPNAME.DAT with the user’s REG.DAT automatically, or instruct the user to merge the file with REGEDIT.

OLE keys and values

Because all objects are members of a particular “class,” all OLE-related keys start from a root key called HKEY_CLASSES_ROOT. The first subkeys from HKEY_CLASSES_ROOT are the object’s class name and the application’s file extension. Both of these subkeys must have values.

Key name Required value Example

HKEY_CLASSES_ROOT\classname Readable version of class name Schmoo Server 1.0
HKEY_CLASSES_ROOT\.ext Associated class name for a file extension .MOO

The HKEY_CLASSES_ROOT\classname key has two standard extensions:

HKEY_CLASSES_ROOT\classname\protocol\StdFileEditing

HKEY_CLASSES_ROOT\classname\protocol\StdExecute

Additional subkeys attached to \protocol\StdFileEditing describe specific characteristics of the OLE protocol supported by the server.

Key name ...\StdFileEditing\ Value Example

server Full path to server executable e:\win31\schmoo\schmoo.exe
handler Full path to object handler DLL (optional) e:\win31\schmoo\schmooh.dll
verb\0 Primary verb in mixed case Edit
verb\n Secondary verb, tertiary verb, and so on (optional) Open
SetDataFormats Comma-separated value (CSV) string of data formats (optional) Native, CF_METAFILEPICT
RequestDataFormats CSV string of data formats (optional) Native, CF_METAFILEPICT

Be sure to include the full path to the server executable and to the object handler in the server and handler subkey values. If you do not include the path, your application must reside in the PATH statement or OLE will force users to provide that path every time they edit an object—a major headache. In addition, the verbs you register must be sequential, starting at 0 and increasing by one for each verb.

Verbs are the types of actions a user can perform on an object, such as Play, Edit, and Open. For most graphical applications, such as Microsoft PaintbrushÔ or Schmoo, Edit is the only verb provided, because editing is the only thing to do with the data. An application like the Microsoft Windows Recorder (version 3.1) supports two verbs, Play and Edit, where Play is the “primary” verb and Edit is the “secondary” verb. When a user double-clicks an object in a client document, the OLE libraries call the primary verb for that object; for Microsoft Windows Recorder this means “play the sound.” All other verbs are accessed through the client application’s Links command. Open is a verb that can mean either Play or Edit, depending on your application.

The \protocol\StdExecute\server is an optional key that contains the value of the application path, exactly like the server subkey in StdFileEditing. Windows uses this entry to find the server if another application attempts to send commands through the OleExecute function in the OLECLI.DLL library. A server need not support StdExecute.

Note:

A server may want to support the OleSetData and OleRequestData calls available to client applications from OLECLI.DLL. These functions let the client set or retrieve the data for an object if the client understands the object’s native format. The value strings for the SetDataFormats and RequestDataFormats keys consist of a CSV list of Clipboard formats such as “Native, CF_METAFILEPICT” or “CF_DIB, CF_BITMAP, CF_TEXT”. Although SetDataFormats and RequestDataFormats are optional in the StdFileEditing protocol, the object SetData and GetData methods, which are unrelated, are absolutely necessary.

Example: FOLEServerInstall and FKeyCreate

The Schmoo application uses the FOLEServerInstall function (in OLEINST.C) to create the necessary keys in the registration database. FOLEServerInstall takes a single pointer to a structure that contains the strings for building the keys and additional information. OLEINST.H defines this structure as follows:

typedef struct

{

LPSTR pszServerName; // full server name

LPSTR pszServerClass; // short server class name

LPSTR pszServerPath; // full path to server module

LPSTR pszHandlerPath; // optional full path to object

// handler DLL

LPSTR pszExt; // file extension for the server

LPSTR *ppszVerbs; // pointer to array of LPSTRs to

// verbs

WORD cVerbs; // number of verbs in array

LPSTR pszSetFormats; // optional CSV list of accepted

// formats

LPSTR pszRequestFormats; // optional CSV list of requestable

// formats

BOOL fExecute; // is OleExecute supported?

} REGINSTALL;.

On entry, FOLEServerInstall checks to see whether the server is already registered in the database, using RegQueryValue:

#include <shellapi.h>

...

BOOL FAR PASCAL FOLEServerInstall(LPREGINSTALL lpRI)

{

LONG lRet;

...

// Check whether this server is already around.

lRet=RegQueryValue(HKEY_CLASSES_ROOT, lpRI->pszServerClass,

szKey, &dw);

if (ERROR_SUCCESS==lRet)

return TRUE;

...

In RegQueryValue:

HKEY_CLASSES_ROOT specifies the root key.

lpRI->pszServerClass is the class name used in all the subkeys.

szKey is a dummy buffer that receives the value of this key.

dw is a DWORD that contains the length of the szKey buffer.

If RegQueryValue returns ERROR_SUCCESS, a value already exists for the class name key and additional keys need not be created.9

If the server is not already registered, FOLEServerInstall creates keys for all desired values. For each key, it calls FKeyCreate with a key name, a subkey name, and a value for the key. FKeyCreate handles the sequence of RegCreateKey, RegSetValue, and RegCloseKey:

BOOL PASCAL FKeyCreate(LPSTR pszKey, LPSTR pszSubKey, LPSTR pszValue)

{

char szKey[128];

HKEY hKey;

WORD cch;

LONG lRet;

cch=lstrlen(pszValue)+1;

lstrcpy(szKey, pszKey);

lstrcat(szKey, pszSubKey);

lRet=RegCreateKey(HKEY_CLASSES_ROOT, szKey, &hKey);

if (lRet!=ERROR_SUCCESS)

return FALSE;

lRet=RegSetValue(HKEY_CLASSES_ROOT, szKey, REG_SZ, pszValue, cch);

if (lRet!=ERROR_SUCCESS)

{

// Delete key if we could not set a value.

RegDeleteKey(hKey, pszSubKey);

return FALSE;

}

lRet=RegCloseKey(hKey);

if (lRet!=ERROR_SUCCESS)

return FALSE;

return TRUE;

}

FKeyCreate appends the pszSubKey subkey to the basic key in pszKey. pszSubKey is passed separately to use RegDeleteKey if necessary. FKeyCreate uses RegDeleteKey only if RegCreateKey succeeded but RegSetValue failed.

Your application can reuse both FOLEServerInstall and FKeyCreate. Schmoo fills a REGINSTALL structure and calls FOLEServerInstall (in INIT.C). It sets fExecute to FALSE, indicating that the server does not support StdExecute. Also note that ppszVerbs must point to an array of LPSTR values, where each value points to an actual string containing a verb. Like most other servers with graphical data, Schmoo supports only one verb, Edit. However, FOLEServerInstall can handle multiple verbs.

OLEINST.H contains the necessary structure definitions and function prototypes to use these functions. OLEINST.H is the only include file independent of OLEGLOBL.H because installation code may reside in a separate installation program.

Compile and test your code

Verify the registration with REGEDIT

After running your server with the registration code, start the Windows SDK Registration Database Editor (REGEDIT) application to verify that your server was properly registered. To see all information and edit the database, start REGEDIT with v on the command line. In Figure 2, the Registration Database Editor shows that Schmoo successfully added the key Schmoo1.0 with the value “Schmoo 1.0 Figure” and that subkeys of \protocol\StdFileEditing\verb\0 and \protocol\StdFileEditing\server also exist. Schmoo also successfully added the key HKEY_CLASSES_ROOT\.MOO. If your code produced the wrong results, just delete the entries and try again.

Figure 2.

The registration editor lets you delete all key/value pairs from the database; if your code produces the wrong results, simply delete the entries and try again.

Step 3. Place Data on the Clipboard

Setting Clipboard data is a simple matter of creating data structures and passing handles to the Clipboard. However, it is a necessary step that allows the user to run the server stand-alone and to copy data to the Clipboard for linking or embedding into another application. If you have already created functions to retrieve the handles for the individual Clipboard formats, adding OLE Clipboard support involves four easy steps:

1.Call OpenClipboard.

2.Call EmptyClipboard to clear out existing data.

3.For each data format, retrieve a handle to the data and call SetClipboardData, observing the standard order of formats:

Any private data supported by the application.

Native

OwnerLink

CF_METAFILEPICT

CF_BITMAP

ObjectLink, if the server has a known file name and the data is being copied, not cut. Linking to non-existent data is esoteric at best.

Any other standard Clipboard format supported by the application.

4.Call CloseClipboard.

A server need not know how to paste the Native, OwnerLink, or ObjectLink formats.

Example: FEditCopy, FOLECopyNative, FOLECopyLink, and HLinkConstruct

Schmoo sends the basic non-OLE formats to the Clipboard using the FEditCopy function in CLIP.C. (FEditCut simply calls FEditCopy and deletes the current data.) Prior to OLE implementation, Schmoo simply sets data for its private format (a format registered as “Schmoo1.0”), for CF_METAFILEPICT, and for CF_BITMAP.

To support OLE, FOLECopy calls FOLECopyNative and FOLECopyLink (used for both OwnerLink and ObjectLink) in the correct order. Even if two formats have the same data (as private and native data have), you should send a different handle for each so that the Clipboard can manage each format separately.

The HLinkConstruct function (in OLECLIP.C) is reusable verbatim in your application. It simply takes three strings, allocates memory for the three strings and for an extra null terminator, copies the three strings into that memory, and returns a memory handle that can be sent to the Clipboard immediately.

Compile and test your code

Verify correct data placement

After adding basic OLE Clipboard support, run your server and copy or cut data to verify that the proper Clipboard formats are appearing on the Clipboard in the correct order, depending on whether you saved a file. The Microsoft Windows Clipboard Viewer’s Display menu lists the data currently held on the Clipboard.

No file saved File saved

<Owner Display> <Owner Display>
<Private Data> <Private Data>
Native Native
OwnerLink OwnerLink
Picture Picture
Bitmap Bitmap
ObjectLink

At this time, you should also verify that the metafile you place on the Clipboard is scalable by viewing the CF_METAFILEPICT format (Picture) and resizing the Clipboard Viewer window. The image should scale smoothly to any rectangle without distortion, unlike a bitmap munged with StretchBlt.

To verify that the OwnerLink and ObjectLink data are correct, you will have to step through your code in a debugger and see exactly what string exists in that memory before the call to SetClipboardData.

You can now open an OLE client application and paste the object into a document. The client application will simply retrieve the Native and OwnerLink data (if available) and a presentation format such as a picture or a bitmap to display that image in the client document. However, before you can double-click to edit the object, more work must be done.

Step 4. Implement Skeleton (Stub) Methods

In the next section, “Step 5. Initialize the Application and VTBLs,” you will add code to call MakeProcInstance for each server, document, and object method. To do that, you need some methods; for example, you can use stub methods.

If you want the server to support multiple object classes, each class needs its own set of methods on the server, document, and object level, so make copies of these source files as necessary. Some of these methods might contain the same code, in which case you can use the same function. When you initialize the VTBL for that object class, you can simply use the same function pointer across classes.

In all cases, these stubs return an appropriate OLE_ERROR_* value depending on the method. For information on the parameters and return values of all methods, consult the Microsoft Windows Software Development Kit (SDK) Programmer’s Reference manuals for Windows version 3.1.10

Export the methods

Let’s not forget to export a function to pass to MakeProcInstance. All OLE methods are passed to MakeProcInstance; you must list these in the EXPORTS section of your DEF file so that OLESVR can call them. Exported methods need not be listed in any particular order in the DEF file, nor do they require specific ordinal numbers or standard names.

Compile and test for errors

Compile here to weed out any syntax errors that may have crept in and to ensure that the source files are compiled and linked flawlessly. You should also do a short run of the application to ensure that the code addition did not cause any strange segmentation errors. Nothing else is left to test.

Step 5. Initialize the Application and VTBLs

This section describes what an OLE server must do during the initialization of the application instance, in addition to its standard operations. Instance initialization takes place before the application has created its main window and entered the message processing loop. If an error occurs during this phase, terminate the application and notify the user if necessary.

The following initialization steps are specific to OLE:

1.Register Clipboard formats for Native, OwnerLink, and ObjectLink types.

2.Initialize VTBLs containing thunks for the server, document, and object methods.

3.Allocate and initialize your application-specific OLESERVER structure, and register the server application with OleRegisterServer.

4.Parse the application command line to determine whether the application is starting for a linked or an embedded object.

5.Allocate and initialize your application’s OLESERVERDOC structure, and register the initial document with OleRegisterServerDoc.

Register clipboard formats

Regardless of whether the server actually does Clipboard I/O, you need Clipboard formats for Native, OwnerLink, and ObjectLink. Store these registered formats in globally visible variables because the application uses them for Clipboard I/O and inside object methods. The object GetData, SetData, and EnumFormats methods use these formats outside the Clipboard context to exchange data with OLESVR. Register the three standard formats with RegisterClipboardFormat:11

pOLE->cfNative =RegisterClipboardFormat("Native");

pOLE->cfOwnerLink =RegisterClipboardFormat("OwnerLink");

pOLE->cfObjectLink=RegisterClipboardFormat("ObjectLink");

if (0==pOLE->cfNative || 0==pOLE->cfOwnerLink || 0==pOLE->cfObjectLink)

return FALSE;

If either call to RegisterClipboardFormat for Native and OwnerLink fails, server initialization will fail. If registering ObjectLink fails, this server cannot do linking. Decide whether this will be a fatal error or will simply disable linking, in which case the server allows only embedding. The Schmoo server terminates if any of the three calls fail.

Initialize VTBLs and VTBL pointers

Before the server application calls an OLESVR function, it must initialize the method callback tables (VTBLs) with the MakeProcInstance function and set each field in the OLESERVERVTBL, OLESERVERDOCVTBL, and OLEOBJECTVTBL structures. You must allocate these structures because the OLESERVER, OLESERVERDOC, and OLEOBJECT structures (or your variants) simply contain a pointer to the VTBLs. If you have already implemented stubbed methods, as described in the previous section, you should have already exported them in your DEF file.

For a single-document server application such as Schmoo, initialize the server, document, and object VTBLs during application initialization. Although document and object VTBLs can be initialized dynamically, this can be cumbersome and inefficient. At the minimum, however, you must initialize the OLESERVERVTBL, the pointer to which is stored in the OLESERVER structure (or in your variant structure):

//

// pvt is of type LPOLESERVERVTBL, hInst is the application

// instance handle, and the Server* functions are the names

// of the server methods.

//

pvt->Create =MakeProcInstance(ServerCreate, hInst);

pvt->CreateFromTemplate=MakeProcInstance(ServerCreateFromTemplate, hInst);

pvt->Edit =MakeProcInstance(ServerEdit, hInst);

pvt->Execute =MakeProcInstance(ServerExecute, hInst);

pvt->Exit =MakeProcInstance(ServerExit, hInst);

pvt->Open =MakeProcInstance(ServerOpen, hInst);

pvt->Release =MakeProcInstance(ServerRelease, hInst);

The OLESERVERDOCVTBL and OLEOBJECTVTBL structures are initialized in the same manner:

pvt->Close =MakeProcInstance(DocClose, hInst);

pvt->GetObject =MakeProcInstance(DocGetObject, hInst);

pvt->Execute =MakeProcInstance(DocExecute, hInst);

pvt->Release =MakeProcInstance(DocRelease, hInst);

pvt->Save =MakeProcInstance(DocSave, hInst);

pvt->SetColorScheme =MakeProcInstance(DocSetColorScheme, hInst);

pvt->SetDocDimensions=MakeProcInstance(DocSetDocDimensions, hInst);

pvt->SetHostNames =MakeProcInstance(DocSetHostNames, hInst);

Local variables are used to eliminate ugly typecasting in the ObjQueryProtocol method. The ObjQueryProtocol method returns something other than an integer type (LPVOID), where the OLEOBJECTVTBL field is not compatible with the standard FARPROC returned from MakeProcInstance. The following code from FOLEVtblInitObject shows the use of these local variables:

FARPROC lpfn;

LPVOIDPROC lpvp;

//

// Local variables are used here simply to make this assignment

// more readable because it requires some typecasting to compile

// clean at warning level 3.

//

lpfn=(FARPROC)ObjQueryProtocol;

lpvp=(LPVOIDPROC)MakeProcInstance(lpfn, hInst);

pvt->QueryProtocol = lpvp;

pvt->DoVerb =MakeProcInstance(ObjDoVerb, hInst);

pvt->EnumFormats =MakeProcInstance(ObjEnumFormats, hInst);

pvt->GetData =MakeProcInstance(ObjGetData, hInst);

pvt->Release =MakeProcInstance(ObjRelease, hInst);

pvt->SetBounds =MakeProcInstance(ObjSetBounds, hInst);

pvt->SetColorScheme =MakeProcInstance(ObjSetColorScheme, hInst);

pvt->SetData =MakeProcInstance(ObjSetData, hInst);

pvt->SetTargetDevice=MakeProcInstance(ObjSetTargetDevice, hInst);

pvt->Show =MakeProcInstance(ObjShow, hInst);

If a MakeProcInstance call for any VTBL fails, you must stop the initialization and terminate the application. An OLE server cannot function if OLESVR does not have the ability to call these methods. If you terminate the application on a failed MakeProcInstance, you can call FreeProcInstance for any instance thunk you created, although all thunks are automatically deleted when the application terminates.

Allocate OLESERVER and register the server

After you initialize the OLESERVERVTBL structure, allocate your OLESERVER structure and initialize it as necessary. Be sure to set LPOLESERVERVTBL to point to the VTBL you just initialized. Then register the server application with OLESVR by calling OleRegisterServer:

os=OleRegisterServer(pszClass, (LPOLESERVER)pSvr, &pSvr->lh,

hInstance, OLE_SERVER_MULTI);

where:

pszClass points to the name of the object class that this server supports.

pSvr points to the relevant OLESERVER structure with an initialized VTBL.

&pSvr->lh points to the LHSERVER associated with the server. In this example, the handle is stored in the application-specific OLESERVER structure.

hInstance is the instance handle for the application.

OLE_SERVER_MULTI indicates that this server supports multiple instances.

Warning:

Do not confuse OLE_SERVER_MULTI with MDI. MDI applications specify OLE_SERVER_SINGLE; SDI applications specify OLE_SERVER_MULTI. If you specify OLE_SERVER_SINGLE, OLESVR will direct the existing instance to open a new document instead of launching a new instance of the application.

An OLE server application must unconditionally call OleRegisterServer. If OleRegisterServer returns OLE_OK, the LHANDLE to which the third parameter points contains a crucial handle that you must pass to future OLESVR calls, such as OleRegisterServerDoc (see the “Allocate and register the initial document(s)” section). Be sure to store this handle in a globally accessible place where it will be associated with the OLESERVER structure. Storing it directly in the application’s OLESERVER meets both needs.

Note:

If an OLE server application is also a DDE server, the class name passed to OleRegisterServer must not be identical to the application’s module (executable) name. This is because OLESVR communicates with the server through DDE and attempts to initiate a DDE conversation with the server using the name given in OleRegisterServer. If the application uses that name for its own DDE, OLE and non-OLE conversations will overlap, causing everything to malfunction.

If OleRegisterServer does not return OLE_OK, terminate the application. Treat this condition as if you could not register the main window class or as any other error that prevents the application from running.

The class name passed in OleRegisterServer must match the class name set in the registration database for the object class. The OLE libraries use this name to find your application if it’s already running when the user edits another object of that class.

Finally, note that you must eventually free the OLESERVER structure; this is best accomplished during application shutdown after exiting the main message loop.

Parse the command line and determine the initial window state

In addition to any command-line parsing you already handle, check for an embedding flag, which may appear (case-insensitive) as –Embedding or /Embedding, possibly with a file name:

If the embedding flag does not appear, the server is started as stand-alone.

If –Embedding or /Embedding appears with no file name, the server is started for embedding.

If –Embedding or /Embedding appears with a single file name, the server is started for linking. In this case, the command line will not include any other arguments or parameters.

Note that the word Embedding is not localized in international versions of Windows.

If the embedding flag does not appear on the command line, OLESVR did not start the application. In this case, continue your initialization as usual, but also register the initial document (see the next section).

If the embedding flag appears, either alone or with a file name, do not display any application window—return a flag or a value to WinMain indicating that the initial parameter to ShowWindow is SW_HIDE instead of WinMain’s nCmdShow parameter. When OLESVR requires the server to become visible, it will call the ObjShow or ObjDoVerb methods.

When the embedding flag appears alone, the server has been started in an embedding case where no file name is known. When the embedding flag appears with a file name, the server has been started in a linking case, and the file name references the linked file. When a server starts for linking, it must also register a document.

Allocate and register the initial document(s)

Allocate and initialize your OLESERVERDOC structure, making sure to fill the LPOLESERVERDOCVTBL field with a pointer to the VTBL you created. Then, if the server starts as stand-alone (no embedding flag) or for linking (the embedding flag appears with a file name on the command line), call OleRegisterServerDoc to inform OLESVR that this instance of the server has an open document. If OLESVR is not aware that a document exists, it may begin a conversation with an existing instance of the application and may call the ObjSetData method with unpredictable results.

If the command line contains a file name, with or without the embedding flag, pass the file name to OleRegisterServerDoc:

// Register the file given on the command line.

os=OleRegisterServerDoc(pSvr->lh, pszFile, (LPOLESERVERDOC)pDoc,

&pDoc->lh)

or use a string such as “(Untitled)” to register a new, unnamed document:

// Register a new untitled document.

os=OleRegisterServerDoc(pSvr->lh, "(Untitled)", (LPOLESERVERDOC)pDoc,

&pDoc->lh);

where:

pSvr->lh contains the LHSERVER generated in OleRegisterServer.

pszFile points to a null-terminated string with the document name.

pDoc points to an OLESERVERDOC structure with an initialized LPOLESERVERDOCVTBL.

&pDoc->lh points to the LHSERVERDOC associated with the document. In this case, the handle is stored in the application-specific OLESERVERDOC structure.

In either case, if OleRegisterServerDoc returns OLE_OK, continue your initialization; otherwise, call OleRevokeServer immediately and fail the initialization because you cannot do OLE without a document. In this case, OleRevokeServer will always return OLE_OK because no conversations can take place yet.

Handling errors: summary

Condition Action

RegisterClipboardFormat fails for Native format. Terminate application.
RegisterClipboardFormat fails for OwnerLink format. Terminate application.
RegisterClipboardFormat fails for ObjectLink format. Disable linking capability or terminate application.
MakeProcInstance fails on any method. Free any thunks created previously and terminate application. If the server does not support the StdExecute protocol, you do not need to terminate if MakeProcInstance fails on ServerExecute or DocExecute.
OleRegisterServer fails during server allocation. Free the VTBL thunks and terminate application.
OleRegisterServerDoc fails during document allocation. Call OleRevokeServer, free VTBL thunks, and terminate application.

Example: FApplicationInit, FOLEInstanceInit, and OLEVTBL.C

The FApplicationInit function (in INIT.C) contains Schmoo’s initialization code, which calls FOLEInstanceInit (in OLEINIT.C) to perform the OLE-specific initialization. Only parts of these functions are reusable, but each calls other functions that you can copy verbatim. The first such function is HListParse (in INIT.C), which parses a command line into an array of pointers that you can walk one by one to check arguments, as described previously. HListParse also uses PszWhiteSpaceScan in INIT.C.

All functions in OLEVTBL.C, specifically FOLEVtblInitServer, FOLEVtblInitDocument, and FOLEVtblInitObject, are also reusable. If you used the template methods provided with the sample and kept the method names the same, these functions in OLEVTBL.C will work as is. OLEVTBL.C also contains functions that handle VTBL cleanup upon termination, as described later in this guide.

Compile and test your code

Verify command-line parsing and initialization

Although the server is by no means functional at this point, verify that you can parse the command line and register the server successfully. For testing purposes, use SW_SHOWNORMAL instead of SW_HIDE in the first call to ShowWindow; otherwise, you might start your application as hidden and might not be able to close it. You can then start your application stand-alone, but use the embedding flag and other arguments to verify that you are parsing the command line correctly and handling each case as necessary. You can also initialize your VTBLs and verify that OleRegisterServer and OleRegisterServerDoc work.

Note:

At this point, you risk leaving OLESVR in an unstable state because you registered the server with OLESVR but have not informed it that you are terminating. This is covered in Step 10.

Step 6. User Interface: Change Window Titles and Menus

User interface standards for a server are quite simple; they affect only the window’s title bar, several menus, and a message box. For complete information on user interface standards for OLE, see The Windows Interface: An Application Design Guide, Chapter 9, included in the Windows version 3.1 SDK. Miniservers never run stand-alone and have no menus, so the only user interface change affects the window title.

User interface changes affect a server only when it is started for embedding; a server opened for linking has the same appearance as when it is started stand-alone. The linked server only cares about OLE when it saves and changes documents, so that it can properly inform the client containing the document.

After making these additions, see if the routines change the title bar and menus as expected. You can call these functions either from your file-handling code or from the methods, such as DocSetHostNames.

Window title change

Most full servers that can open files display the name of the open file somewhere in their title bar. For example, Microsoft PC PaintbrushÒ for Windows version 3.1 displays the file name of the currently loaded bitmap if it has a file name; otherwise, it uses the string “(Untitled)”. The standard for file names in the title bar of an application is:

<Application Name> - <File Name>

For example:

Paintbrush - CHESS.BMP

When the server is started for embedding (and only for embedding) the title bar appears as follows:

<Application Name> - <Descriptive Object Classname> in <Client Document>

For example:

Paintbrush - Paintbrush Picture in SERVER.DOC

The DocSetHostNames method provides the names of objects and clients. When we implement DocSetHostNames in the next section, we’ll have a real string to use in titles; for now, use a string such as “Client Document”.

Menu changes for embedding

Changes to the menu are simple; they affect only the File menu in two locations. You can use the ModifyMenu API call to make these changes:

1.First, change File Save to File Update when the server starts for embedding. This does not affect the File Save As... menu item if the server has one.

2.Second, change the File Exit menu item to File Exit & Return to <Client Document>, where <Client Document> is the name given in the DocSetHostNames method (see next section). For now, use a string such as “Client Document” where the real name will appear later.

Note:

When the focus in an MDI server changes from a window that had activated an embedded object to a window that is editing a document that does not contain an embedded object, the Update command should change back to Save.

Example: SCHMOO.C

SCHMOO.C contains a function called MenuEmbeddingSet that takes the window handle of the main server window, a string of the client name, and a flag indicating whether to set the menu to the OLE style or to the stand-alone style:

void FAR PASCAL MenuEmbeddingSet(HWND hWnd, LPSTR pszClient, BOOL fOLE)

{

HMENU hMenu;

char szTemp[130];

LPSTR pszT;

hMenu=GetMenu(hWnd);

// Change the File Save menu to File Update or vice versa.

pszT=(fOLE) ? rgpsz[IDS_UPDATE] : rgpsz[IDS_SAVE];

// IDM_FILESAVE defined in SCHMOO.H.

ModifyMenu(hMenu, IDM_FILESAVE, MF_STRING, IDM_FILESAVE, pszT);

// Change "Exit" to "Exit & Return to xx" or vice versa.

if (fOLE)

wsprintf(szTemp, rgpsz[IDS_EXITANDRETURN], pszClient);

else

lstrcpy(szTemp, rgpsz[IDS_EXIT]);

ModifyMenu(hMenu, IDM_FILEEXIT, MF_STRING, IDM_FILEEXIT, szTemp);

return;

}

You can call a function such as MenuEmbeddingSet at any time to toggle the OLE menu on and off as necessary. This function changes only strings on the menu items; the application must distinguish between File Save and File Update when it receives the WM_COMMAND message for an item. If you replace the item completely and change the ID value, it will receive separate WM_COMMAND messages for each case. Make the decision that is most convenient for your application.

Step 7. Implement Basic Methods

The real fun with an OLE server begins when you implement the basic methods for the server, for the document, and for objects; after implementation, you can start seeing OLE working. Double-clicking an embedded object in a client will start an instance of your server, set data in an object, instruct the server to show itself, and so on. However, do not test these methods until you add minimal shutdown code, which is covered in the “Step 8. Handle Simple Shutdown” section.

Warning:

Do not post any DDE messages or call any OLE functions from within a method. Methods are called as part of an asynchronous process that affects an object, a document, or the server. Do not display a message box or a dialog box because these components process messages internally, causing DDE messages to be processed before the server has finished executing the method it is in.

Thinking about the methods

Implementing the 18 basic methods is not extremely complicated but will take time. Before starting to code these methods, think about what should happen during each method, and identify methods that will possibly share code. The skeleton method stubs in the SKEL directory of the sample code describe the responsibilities of each function briefly; the descriptions are repeated in the following sections. It becomes increasingly difficult to see global issues between functions once you immerse yourself in code; please read through this section or review the sample code first.

In the following method descriptions, you will begin to see some overlap between methods. Note that the names of the methods are given as ServerRelease and DocRelease instead of Release to make it easier to associate methods with OLE items. These names also match the names in the sample code.

Note:

Although the methods for the server, documents, and objects are documented to accept pointers like LPOLESERVER, you may code your methods to accept your private LPOLESERVER data type. For example, Schmoo’s ServerCreate in OLESVR.C accepts the LPSCHMOOSERVER type instead of LPOLESERVER as the first parameter. This eliminates the need to declare a local variable for your private data structure and to assign the LPOLESERVER value to it on entry.

Basic server methods

Basic server methods encompass all methods in OLESERVERVTBL except ServerExecute:

ServerCreate

ServerCreateFromTemplate

ServerEdit

ServerExit

ServerOpen

ServerRelease

ServerCreate

OLESVR calls ServerCreate when a client application creates a new object with the Insert Object command12 and the client calls OleCreate.

Parameters:

pServer (LPOLESERVER) Points to the OLESERVER structure passed in OleRegisterServer.
lhDoc (LHSERVERDOC) Identifies the handle to associate (to store) with the document.
pszClass (LPSTR) Provides the name of the document class.
pszDoc (LPSTR) Provides the name of the document for user interface changes.
ppServerDoc (LPLPOLESERVERDOC) Specifies the location of the pointer to the new OLESERVERDOC structure.

Responsibilities:

1.Create a document of the specified class.

2.Allocate and initialize an OLESERVERDOC structure.

3.Store lhDoc in the OLESERVERDOC structure.

4.Store a pointer to the new OLESERVERDOC structure in ppServerDoc.

5.Return OLE_OK if successful, OLE_ERROR_NEW otherwise.

Steps 1 and 2 can be combined. Single-document servers can have a single OLESERVERDOC structure as a global variable that is initialized during startup, so document creation and initialization are already complete. A server can allocate memory in Step 1 instead, as the Schmoo sample demonstrates. Note that an MDI server generally creates a new window for the new document.

ServerCreateFromTemplate

OLESVR calls ServerCreate when a client application creates a new object from a template with the Insert Object command and the client calls OleCreateFromTemplate.

Parameters:

Same as ServerCreate, with an additional LPSTR, pszTemplate, pointing to the file name of the template file.

Responsibilities:

1.Create a document of the specified class.

2.Read the contents of the specified file and initialize the document.

3.Allocate and initialize an OLESERVERDOC structure.

4.Store lhDoc in the OLESERVERDOC structure.

5.Store a pointer to the new OLESERVERDOC structure in ppServerDoc.

6.Return OLE_OK if successful, OLE_ERROR_TEMPLATE otherwise.

ServerCreateFromTemplate is a fancy name for initializing a new document with the contents of a file, although the file is not referenced anywhere after initialization. Everything else is identical to ServerCreate except that ServerCreateFromTemplate returns OLE_ERROR_TEMPLATE instead of OLE_ERROR_NEW.

ServerEdit

OLESVR calls ServerEdit when a client application activates an embedded object for editing and the server must create a new document. The DocGetObject method creates the object and ObjSetData sets the object’s data. At this point, the server is not visible.

Parameters:

pServer (LPOLESERVER) Identifies the server.
lhDoc (LHSERVERDOC) Identifies the handle to store with the document.
pszClass (LPSTR) Describes the class of document to create.
pszDoc (LPSTR) Provides the name of the document for optional use in window titles. However, the DocSetHostNames method is also called later, so you need not change the window title here.
ppServerDoc (LPLPOLESERVERDOC) Specifies the location of the pointer to the new document.

Responsibilities:

1.Create a document of the specified class.

2.Allocate and initialize an OLESERVERDOC structure.

3.Store lhDoc in the OLESERVERDOC structure.

4.Store a pointer to the new OLESERVERDOC structure in ppServerDoc.

5.Return OLE_OK if successful, OLE_ERROR_EDIT otherwise.

ServerEdit is identical to ServerCreate except that it results in an ObjSetData call.

Warning:

If a client pastes an object that was created and copied from a stand-alone server, and if that object is activated while the stand-alone server is active, the server’s instance is transformed into an embedded server. Therefore, when ServerEdit is called, the server must convert itself to an embedded configuration. The Schmoo example simply sets pGlob->fOLE=TRUE.

ServerExit

OLESVR calls ServerExit when the server must terminate immediately as a result of a fatal error. The server is always hidden when ServerExit is called, so there are no dirty files to be saved.

Parameter:

pServer (LPOLESERVER) Identifies the server.

Responsibilities:

1.Hide the window to prevent any user interaction.

2.Call OleRevokeServer. Ignore an OLE_WAIT_FOR_RELEASE return value.

3.Perform the necessary action (for example, DestroyWindow) to terminate the application.

4.Return OLE_OK.

Example:

ShowWindow(pGlob->hWnd, SW_HIDE);

pServer->fRelease=FALSE;

os=OleRevokeServer(pServer->lh);

DestroyWindow(pGlob->hWnd);

ServerOpen

OLESVR calls ServerOpen when the user activates a linked object in an OLE client and the client calls OleActivate. The server simply creates a new document with that file loaded, as in ServerCreateFromTemplate, but leaves the file open (ServerCreateFromTemplate uses the file to initialize the data but discards the file name). When ServerOpen is called, the server is still hidden.

Parameters:

pServer (LPOLESERVER) Identifies the server.
lhDoc (LHSERVERDOC) Identifies a handle to store with the document.
pszDoc (LPSTR) Provides the file name of the document to open.
ppServerDoc (LPLPOLESERVERDOC) Specifies the location of the pointer to the new document.

Responsibilities:

1.Create a document of the specified class.

2.Read the contents of the specified file and initialize the document.

3.Save the file name of the loaded file with this document if necessary.

4.Allocate and initialize an OLESERVERDOC structure.

5.Store lhDoc in the OLESERVERDOC structure.

6.Store a pointer to the new OLESERVERDOC structure in ppServerDoc.

7.Return OLE_OK if successful, OLE_ERROR_OPEN otherwise.

ServerRelease

OLESVR calls ServerRelease after the server calls OleRevokeServer and when the DDE conversation with the client has been successfully closed. This informs the server that no conversations exist and that it is free to terminate at this point. ServerRelease also terminates a server started from the client to update links with the OleUpdate call.

Parameter:

pServer (LPOLESERVER) Identifies the server.

Responsibilities:

1.Set a flag to indicate that ServerRelease has been called.

2.If the application is hidden and has not called OleRevokeServer itself, you must use ServerRelease to instruct the application to terminate, by posting a WM_CLOSE (or otherwise effective) message and immediately returning OLE_OK.

3.Otherwise, free any resources allocated for this server, including documents (if necessary) and possibly the structure that holds server information.

4.Return OLE_OK if successful, OLE_ERROR_GENERIC otherwise.

ServerRelease is a tricky method to handle because it may be called twice in the life of a server application. If you run the server and close it, you call OleRevokeServer, which eventually calls ServerRelease. However, your server may start hidden and stay hidden to perform an invisible update in a client application. OLESVR calls ServerRelease before any other methods, in which case you must tell yourself to close because no user interaction could close the application.

Schmoo uses the server handle to determine whether to close itself on ServerRelease. When the method is called, Schmoo checks if the window is hidden and if a non-NULL value exists in pOLE->pSvr->lh. If so, it posts a WM_CLOSE to kill the application and returns OLE_OK:

if (!IsWindowVisible(hWnd) && 0!=pOLE->pSvr->lh)

{

PostMessage(hWnd, WM_CLOSE, 0, 0L);

return OLE_OK;

}

Posting a WM_CLOSE message is the suggested way to terminate the application because it calls DestroyWindow by default and allows centralization of the application’s cleanup code. DestroyWindow sends a WM_DESTROY message to the application, whereas most applications call PostQuitMessage. Do not call PostQuitMessage directly from this method because it may bypass your application’s cleanup procedures.

When Schmoo calls OleRevokeServer itself, it first clears the pOLE->pSvr->lh value to tell ServerRelease that it actually called OleRevokeServer. When called, ServerRelease frees any document it is holding. If we did not make this distinction, the invisible update case above might have freed the document before that document was released.

Document methods

Basic document methods encompass all methods in OLESERVERDOCVTBL except for DocExecute, DocSetColorScheme, and DocSetDocDimensions:

DocClose

DocGetObject

DocRelease

DocSave

DocSetHostNames

The return value for all document methods is the OLESTATUS type, as in server methods. For method stubs, simply return OLE_OK for all methods.

DocClose

OLESVR calls DocClose when the client containing a link (embedding or linking) to a document shuts down and the document must be unconditionally closed. This method is always called before the DocRelease method.

Parameter:

pDoc (LPOLESERVERDOC) Identifies the document to be closed.

Responsibilities:

1.Call OleRevokeServerDoc; wait until OLESVR calls DocRelease before freeing resources and allocations.

2.Return the return value of OleRevokeServerDoc; this will generally be OLE_OK.

When DocClose is called, take no action to notify the user. The client application handles that responsibility.

os=OleRevokeServerDoc(pDoc->lh); // lh was stored in ServerCreate,

// ServerOpen, and so on.

return os;

DocGetObject

A client application calls OleGetObject to retrieve a connection to an object, which generates a call to this method. If the pszObj parameter is NULL, DocGetObject is called for an embedded object, after ServerCreate, ServerEdit, or ServerCreateFromTemplate. If pszObj is non-NULL, ServerOpen was used to open a linked object.

Parameters:

pDoc (LPOLESERVERDOC) Identifies the affected document.
pszObj (LPSTR) Specifies the name of the object to be created. If NULL, OLESVR requests the entire document.
ppObj (LPLPOLEOBJECT) Identifies the location of the pointer to the new object.
pClient (LPOLECLIENT) Identifies the client that will connect to this object.

Responsibilities:

1.Allocate and initialize an OLEOBJECT structure.

2.Store pClient in the object’s OLEOBJECT structure.

3.Return OLE_OK if successful, OLE_ERROR_NAME if pszObj is not recognized, or OLE_ERROR_MEMORY if the object could not be allocated.

The pClient pointer is not the same pointer a client application passes to OLECLI. This client structure resides in OLESVR and acts on behalf of the client application. You must save this pointer so that you can send notifications such as OLE_CHANGED to the pClient->lpvtbl->CallBack function.

DocRelease

OLESVR calls DocRelease when all DDE conversations to the object are closed, after the server calls OleRevokeServerDoc or OleRevokeServer. After DocRelease, no more calls are made to the document methods for this document. The document can free any resources or objects at this time, including its own OLESERVERDOC structure allocated through a method such as ServerOpen.

Parameter:

pDoc (LPOLESERVERDOC) Identifies the document to be released.

Responsibilities:

1.Release any resources and allocations associated with this document.

2.Return OLE_OK.

Example:

The Schmoo server stores client names and atoms for the document in its SCHMOODOC structure and frees them on DocRelease:

if (NULL!=pDoc->aName) // created in DocSetHostNames

{

DeleteAtom(pDoc->aName);

pDoc->aName=NULL;

}

if (NULL!=pDoc->aClient) // created in DocSetHostNames

{

DeleteAtom(pDoc->aClient);

pDoc->aClient=NULL;

}

DocSave

When a client application is closing and the user saves the client document containing a linked object, OLESVR directs the server to save the linked file by calling the DocSave method. This ensures that the data currently displayed in the client is saved when the user saves the client document. OLESVR uses this method only when a server is editing a linked object, so it assumes that the server already knows the file name.

Parameter:

pDoc (LPOLESERVERDOC) Identifies the document to save.

Responsibilities:

1.Save the document to the known file name. This task is application-specific.

2.Return OLE_OK if the save is successful, OLE_ERROR_GENERIC otherwise.

DocSetHostNames

OLESVR calls DocSetHostNames to provide the server with the name of the client document and with the name of the object in the client application. These names are used to make the necessary title bar and menu changes described in the “Step 6. User Interface: Change Window Titles and Menus” section. OLESVR calls this method only for embedded objects.

Parameters:

pDoc (LPOLESERVERDOC) Identifies the affected document.
pszClient (LPSTR) Provides the name of the client document.
pszObj (LPSTR) Provides the name of the object in the client application.

Responsibilities:

1.Change the title bar to reflect the embedded state with the appropriate names.

2.Change the File menu to reflect the embedded state and the name of the client application.

3.Store the object and client names in the OLESERVERDOC structure. These will be needed later for message boxes that display the name of the client application.

4.Return OLE_OK if successful, OLE_ERROR_GENERIC otherwise.

Once you have implemented this method, you can modify your previous work for the title bar and menus to reflect the names in pszClient and pszObj passed to this method. If you store names as atoms, be sure to free existing atoms before adding a new one; otherwise, you will have a small memory leak.

Example:

if (NULL!=pDoc->aObject)

DeleteAtom(pDoc->aObject);

pDoc->aName=AddAtom(pszDoc);

if (NULL!=pDoc->aClient)

DeleteAtom(pDoc->aClient);

pDoc->aClient=AddAtom(pszClient);

Object methods

The ObjSetBound, ObjSetColorScheme, and ObjSetTargetDevice methods are not necessary for basic server operations. This leaves seven basic object methods, each of which is straightforward to implement:

ObjDoVerb

ObjEnumFormats

ObjGetData

ObjQueryProtocol

ObjRelease

ObjSetData

ObjShow

ObjDoVerb

OLESVR calls ObjDoVerb when the client application has called OleDoVerb on an embedded object. This method receives a verb identifier and must perform the actions necessary to execute that verb.

Parameters:

pObj (LPOLEOBJECT) Identifies the affected object.
iVerb (WORD) Index to the verb to execute.
fShow (BOOL) Indicates whether the server should show the object (TRUE) or remain in its current state (FALSE).
fFocus (BOOL) Indicates whether the server should take the focus (TRUE) or leave the focus unaffected (FALSE).

Responsibilities:

1.Execute the verb:

For a Play verb, a server does not generally show its window or affect the focus.

For an Edit verb, show the server’s window and the object if fShow is TRUE, and take the focus if fFocus is TRUE. An ideal way to do this is to call the ObjShow method through the OLEOBJECTVTBL; ObjShow will handle showing the object and taking the focus itself.

An Open verb is not clearly defined; it may mean Play or Edit, depending on the application. If the Schmoo server had an Open verb, it would treat it the same as Edit.

2.Return OLE_OK if the verb was successfully executed, OLE_ERROR_DOVERB otherwise.

OLESVR will call the ObjSetData method before ObjDoVerb so that the object has data to edit or to play when so instructed.

Example:

switch (iVerb)

{

case OBJVERB_EDIT:

//

// Schmoo's edit is the same as simply showing the object.

// Call ObjShow through the LPOLEOBJECTVTBL.

//

if (fShow)

return (pObj->pvtbl->Show)((LPOLEOBJECT)pObj, fShow);

// Return OLE_OK.

break;

case OBJVERB_PLAY:

// Unsupported (but perhaps known) verb.

return OLE_ERROR_DOVERB;

default:

// Unknown verb.

return OLE_ERROR_DOVERB;

}

return OLE_OK;

ObjEnumFormats

On receiving various requests from OLECLI, OLESVR asks the server for the types of data it can render for an object. OLESVR makes multiple calls to ObjEnumFormats until the method returns the appropriate format.

Parameters:

pObj (LPOLEOBJECT) Specifies the affected object.
cf (WORD) Indicates the last format returned by this method. If cf is 0, this is the first call to this method in a series. The order in which formats are returned must match the order in which the data is placed on the Clipboard.

Responsibilities:

1.Depending on cf, return the next Clipboard format in which the server can render the object’s data.

2.If no supported formats appear after the format in cf, return NULL.

Example:

A simple series of IF statements determines the order of formats. (The logic of the statements determines the order, not their order of appearance.) The format order must match the order of the same data on the Clipboard. Note that you cannot use a SWITCH statement because the format identifiers from RegisterClipboardFormat (for example, cfNative) are not constants:

if (0==cf)

return pOLE->cfNative;

if (pOLE->cfNative==cf)

return pOLE->cfOwnerLink;

if (pOLE->cfOwnerLink==cf)

return CF_METAFILEPICT;

if (CF_METAFILEPICT==cf)

return CF_BIÔAP;

if (CF_BIÔAP==cf)

return pOLE->cfObjectLink;

// This IF is here just to be explicit.

if (pOLE->cfObjectLink==cf)

return NULL;

return NULL;

ObjGetData

OLESVR calls ObjGetData to ask the server to render an object in a specific format, such as Native or CF_METAFILEPICT. These requests occur whenever the client needs to display an object and whenever the data must be written to a client file.

Parameters:

pObj (LPOLEOBJECT) Specifies the object in question.
cf (WORD) Identifies the data format requested.
phData (LPHANDLE) Identifies the location of the handle to the allocated data.

Responsibilities:

1.Allocate the requested data through GlobalAlloc (with GMEM_MOVEABLE and GMEM_DDESHARE). The exception is data for CF_BITMAP that uses a call such as CreateBitmap.

2.Lock and fill the memory with the appropriate data.

3.Unlock the memory and store the handle in *phData.

Example:

As previously suggested, you can implement a routine or a set of routines to create data structures for each format, specifically because OLESVR requests ObjGetData to return any data format at any time. Having a function to call for each format reduces ObjGetData to a trivial sequence of calls:

if (pOLE->cfNative==cf)

hMem=HGetPolyline(pGlob->hWndPolyline);

// Polyline is Schmoo's native data

if (CF_METAFILEPICT==cf)

hMem=HGetMetafilePict(pGlob->hWndPolyline);

...

if (NULL==hMem)

return OLE_ERROR_MEMORY;

*phData=hMem;

return OLE_OK;

ObjQueryProtocol

OLESVR calls ObjQueryProtocol to determine which protocols (stdEditing and possibly StdExecute) the server supports.

Parameters:

pObj (LPOLEOBJECT) Specifies the affected object.
pszProtocol (LPSTR) Provides the name of the protocol.

Responsibilities:

1.If the protocol in pszProtocol is supported, return a pointer to an OLEOBJECT structure that contains an appropriate VTBL for that protocol (such as the pObj passed to this method).

2.If the protocol is not supported, return NULL.

Example:

// lstrcmp returns 0 if the two strings are identical.

if (0==lstrcmp(pszProtocol, "StdEditing"))

return (LPVOID)pObj;

return NULL;

ObjRelease

OLESVR informs an object that it is no longer connected to any client after the client calls OleDelete or the server calls OleRevokeServer, OleRevokeServerDoc, or OleRevokeObject. Because this is the last object method that OLESVR calls for a given object, the application can free any resources or allocations for that object except for the object’s OLEOBJECT structure.

Parameter:

pObj (LPOLEOBJECT) Specifies the object being released.

Responsibilities:

1.Free any resources and allocations for this object.

2.Set to NULL any saved LPOLECLIENT stores in the OLEOBJECT structure.

3.Set the release flag in the OLEOBJECT structure to indicate that ObjRelease has been called.

4.Return OLE_OK.

Example:

Schmoo frees the atom containing the object name and frees the SCHMOOOBJECT structure. Schmoo allocated local memory for the object in DocGetObject and stored the handle in the object structure so it would have that handle here in ObjRelease:

// Delete the atom storing the object name.

if (NULL!=pObj->aName)

DeleteAtom(pObj->aName);

// Free the memory containing this object. pObj is now invalid.

LocalFree(pObj->hMem); // Handle to this object in pObj->hMem,

// stored in DocGetObject.

pOLE->pObjCur=NULL;

return OLE_OK;

ObjSetData

When OLESVR launches a server to edit an embedded object, it calls ObjSetData to provide the server with the data embedded in the client. OLESVR calls ObjSetData before calling methods like ObjDoVerb and ObjShow because ObjSetData is one of the most important methods in OLE.

Parameters:

pObj (LPSCHMOOOBJECT) Identifies the affected object.
cf (WORD) Specifies the format of the data contained in hData.
hData (HANDLE) A handle to global memory containing the data, allocated with GMEM_DDESHARE and GMEM_MOVEABLE.

Responsibilities:

1.If the data format is not supported, return OLE_ERROR_FORMAT.

2.Attempt to GlobalLock the memory to get a pointer to the data. If GlobalLock returns NULL, return OLE_ERROR_MEMORY.

3.Copy the data to the object identified by pObj.

4.Unlock and GlobalFree the data handle. The ObjSetData method is responsible for the memory.

5.Return OLE_OK.

Example:

Schmoo accepts only native data through ObjSetData; it copies the data to the object structure and passes it to the Polyline window, which accepts data through the window-specific PLM_POLYLINESET message:

LPPOLYLINE lppl;

// Check to see whether we were given native data.

// We don't support anything else.

if (pOLE->cfNative!=cf)

return OLE_ERROR_FORMAT;

lppl=(LPPOLYLINE)GlobalLock(hData);

//

// CHECK the return from GlobalLock because we don't know

// where this handle has been.

//

if (NULL==lppl)

return OLE_ERROR_MEMORY;

// Copy the data to the object structure.

pObj->pl=*lppl;

// Set the data through the editing window.

SendMessage(pGlob->hWndPolyline, PLM_POLYLINESET, TRUE, (LONG)lppl);

// Server is responsible for freeing the data.

GlobalUnlock(hData);

GlobalFree(hData);

return OLE_OK;

ObjShow

OLESVR calls ObjShow when an object must become visible. This method makes the server window visible and possibly scrolls the object into view. If the object is selectable (such as a range of cells in a spreadsheet), it selects the object as well.

Parameters:

pObj (LPOLEOBJECT) Identifies the object to show.
fTakeFocus (BOOL) Indicates whether the server should SetFocus to itself or leave the focus unaffected.

Responsibilities:

1.Show the application window(s), if not already visible.

2.Scroll the object identified by pObj into view, if necessary.

3.Select the object if possible.

4.If fTakeFocus is TRUE, call SetFocus with the main window handle.

5.Return OLE_OK if successful.

Example:

BOOL fVisible

fVisible=IsWindowVisible(pGlob->hWnd);

// Because we have only one object, we don't care what's in pObj.

ShowWindow(pGlob->hWnd, SW_NORMAL);

if (fTakeFocus)

SetFocus(pGlob->hWnd);

return OLE_OK;

Compile the new code

Now is a good time to compile all the new code added for basic methods so that you can verify syntax and compilation. However, before testing the methods, you must implement a simple shutdown procedure that calls OleRevokeServer.

Step 8. Handle Simple Shutdown

To close the server application, you must call OleRevokeServer and perform application shutdown tasks such as freeing the thunks in the VTBLs created during initialization. OleRevokeServer automatically revokes documents which, in turn, revokes any objects. The other revoke functions, OleRevokeServerDoc and OleRevokeObject, are discussed in Step 10. The best place to call OleRevokeServer is from the main application window’s WM_CLOSE message case, just before calling DestroyWindow.

If a revoking function returns OLE_WAIT_FOR_RELEASE, you would normally enter a message-processing loop. However, when calling OleRevokeServer, you will not make any more OLE function calls and must assume that all OLE conversations are finished.

You will call OleRevokeServerDoc during Step 9. If that returns OLE_WAIT_FOR_RELEASE, you should enter a message loop that does not exit until the DocRelease method has been called. This message loop allows DDE messages to be processed (which is critical to having DocRelease called) without allowing the server to execute past a particular point. This is how OLE takes an asynchronous protocol like DDE and lets the application synchronize OLE calls.

The FOLEReleaseWait function (in Schmoo’s OLEMISC.C) demonstrates how to process messages while waiting for a flag (pointed to by pf) to change to TRUE. Because FOLEReleaseWait takes a pointer to any BOOL, you can use the function to wait for ObjRelease, DocRelease, or ServerRelease by passing a pointer to the fRelease flag contained in an application-specific OLEOBJECT, OLESERVERDOC, or OLESERVER structure.

FOLEReleaseWait uses PeekMessage to provide a space for background processing. If your application does not perform background processing, replace PeekMessage with GetMessage and remove the ELSE clause that contains the WaitMessage. Do not concern yourself with OleUnblockServer for now.

BOOL FAR PASCAL FOLEReleaseWait(BOOL FAR *pf, LONG lhSvr)

{

MSG msg;

BOOL fMsg=FALSE;

*pf=FALSE;

while (FALSE==*pf)

{

OleUnblockServer(lhSvr, &fMsg);

// Process normal messages after we've cleared up the server queue.

if (!fMsg)

{

//

// We use PeekMessage here to make a point about power

// management and Windows--when no messages are left,

// GetMessage correctly lets the system go into a low-

// power idle state. PeekMessage by itself does not.

// If you do background processing with PeekMessage

// and have nothing to do, call WaitMessage to let

// Windows detect the idle state.

//

if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))

{

TranslateMessage (&msg);

DispatchMessage (&msg);

}

else

{

WaitMessage();

}

}

}

return *pf;

}

After you add the single call to OleRevokeServer in your WM_CLOSE case, you are ready to compile and test your server with embedded objects.

Compile and test your code

Test the basic functionality of your OLE server

First, open the server in stand-alone mode, create some data, copy it to the Clipboard, close the server, and paste the data into a suitable client document. Then launch your server by activating the object in the client for editing. In a debugger with breakpoints on each method, you can begin to see the sequence of method calls and can verify that the correct actions are happening. When all goes well, your server will appear with the data from the embedded object.

To test linking, start your application in stand-alone mode, open a file, copy data to the Clipboard, close the server, and paste the data into an appropriate client application. When you activate the object in the client, you will see your server start and can observe what happens when OLESVR calls your methods.

You can also verify the server’s user interface and ensure that you change the title bar and the File menu when the server starts for embedding. At this time, you cannot update the object in the client properly; this is where the File menu commands come in.

Step 9. File Menu Commands: New, Open, Import, Save (Copy) As, and Save/Update

When a full server carries out the File New, File Open, File Import, File Save (Copy) As, and File Save/Update commands, it must let OLESVR know what is happening to its documents. This section describes the steps necessary to handle each case.

In a single-document server, the File New and File Open commands break the link between the client application and the server document. The File Save As command also breaks the link between the client and the server if the server was editing an embedded object but not if it was editing a linked object—the server simply has to tell OLESVR that the document was renamed.

Note:

In an MDI server, the New and Open commands do not affect the window containing an embedded object because they simply create a new window. This eliminates the need to prompt the user to update the object.

Before addressing the menu commands, two topics need brief treatment: maintaining a dirty flag for the document and notifying the client.

When to consider the document dirty

Before diving into each case, examine what operations in your application make the document dirty and prompt the user to save changes before carrying out an operation like File New. An application normally tracks a dirty flag that is set or cleared on various conditions.

Flag Condition

TRUE OLESVR calls the ServerCreate method.
TRUE OLESVR calls the ServerCreateFromTemplate method.
TRUE A user action in the server changes the document.
TRUE (Optional) The user resizes the server window, which resizes the server’s data.
FALSE OLESVR calls the ServerEdit method.
FALSE OLESVR calls the ObjGetData method and requests the Native data format.

The Schmoo server contains a single function, FDirtySet, to set or clear the dirty flag. Commands that must watch this flag to prompt the user to save changes are generally on the File menu.

The Windows Interface: An Application Design Guide defines a standard message box (Figure 3) for informing the user that an embedded object is dirty when the user closes a document (or the server). For a linked object, use the message boxes that your application already has.

Figure 3.

Notifying the client

File Save and File Update require the server to notify the client (through OLESVR) of those actions by calling the OLECLIENTVTBL CallBack function; the pClient structure passed to DocGetObject contains a pointer to the client VTBL containing CallBack.

Calls like OleSavedServerDoc generally handle these notifications automatically. In the following cases, the server must send a notification code directly to CallBack.

Notification code Condition

OLE_CHANGED Any operation that changes the object, making it dirty. Not necessary for embedded objects.
OLE_SAVED When the user saves a file in a stand-alone server.
OLE_CLOSED When the user closes a document with dirty objects (that is, embedded objects that require updating) before calling OleRevokeServerDoc.

The OLE_CHANGED notification works expressly for linked objects; it allows the client application to change the object dynamically as the user changes it in the server.

The Schmoo sample contains the OLEClientNotify function (in OLEMISC.C), which takes an LPOLECLIENT and a notification code and sends that notification to the CallBack referenced through the OLECLIENT pointer. It is used only in the File Save (OLE_SAVE) and File Exit (OLE_CLOSED) menu cases and from the FDirtySet function (OLE_CHANGED):

void FAR PASCAL OLEClientNotify(LPOLECLIENT pClient, WORD wMsg)

{

LPOLECLIENTVTBL pvt;

if (NULL==pClient)

return;

pvt=pClient->lpvtbl;

if (NULL==pvt)

return;

(pvt->CallBack)(pClient, wMsg, (LPOLEOBJECT)pxObj);

return;

}

File New and File Open

File New and File Open follow the same sequence of steps, because File Open is really File New with the extra step of loading the contents of a file.

1.(SDI server only) Prompt the user to update the objects before breaking the connection.

2.(SDI server only) Close the old document and call OleRevokeServerDoc. If OleRevokeServerDoc returns OLE_WAIT_FOR_RELEASE, process messages until the DocRelease method is called.

3.Allocate, initialize, and register a new document with OleRegisterServerDoc.

4.For File Open, load the file as necessary.

5.(SDI server only) Reconfigure the user interface for a stand-alone server.

As soon as the user chooses to update the objects or to discard changes, close the old document and call OleRevokeServerDoc. If OLE_WAIT_FOR_RELEASE is called, use a function such as FOLEReleaseWait to process messages until the DocRelease method is called. In the example below, note that FOLEReleaseWait watches SCHMOODOC’s fRelease flag, which DocRelease sets to TRUE:

os=OleRevokeServerDoc(pOle->oleDoc.lh);

if (OLE_WAIT_FOR_RELEASE==os)

{

pOLE->oleDoc.fRelease=FALSE;

FOLEReleaseWait(&pOLE->oleDoc.fRelease, pOLE->oleSvr.lh);

}

As soon as the previous document is released, the server can continue the File New operation and create a new, untitled document, which involves allocating a new OLESERVERDOC structure and calling OleRegisterServerDoc. You must register the new document, even if it does not have a file name (use “(Untitled)” or some other appropriate name).

An SDI server that was embedding when the user chooses File New must reconfigure itself to appear as a stand-alone server by changing the title bar and menu back to the stand-alone interface: The new file name appears in the title bar, and the File menu contains the standard Save and Exit items in place of the Update and Exit & Return to... menu items. From this point onward, the server operates stand-alone. Be sure to reset any global flags in your application that track whether the server was started through OLE and whether it was started for linking or embedding.

If desired, an application may disable File New and change File Open to File Import, thus eliminating operations that break the connection to the server.

File Import

Because the File Open command breaks the connection between the object in the server and the client application, it prevents users from importing data from existing files into an embedded or linked object. If you want to include this capability, your server application should support a File Import command. This command acts almost identically to File Open, but instead of renaming the server’s document and breaking the connection to the client document, the command simply loads data from a file into the current object. That file is then closed and referenced again only if File Import is reused.

A File Import command may either fully replace the data in the object (as the Schmoo server does) or may simply add to it. If you wish to provide both capabilities, you should support a File Import command (to fully replace the object’s contents) and an Edit Paste From... command (to paste the contents of a file into the object). A standard interface for importing data from existing files does not currently exist.

File Save (Copy) As

For an embedded object, the File Save (Copy) As command saves a copy of the object in a file. For a linked object, the command saves an untitled or existing file under a new name. Because File Save As affects a single document and does not create a new one, the steps are identical for SDI and MDI servers.

1.Retrieve the new file name.

2.Create and write the file.

If the object is embedded, the functionality of this command ends here. For linked objects, continue with steps 3 through 5.

3.Call OleRenameServerDoc to inform OLESVR that the document has a new name.

4.Call OleSavedServerDoc to inform OLESVR that the document has been saved.

5.Change the window title bar to reflect the new file name for a linked file.

File Save/Update

The File Save and File Update commands require the following steps:

1.If the object is linked, write the new data to a file.

2.Call OleSavedServerDoc.

3.Set your dirty flag to FALSE.

If the server is running stand-alone and saves a file, it should simply send the OLE_SAVED notification to OLESVR as described previously.

Handling the File Update command is quite simple. However, when you call OleSavedServerDoc, you will see that many methods, such as ObjGetData, are called from OLESVR. The majority of the work in updating an object is handled through the various methods.

Compile and test your code

Verify File menu commands

You are now ready to test linking and embedding with all the File menu commands you implemented. To test a linked object, start the server stand-alone, edit and save a file, and then copy an object to the Clipboard and Paste Link it into a suitable client application. Close the server and double-click in the client to activate the linked object. Your server should start with –Embedding <filename> on the command line. Verify that the changes you make are noted to the client (sending the OLE_CHANGED notification). Whenever a change occurs and you send the notification, you will notice calls to your methods to retrieve the updated data.

When editing an embedded object, verify that the proper message boxes appear when necessary and that you register new documents successfully when closing an existing document. In addition, verify that updating the object causes the client to display the new data and that Save Copy As makes a copy of the object being edited without deleting the object.

Test your server with each menu command under stand-alone, linking, and embedding cases. If you have a multiple-instance SDI server, you might also want to verify that when you edit an embedded object and then create a new object (this should revert the server to stand-alone configuration unless you disable File New), double-clicking on an object in a client starts a new instance of your application to edit that object. If a new instance does not start, check that you always register a document (regardless of whether it’s untitled), and check your use of OLE_SERVER_MULTI and OLE_SERVER_SINGLE in your OleRegisterServer call.

Step 10. Close Objects, Documents, and the Server

A server can close:

An object (by deleting it).

A document containing objects (by closing the file, with or without saving changes).

The server itself.

Remember to call FreeProcInstance on the VTBL methods when you terminate the application.

Closing one of these OLE items means “revoking” it, or terminating any conversations that a client may have with that item. Releasing an item, on the other hand, means informing the item that no client document is connected to it, so it can perform any final cleanup. OLESVR has three revoke APIs to handle these cases.

Item closing OLESVR call

Object OleRevokeObject
Document OleRevokeServerDoc
Server OleRevokeServer

OleRevokeServerDoc revokes all objects in the document, and OleRevokeServer revokes all documents and all objects in the server. Any revoke call may return OLE_WAIT_FOR_RELEASE, and when receiving this return value, may enter a message loop like that in Schmoo’s FOLEReleaseWait until that particular item’s release method is called.

When the user closes the server with an embedded object that requires updating, display the message box shown in Figure 3. If the user chooses to save changes, send the OLE_CLOSED notification to OLESVR through the OLECLIENT pointer given in DocGetObject, and call OleRevokeServerDoc. If the user does not want to save changes, simply call OleRevokeServerDoc. You can see an example of this in Schmoo’s FCleanVerify function.

Compile and test your code

After successfully compiling and linking your server, verify that revoking objects, documents, and the server occur at the proper time and that the server waits until the appropriate Release method is called. In addition, verify that you close the server properly when OLESVR calls ServerRelease and the application is hidden. This normally occurs when a client application updates a link; to test this, open a suitable client application and create a linked object from your server. When you save the client file and reload it, you will be prompted to update links. Updating the link to your server starts a hidden instance of the server and eventually calls ServerRelease. You can use the Windows SDK Heap Walker utility to verify that your server’s modules are indeed freed from memory as soon as the link update is complete.

Step 11. Implement Optional Methods and OLE Functions

This section discusses how to handle the remaining (optional) server, document, and object methods. Minimal stubs must exist for these methods, but they do not necessarily have to contain code. This section also discusses the remaining OLESVR calls, such as OleBlockServer and OleRevertServerDoc.

Document and object SetColorScheme

DocSetColorScheme and ObjSetColorScheme provide the document or the object with a set of colors suggested by the client for foreground, background, fill, and lines.13 The LPLOGPALETTE parameter to this function points to a LOGPALETTE structure that contains colors suggested by the client that the server can display for color choices when editing the embedded or linked object; these colors should not be sent to the video hardware.

In the LOGPALETTE structure, the palNumEntries field contains the number of total colors in this color scheme. The palPalEntry array contains those colors. The first color is the suggested foreground color; the second color is the suggested background color. The first half of the remaining colors are suggested fill colors, and the second half are suggested line colors. If there are an odd number of entries, use the extra color as a fill color (there is one less line color than there are fill colors). If the entire color scheme has nine entries, use the first two for foreground and background, the next four for fill colors, and the final three for line colors.

If the server uses these colors, apply (or save) the colors to the color selection menus or dialog boxes the application displays, and return OLE_OK. If the server does not manipulate colors with a palette, return OLE_ERROR_PALETTE.

DocSetDocDimensions

DocSetDocDimensions informs the document that the user changed the object’s size in the client application. The server (although not required) can resize itself or the object to keep the object consistent in both client and server.

DocSetDocDimensions receives an LPOLESERVERDOC parameter and a pointer to a RECT structure containing the object size in the client, given in MM_HIMETRIC units. Be sure to convert these coordinates to the units manipulated by your server, and carry out any object or document resizing, as the sample server does in OLEDOC.C:

OLESTATUS FAR PASCAL DocSetDocDimensions(LPSCHMOODOC pDoc, LPRECT pRect)

{

//

// OLESVR will call this method when the client has resized the

// object.

//

// In this case, we try to make the parent window the correct

// size to just contain the object.

//

// First, convert the rectangle to MM_TEXT units.

RectDeviceConvert(pGlob->hWnd, pRect);

//

// Tell the Polyline document to use this rectangle, and notify

// the parent, which will then resize itself.

//

SendMessage(pGlob->hWndPolyline, PLM_RECTSET, TRUE, (LONG)pRect);

return OLE_OK;

}

RectConvertToDevice is a private function that converts the MM_HIMETRIC units into MM_TEXT. The document is resized by sending a private message to the document window (hWndPolyline) with the new rectangle.

Note:

Although resizing the document through user manipulation could be considered an action that changes the document (makes it dirty), resizing through the DocSetDocDimensions method should not make the document dirty because the client object is already the given size; that is, the server and client are synchronized.

ObjSetBounds

OLE version 1.x does not use ObjSetBounds. If you use this method, handle it as you would handle DocSetDocDimensions, that is, size only an object within a document and not the entire document.

ObjSetTargetDevice

The ObjSetTargetDevice method informs the object that any rendering through the ObjGetData call targets the screen or a printer. The hData parameter is either NULL or contains an OLETARGETDEVICE structure, as defined in OLE.H.

If hData is non-null, use the data in this structure to extract device-specific information. If your application can create a better metafile or bitmap for a specific device, it should use this method to detect that change and to optimize the presentation. If you create the same metafile or bitmap for the screen or for a printer, this method need not be implemented.

After ObjSetTargetDevice is called, any subsequent presentations created through the ObjGetData method should be rendered for the given device until ObjSetTargetDevice is called again with a different device in hData (which might be NULL).

ServerExecute and DocExecute

A server that supports the StdExecute protocol (by being registered as supporting StdExecute in the registration database and by returning an LPOLEOBJECT in ObjQueryProtocol for StdExecute) can expect to receive DDE-style Execute strings through the ServerExecute and DocExecute methods. These methods provide a central location for parsing and executing these strings.

Execute strings are entirely application specific. For example, the Windows Program Manager (although not an OLE server) supports the DDE Execute string “[CreateGroup(OLE Test Applications)]”, which causes it to create a new group window. These Execute strings generally instruct the receiving application to perform a set of commands, usually not to generate and return data other than TRUE or FALSE.

Blocking requests (optional)

If your server is carrying out a long operation and does not want OLE in the picture, use the OleBlockServer function to instruct OLESVR to queue any OLE messages destined for the server. When the server is ready to process these requests, call OleUnblockServer before GetMessage in your main message loop and in any message loop used to wait for a release. OleUnblockServer causes all queued requests to be processed before GetMessage can retrieve the next message.

For example, a server can use OleBlockServer when a modal dialog appears. A modal dialog has its own message-processing loop inside Windows, so any DDE messages between the OLE libraries result in calls to your server’s methods. This might cause unwanted actions while a dialog box is displayed, so block the server at that point; OLESVR cannot make any requests to your application until you unblock.

Alternatively, a server can return OLE_BUSY from a method when the server does not want to handle OLE requests. Unlike OleBlockServer, returning OLE_BUSY lets the server choose the requests to process and lets the client decide whether to discontinue or to retry the request.

OleRevertServerDoc

OleRevertServerDoc is a special OLESVR function for applications that let the user open a file and make changes, but then reload the original file and discard the changes. If you provide this functionality, call the OleRevertServerDoc function when you reload a file without closing it.

Compile and test your code

GLOSSARY

Client (or Client Application) An application that creates and edits compound documents containing objects from one or more server applications. Clients only store objects; servers actually edit them.
Container Synonym for Client.
Destination Synonym for Client.
Document A container for one or more objects; generally the same as a physical file.
Embed To create and store an object completely within a client document. An embedded object contains a presentation format (a bitmap or a metafile), an OwnerLink data structure identifying the server, and the native data provided by the server. Editing an embedded object starts the server and sends the native data back to that server.
File A physical file on a disk, usually containing a document.
Key Unit of storage in the registration database. Subkeys are attached to one root key. A key is physically a character string. Each subkey is separated with a backslash (\).
Link To insert a copy of an object from a source document into a client document. The native data for the object is stored in a file maintained by the server. The client document contains only a presentation format and an ObjectLink data structure identifying the linked file.
Method A callback function in the server application that the OLESVR library calls to perform specific actions such as creating documents or retrieving object data.
Native An internal data structure that contains enough information to completely reconstruct an object. The server application is the only application that understands and manipulates this data.
Object A black box of information with a presentation that represents that data. A server application understands the internal data of an object it creates, whereas a client application treats the internal data like a number of bytes with a pretty picture on the box.
ObjectLink A data structure that identifies the class, the document file name, and the object name that is the source for a linked object.
OLECLI The OLE client library (OLECLI.DLL) that contains the OLE API used by client applications.
OLESVR The OLE server library (OLESVR.DLL) that contains the OLE API used by server applications.
OwnerLink A data structure that identifies the class, the document path, and the object name to describe the owner of an embedded object.
Registration Database The system database that holds the names and paths of applications that support the OLE protocol, the objects they can edit, what verbs those objects support, and whether or not an object handler exists for that class.
Release To sever the connection between an object, a document, or a server and a client document. Objects, documents, and servers have release methods that inform the item that no client is connected to it.
Revoke To close off communication from a client application to a server, a document, or an object. When one of these items is revoked, the item will eventually be released. A client may also revoke communication to a server, a document, or an object.
Server (or Server Application) An application that creates and edits objects for storage in a client application’s compound document.
SHELL.DLL A dynamic link library that contains functions to manipulate the registration database.
Source Synonym for Server.
Subkey A refinement of a key in the registration database. A key can have any number of subkeys, and subkeys can have their own subkeys.
Thunk A procedure-instance address created through a call to MakeProcInstance. Also called instance thunk.

1Maybe a little interesting. The SHIP.MOO and TETRA.MOO samples show a few figures that you can make with Schmoo.

2Object handlers are described in the Windows version 3.1 Software Development Kit (SDK).

3SHELL.DLL also contains functions for the Windows version 3.1 drag/drop interface.

4For example, Microsoft Excel has a Worksheet object class and a Chart object class, both supported by the same application. The distinction is visible at the document level.

5\b RegisterClipboardFormat simply uses the AddAtom function on the given string, and atoms are constant across the system for any given string.

6This is far more convenient and efficient than using separate window bytes for each field in this structure.

7OLE does not ask for an HMF directly but does request a handle to a METAFILEPICT structure.

8Miniservers do not generally interact with the Clipboard.

9This assumes that if the class name key exists, all other necessary keys also exist, which is a safe assumption because users cannot modify the database. Only the SHELL.DLL API and the Windows version 3.1 REGEDIT application can change the database.

10The WIN31QH.HLP and WIN31WH.HLP files included with Windows version 3.1 include OLE information. Search for OLESERVERVTBL, OLESERVERDOCVTBL, and OLEOBJECTVTBL for server, document, and object methods, respectively.

11\b pOLE is a pointer to global OLE variables in the Schmoo sample server.

12See The Windows Interface: An Application Design Guide, Chapter 9, for an overview of user actions for this command.

13This is not a palette from which you should call CreatePalette and RealizePalette.