Associating Code with Storage

Being the experienced Windows user that you are, you know that Windows allows you to associate an application with a file so that when you double-click the filename in the system shell, you automatically launch the associated application and it opens the file. OLE, in fact, takes file association a little further, allowing you to assign to a file the CLSID of some code that can work with that file. In addition, OLE provides essentially the same association capabilities for storage and stream elements within a hierarchy (including the root storage). This again associates some CLSID with those elements to identify the code that can read and write the data.

When some client (or the system shell) wants to run code that knows how to work with the information in that element, it can take the CLSID and call CoCreateInstance. The interfaces this caller might request are the topic of Chapter 8. They will be from among IPersistFile, IPersistStorage, IPersistStream, and IPersistStreamInit. Through these interfaces, the client can make the object aware of the filename, storage element, or stream element in which the data resides.

To this end, OLE provides a few API functions to assign and retrieve the CLSID associated with some type of storage:

API Function

Description

WriteClassStg

Serializes a CLSID into an OLE-controlled stream within a given storage, associating that CLSID with the storage. This API calls IStorage::SetClass, which creates a stream named "\001CompObj", in which it writes the CLSID. Calling WriteClassStg for a root storage associates the entire compound file with that CLSID.

ReadClassStg

Reads the CLSID from the stream created through WriteClassStg. This API calls IStorage::Stat and returns the clsid field of the STATSTG structure.

WriteClassStm

Serializes a CLSID into a stream starting at the current seek offset. By convention, a CLSID must appear before any application or component-specific data within a stream.*

ReadClassStm

Reads a CLSID from the current seek offset in a stream, expecting the format written by WriteClassStm. Again, by convention the CLSID appears before any custom information does.

GetClassFile

Returns a CLSID associated with a particular filename. (See the discussion that follows.)


* As we'll see in Chapter 8, a client can have multiple components write their persistent data into the same stream, writing a CLSID before each object's private data. The first CLSID in the entire stream should identify the client that understands the remaining stream contents.

In addition, OLE offers two other API functions for writing and reading format information into a storage object. This information consists of a clipboard format value (a CF_* value or a registered one) and a "user type," which is a user-readable string describing the data type as the end user understands it—for example, "Rich Text". Such information is quite useful for servers that want to emulate others because it describes the internal format of information in streams. We'll see where these functions become important in the samples of later chapters.

WriteFmtUserTypeStg

Serializes a clipboard format and a user-readable name describing the format of the contents of the storage. If the clipboard format is a registered one, this function stores the registered string instead of the value.

ReadFmtUserTypeStg

Reads the clipboard format and the string previously written by WriteFmtUserTypeStg. If the clipboard format is a string, it registers the format and returns the value.


The matter of associating a CLSID with a storage or stream object is entirely handled through [Read | Write]Class[Stg | Stm]. Associating a CLSID with a file presents a few additional concerns. First of all, if the file is a compound file, [Read | Write]ClassStg apply perfectly well to the root storage in that file, as they do to any other storage element. But what if the file is not a compound file? OLE still allows you to create an association in two other ways, using additional registry entries.

The first method is to create a registry entry for the file's extension of the form:


\
.<ext> = <ProgID>

<ProgID> = <Name of class>
CLSID = {<CLSID in hex>}

CLSID
{<CLSID in hex>}
[Inproc | Local]Server32 = <path to server module>

The first entry in this list is a file extension with a period. (The string ".doc" is an example.) The value of this key is the ProgID under which OLE can locate the CLSID associated with this file type. The .<ext> entry is the same one that Windows itself uses to associate extensions with applications. What differs with OLE is the entries under the <ProgID> entry. Whereas Windows stores varied information under a shell subkey, OLE needs only the CLSID entry.

The second method of association is a little more involved. OLE defines a root registry key named FileType, under which appear entries in the following form:


\
FileType
{<CLSID in hex>}
<type id> = <offset>,<cb>,<mask>,<value>
<type id> = <offset>,<cb>,<mask>,<value>
<type id> = <offset>,<cb>,<mask>,<value>
§

Each <type id> key, which is some integer unique for the CLSID, describes a byte pattern <value> that will match <cb> bytes in an associated file found at <offset> (from the beginning of that file) when a bitwise AND operation has been performed on those file bytes with <mask> (which may be omitted to indicate that no mask is necessary). Here are the patterns for Microsoft Word 6.0 as an example:


\
FileType
{00020900-0000-0000-C000-000000000046}
0 = 0,2,FFFF,DBA5
1 = 0,2,FFFF,9BA5

The GetClassFile function will use these byte patterns in their registered order if the given file is not a compound file with a CLSID in it already. If GetClassFile cannot match these byte patterns, it will attempt to associate the file by extension; otherwise, it fails with MK_E_INVALIDEXTENSION.