In the previous post we introduced COM technology, of which I will make a very brief summary to keep in memory the most relevant details.

COM: Recap

COM allows methods and properties to be represented in memory in a “standard” way, so that developers of different languages (and consequently different applications) can, by adapting to this standard, call functions and obtain property values. All in an OOP fashion: in fact, it uses inheritance applied to interfaces and “virtual function tables”.

So, COM is an Application Binary Interface:

  • language-agnostic
  • platform-independent
  • object-oriented

As a garbage collection method, it uses Reference Counting.

All COM objects are derived from at least one basic interface: IUnknown, which provides essential methods for each object (AddRef, Release, and QueryInterface).

Each COM object has its own unique CLSID which is a mandatory GUID to set in Registry Editor for COM registration, while the ProgID, the “human” name of the related COM functionality, is optional.

Each COM object is:

  • A .dll or an .ocx if set up as an In-Process Server. This does not appear in the Task Manager and resides in the address space of the same process that uses it.

  • An .exe if set up as a Local Server. Having a separate PID, it appears in the Task Manager and is in a different address space from the process that uses it. For communication between COM client and COM server, in this case, RPC mechanisms with related “proxies” and “stubs” also come into play.

Windows has awareness of registered COM objects via the registry: HKLM\Software\CLSID or HKCU\Software\Classes\CLSID, while it has awareness of active COM instances via the system Running Object Table (ROT). It is used to retrieve specific active instances of COM objects, without going to recreate them each time with CoCreateInstance.

Well, now let’s jump into Object Linking and Embedding (OLE)!


OLE

OLE is a proprietary Microsoft technology that allows the embedding and linking of documents and other objects. You know when, in Microsoft Word, you add an object, which can be an Excel Worksheet or a Bitmap, into the open document, and you can edit or copy it just by clicking on it? There, the magic behind it is all from OLE.

As already written in the Microsoft documentation, OLE relies heavily on COM technology. To take advantage of all its features, OLE also has an associated file format: Microsoft Compound File Binary (CFB).

Basic Overview

We must distinguish, in the OLE context, the types of applications:

  • Container Application: the “parent” application that embeds OLE objects in its documents (e.g., Microsoft Word)

  • Full Server Application: the “child” application that creates OLE objects to be represented in a Container Application (e.g. Microsoft Excel, although it is a special case of container-server dualism as we will see later)

  • MiniServer Application: particular Server Application that cannot be started as a stand-alone executable but needs a Container Application to be displayed (e.g. Microsoft Graph)

An application can be both Container and Server.
Container & Server Application
Container is the surrounding Microsoft Word; Server is Excel Spreadsheet

Now that we are clear on these concepts, let us turn to the division between Embedding and Linking:

  • Embedding: the Container contains the OLE object in its entirety, with all its data

  • Linking: the OLE object in the Container contains only a reference (a Path) to the file: its contents are not stored in the object

Linking in Microsoft Word
Checkbox to enable OLE Link Mode

An OLE object can also be edited through a sub-window of the Container window, without opening a new window (as happens when editing an Excel spreadsheet within a Word document). This behavior is known as In-Place Activation.

Storage OLE

Each OLE object has its own representation (which is similar to a mini filesystem inside Windows, with folders, called Storage, and files, called Streams) on disk, in the form of CFB files, and in memory, via method calls to the IStorage and IPersistStorage interfaces. We can see examples of CFB files by opening a .docx as an archive:

OLE Container Application
Microsoft Word Container App with an OLE object embedded
OLE binary inside .docx archive
OLE binary inside .docx archive
OLE CFB content extracted by 7zip
OLE CFB content extracted by 7zip

OLE Functions

Now, let’s imagine that we need to develop a container application and a server application, to communicate via OLE objects. We will need the IStorage and IPersistStorage interfaces to write changes to the OLE object in memory, but also to read its data in order to represent it later. Specifically for IStorage:

# CreateStorage(): Create a sub-storage (similar to a folder)
# OpenStorage(): Opens an existing sub-storage
# CreateStream(): Create a new Stream (similar to a file)
# OpenStream(): Opens an existing stream
# Commit(): Write storage changes to disk
# Revert(): Undo non-commited changes
To write the storage to disk we will instead use StgCreateDocfile().

The IOleObject interface is implemented by the server (OLE object) and allows communication and object management between the container and the OLE object, whether the object is embedded or attached. Its most important methods are:

# DoVerb(): Performs the desired action on an object based on the “verb” passed as a parameter. For example, it is used for “in-place activation”. Available “verbs” are recorded in the System Registry
# Update(): Called by the container, it invokes an update of the representation of the OLE object
# SetClientSite(): Sets the “client site”, the environment that hosts the object
# SetMoniker(): Sets a moniker to uniquely identify the object OLE

The IOleClientSite interface is implemented by the container and is unique per component. Through this an OLE object can receive information about the location and size of its representation, its moniker, its UI, and more.

# SaveObject(): Saves embedded object
# GetMoniker(): Requests object's moniker
# GetContainer(): Requests pointer to object's container
# ShowObject(): Asks container to display object
# OnShowWindow(): Notifies container when object becomes visible or invisible

The IAdviseSink interface is implemented by the container, and its methods are called by an OLE object to alert it to changes in the data, view, name, or state of the object. Here are its methods:

# IAdviseSink::OnClose()
# IAdviseSink::OnDataChange()
# IAdviseSink::OnRename()
# IAdviseSink::OnSave()
# IAdviseSink::OnViewChange()

CFB format.

If you would like a more in-depth discussion of the Microsoft format for storing OLE objects on disk, please write it in the comments and I will be happy to accommodate you. I am sending you a link to download the documentation for this format: MS-OLEDS Page


CONCLUSION

We have seen how there is a world behind the term OLE. Do an experiment: with APIMonitor, try to see how many OLE/COM calls are made when, for example, you create or modify an object: you will find matches with the theory we have learned so far.

What can we say…see you next time!



I new mates!