Monikers and Persistence

No discussion of monikers would be complete without a discussion of the
File Moniker. Recall that COM supports three activation primitives: binding to class objects, binding to new class instances, and binding to persistent
objects stored in files. This chapter has explored the first two of these primitives in detail. The third primitive is based on the COM API function
CoGetInstanceFromFile:

HRESULT CoGetInstanceFromFile(
    [in, unique] COSERVERINFO *pcsi, // host/security info
    [in, unique] CLSID      *pClsid,   // explicit CLSID (opt)
    [in, unique] IUnknown *punkOuter,// for aggregation
    [in] DWORD dwClsCtx,   // locality?
    [in] DWORD grfMode,   // file open mode
    [in] OLECHAR *pwszName,   // file name of object
    [in] DWORD cmqi,   // how many interfaces?
   [out, size_is(cmqi)] MULTI_QI *prgmq // where to put itfs
);

This routine accepts as input a filename that refers to the persistent state of an object.13 CoGetInstanceFromFile ensures that the object is running and then returns one or more interface pointers to the [re]activated object. To perform this task, CoGetInstanceFromFile first needs to determine the CLSID of the object. The CLSID is needed for two reasons. If the object is not running, COM will need this CLSID to create a new instance to be initialized from the persistent image. Second, if the caller does not specify an explicit host name to forward the activation call to, COM will use the CLSID to determine which machine to activate the object at.14

If the CLSID is not passed explicitly by the caller, the CoGetInstanceFromFile derives the CLSID from the file itself by calling the COM API function GetClassFile:

HRESULT GetClassFile([in, string] OLECHAR *pwszFileName, 
                   [out] CLSID *pclsid);

GetClassFile uses header information in the file as well as registry information to determine what type of object is contained in the file.

Once the class and host machine are determined, COM examines the Running Object Table (ROT) on the target host machine to determine whether the object has already been activated. The ROT is a facility of the SCM that maps arbitrary monikers onto running instances on the local host machine. Persistent objects are expected to register themselves at load time in the local ROT. To represent the persistent object’s filename as a moniker, COM provides a standard moniker type called the File Moniker that wraps a filename behind the IMoniker interface. File monikers can be created either by passing the file name to MkParseDisplayName or by calling the explicit API function CreateFileMoniker:

HRESULT CreateFileMoniker(
       [in, string] const OLECHAR *pszFileName,
       [out] IMoniker **ppmk);

If the persistent object has already registered its File Moniker in the ROT, CoGetInstanceFromFile simply returns a pointer to the already running object. If the object is not found in the ROT, COM creates a new instance of the file’s class and initializes it from the persistent image via the instance’s IPersistFile::Load method:

[ object, uuid(0000010b-0000-0000-C000-000000000046) ]
interface IPersistFile : IPersist {
// called by CoGetInstanceFromFile to initialize object
  HRESULT Load(
      [in, string] const OLECHAR * pszFileName,
      [in] DWORD grfMode
  );
// remaining methods deleted for clarity
}

It is the responsibility of the object implementation to load any persistent state from the file as well as to register itself with the local ROT to ensure that only one instance per file is running at a time:

STDMETHODIMP Gorilla::Load(const OLECHAR *pszFileName,
                         DWORD grfMode) {
// read in persisted object state
  HRESULT hr = this->MyReadStateFromFile(pszFile, grfMode);
  if (FAILED(hr)) return hr;
// get pointer to ROT from SCM
  IRunningObjectTable *prot = 0;
  hr = GetRunningObjectTable(0, &prot);
  if (SUCCEEDED(hr)) {
// create a file moniker to register in ROT
    IMoniker *pmk = 0;
    hr = CreateFileMoniker(pszFileName, &pmk);
    if (SUCCEEDED(hr)) {
// register self in ROT
      hr = prot->Register(0, this, pmk, &m_dwReg);
      pmk->Release();
    }
    prot->Release();
  }
  return hr;
}

The newly created instance’s IPersistFile::Load method will be called by the SCM during the execution of CoGetInstanceFromFile. The example above uses the COM API function GetRunningObjectTable to get an IRunningObjectTable interface pointer into the SCM. It then uses this in-
terface to register its moniker in the ROT so that subsequent calls to
CoGetInstanceFromFile using the same filename will not create new objects but instead will return references to this object.15

The File Moniker exists for two reasons. One reason is to allow objects to register themselves in the ROT so that CoGetInstanceFromFile can find them. The second reason is to hide the use of CoGetInstanceFromFile from the client behind the IMoniker interface. The File Moniker’s implementation of BindToObject simply calls CoGetInstanceFromFile:

// pseudo-code from OLE32.DLL
STDMETHODIMP FileMoniker::BindToObject(IBindCtx *pbc,
               IMoniker *pmkToLeft, 
               REFIID riid, void **ppv) {
// assume failure
  *ppv = 0;
  HRESULT hr = E_FAIL;
  if (pmkToLeft == 0) { // no moniker to left
    MULTI_QI mqi = { &riid, 0, 0 };
    COSERVERINFO *pcsi;
    DWORD grfMode;
    DWORD dwClsCtx;
// these three parameters are attributes of the BindCtx
    this->MyGetFromBindCtx(pbc, &pcsi, &grfMode, &dwClsCtx);
    hr = CoGetInstanceFromFile(pcsi, 0, 0, dwClsCtx, 
                             grfMode, this->m_pszFileName,
                             1, &mqi);
    if (SUCCEEDED(hr)) 
      *ppv = mqi.pItf;
  }
  else { // there’s a moniker to the left
    // ask object to left for IClassActivator 
    // or IClassFactory
  }
  return hr;
}

Given the behavior of the File Moniker, the following function that calls CoGetInstanceFromFile:

HRESULT GetCornelius(IApe * &rpApe) {
  OLECHAR *pwszObject =
             OLESTR("\\\\server\\public\\cornelius.chmp");
  MULTI_QI mqi = { &IID_IApe, 0, 0 };
  HRESULT hr = CoGetInstanceFromFile(0, 0, 0, CLSCTX_SERVER, 
                     STGM_READWRITE, pwszObject, 1, &mqi);
  if (SUCCEEDED(hr)) 
    rpApe = mqi.pItf;
  else
    rpApe = 0;
  return hr;
}

could be simplified by calling CoGetObject instead:

HRESULT GetCornelius(IApe * &rpApe) {
  OLECHAR *pwszObject =
             OLESTR("\\\\server\\public\\cornelius.chmp");
  return CoGetObject(pwszObject,0,IID_IApe, (void**)&rpApe);
}

As was the case when the Class Moniker was used previously, the level of indirection afforded by CoGetObject allows the client to specify arbitrarily complex activation policies without changing one line of code.

13 An alternative version of this API function, CoGetInstanceFromIStorage, accepts a pointer to a hierarchical storage medium instead of a filename.

14 In addition to the normal rerouting of CLSIDs to host machines that is used by CoGetClassObject/CoCreateInstanceEx, CoGetInstanceFromFile can use the UNC host name of the file to reroute the activation request to the host machine where the file resides. This activation mode is referred to by the COM Specification as “AtBits” activation and is designated using the “ActivateAtStorage” registry setting described in Chapter 6.

15 Technically, the ROT is not a machine-wide table but rather a Winstation-wide table, which means that by default, not all logon sessions will have access to the object. To ensure that the object is visible to all possible clients, the object should specify the ROTFLAGS_ALLOWANYCLIENT flag when calling IRunningObjectTable::Register.

© 1998 by Addison Wesley Longman, Inc. All rights reserved.