Optimizations

One of the advantages of having a standard interface for instantiation is that COM can provide a more efficient technique for instantiation. Consider the following code, which creates a new instance of the class Chimp:

HRESULT CreateChimp(/*[out]*/ IApe * &rpApe) {
  extern const CLSID CLSID_Chimp;
  rpApe = 0;
  IClassFactory *pcf = 0;
  HRESULT hr = CoGetClassObject(CLSID_Chimp, CLSCTX_ALL, 0, 
                         IID_IClassFactory, (void**)&pcf);
  if (SUCCEEDED(hr)) {
    hr = pcf->CreateInstance(0, IID_IApe, (void**)&rpApe);
    pcf->Release();
  }
  return hr;
}

This code performs one operation: creating a Chimp object. Note that to perform one operation, three suboperations were required (CoGetClassObject, CreateInstance, Release). If the server is loaded as an in-process server, these three suboperations will not be particularly expensive. However, if the server is an out-of-process or off-host server, then each of these suboperations will require a round trip between the client and server processes. Although COM uses a very efficient IPC/RPC transport, each of these suboperations will have a nontrivial performance cost. Ideally, it would be better to ask COM to go to the server process once and, while there, use the class object to call CreateInstance on behalf of the client. If the class object is being used only to implement IClassFactory, this technique will be more efficient than the three-step technique shown earlier. COM provides an API function, CoCreateInstanceEx, that subsumes the functionality of CoGetClassObject and IClassFactory::CreateInstance to allow single round trip creation of new objects.

CoCreateInstanceEx allows the client to specify a CLSID that identifies the kind of object to instantiate. On successful completion, CoCreateInstanceEx returns one or more interface pointers that refer to a new instance of the specified class. When using CoCreateInstanceEx, the client never sees the intermediate class object that is used to instantiate the object. However, server implementors do not need to implement any additional functionality to enable clients to call CoCreateInstanceEx. From the server’s perspective, all that is required is to expose its class objects as required by CoGetClassObject. The implementation of CoCreateInstanceEx will use the same techniques used by CoGetClassObject to find the corresponding class object. The primary difference is that once the class object is found, CoCreateInstanceEx takes the additional step of calling IClassFactory::CreateInstance potentially followed by one or more calls to QueryInterface all while executing in the process of the class object. If the activation request must be satisfied by a distinct process, this can result in considerable performance savings.

Like CoGetClassObject, CoCreateInstanceEx allows the client to specify the desired CLSCTX and COSERVERINFO parameters. In addition, CoCreateInstanceEx allows the client to ask for more than one interface pointer to the newly created object. It does this by allowing the client to pass an array of MULTI_QI structures that will be used to call QueryInterface on the new instance while executing in the server’s process:

typedef struct tagMULTI_QI {
// which interface is desired?
  const IID *piid; 
// null on input, will contain the pointer on output
  [iid_is(piid)] IUnknown *pItf;  
// will contain the HRESULT from QueryInterface on output
  HRESULT hr;  
} MULTI_QI;

By allowing the client to request more than one interface pointer to the new object, the client will not need to call QueryInterface explicitly if more than one type of interface pointer is desired. Because these QueryInterface calls will be made by COM on behalf of the client while running inside the process of the class object, no additional client-object round trips will be needed. Note that each of the interface pointers returned by CoCreateInstanceEx will point to the same object. COM does not provide an intrinsic operation for creating multiple instances in one round trip.

Armed with an understanding of the MULTI_QI structure, the CoCreateInstanceEx definition is fairly simple to understand:

HRESULT CoCreateInstanceEx(
       [in] REFCLSID rclsid,   // what kind of object?
       [in] IUnknown *pUnkOuter, // for aggregation
       [in] DWORD dwClsCtx,   // locality?
       [in] COSERVERINFO *pcsi,   // host/security info
       [in] ULONG cmqi,   // how many interfaces?
[out, size_is(cmqi)] MULTI_QI *prgmq); // where to put itfs

If all of the requested interfaces are available on the new object, CoCreateInstanceEx returns S_OK. If at least one but not all of the requested interfaces are available, then CoCreateInstanceEx returns CO_S_
NOTALLINTERFACES, indicating partial success. The caller must then inspect the individual HRESULTs in each MULTI_QI structure to determine which interfaces were available and which were not. If CoCreateInstanceEx is unable to create an object or none of the requested interfaces are available, then CoCreateInstanceEx returns a SEVERITY_ERROR-based HRESULT indicating why the operation failed.

CoCreateInstanceEx is extremely efficient when multiple types of interfaces are needed. Consider the following additional interface definition:

[object,uuid(753A8F7C-A7FF-11d0-8C30-0080C73925BA)]
interface IEgghead : IUnknown {
  import "unknwn.idl";
  HRESULT ContemplateNavel(void);
}

Given this interface definition, the client can now bind both types of pointers to a new chimpanzee in one client-server round trip:

void CreateChimpEatBananaAndThinkAboutIt(void) {
// declare and initialize an array of MULTI_QI’s
  MULTI_QI rgmqi[2] = 
     { { &IID_IApe, 0, 0 }, { &IID_IEgghead, 0, 0 } };
  HRESULT hr = CoCreateInstanceEx(
                      CLSID_Chimp, // make a new chimp
                      0,          // no aggregation
                      CLSCTX_ALL,  // any locality
                      0, // no explicit host/security info
                      2, // asking for two interfaces
                      rgmqi); // array of MULTI_QI structs
  if (SUCCEEDED(hr)) { 
// hr may be CO_S_NOTALLINTERFACES, so check each result
    if (hr == S_OK // SUCCEEDED(rgmqi[0].hr)) {
// it is safe to blindly cast the resultant ptr to the type
// that corresponds to the IID used to request the interface
      IApe *pApe = reinterpret_cast<IApe*>(rgmqi[0].pItf);
      assert(pApe);
      HRESULT hr2 = pApe->EatBanana();
      assert(SUCCEEDED(hr2));
      pApe->Release();
    }
    if (hr == S_OK // SUCCEEDED(rgmqi[1].hr)) {
      IEgghead *peh = 
   reinterpret_cast<IEgghead*>(rgmqi[1].pItf);
      assert(peh);

Figure 3.3 COCreateInstanceEx

      HRESULT hr2 = peh->ContemplateNavel();
      assert(SUCCEEDED(hr2));
      peh->Release();
    }
  }
}

Figure 3.3 illustrates the steps that are taken by CoCreateInstanceEx to create a new object. It is important to note that both resultant pointers will point to the same object. If two distinct objects are required, then two distinct calls to CoCreateInstanceEx are required.

Using CoCreateInstanceEx is fairly straightforward if only one interface is desired:

HRESULT CreateChimpAndEatBanana(void) {
// declare and initialize a MULTI_QI
  MULTI_QI mqi = { &IID_IApe, 0, 0 };
  HRESULT hr = CoCreateInstanceEx(
                      CLSID_Chimp, // make a new chimp
                      0,          // no aggregation
                      CLSCTX_ALL,  // any locality
                      0, // no explicit host/security info
                      1, // asking for one interface
                      &mqi); // array of MULTI_QI structs
  if (SUCCEEDED(hr)) {
    IApe *pApe = reinterpret_cast<IApe*>(mqi.pItf);
    assert(pApe);
// use the new object
    hr = pApe->EatBanana();
// release the interface pointer
    pApe->Release();
  }
  return hr;
}

If only one interface is needed and no COSERVERINFO will be passed, COM provides a somewhat more convenient version of CoCreateInstanceEx called CoCreateInstance:10

HRESULT CoCreateInstance(
       [in] REFCLSID rclsid,   // what kind of object?
       [in] IUnknown *pUnkOuter,   // for aggregation
       [in] DWORD dwClsCtx,   // locality?
       [in] REFIID riid,   // what kind of interface
       [out, iid_is(riid)] void **ppv); // where to put itf

The preceding example becomes much simpler when CoCreateInstance
is used:

HRESULT CreateChimpAndEatBanana(void) {
  IApe *pApe = 0;
  HRESULT hr = CoCreateInstance(
                      CLSID_Chimp, // make a new chimp
                      0,          // no aggregation
                      CLSCTX_ALL,  // any locality
                      IID_IApe, // what kind of itf
                      (void**)&pApe); // where to put itf
  if (SUCCEEDED(hr)) {
    assert(pApe);
// use the new object
    hr = pApe->EatBanana();
// release the interface pointer
    pApe->Release();
  }
  return hr;
}

The previous two examples are functionally equivalent. In fact, the implementation of CoCreateInstance simply calls CoCreateInstanceEx internally:

// pseudo-code for implementation of CoCreateInstance API
HRESULT CoCreateInstance(REFCLSID rclsid, 
                  IUnknown *pUnkOuter, DWORD dwClsCtx,
                  REFIID riid, void **ppv) {
  MULTI_QI rgmqi[] = { &riid, 0, 0 };
  HRESULT hr = CoCreateInstanceEx(rclsid, pUnkOuter, 
                            dwClsCtx, 0, 1, rgmqi);
  *ppv = rgmqi[0].pItf;
  return hr;
}

Although it is possible to issue a remote activation request using CoCreateInstance, the lack of a COSERVERINFO parameter does not allow the caller to specify an explicit host name. Instead, calling CoCreateInstance and specifying only the CLSCTX_REMOTE_SERVER flag informs the SCM to use per-CLSID configuration information to select the host machine that will be used to activate the object.

Figure 3.4 COCreateInstanceEx versus CoGetClassObject

Figure 3.4 shows how the parameters of CoCreateInstanceEx map onto the parameters of CoGetClassObject and IClassFactory::CreateInstance. Contrary to popular belief, CoCreateInstanceEx does not call CoGetClassObject internally. Although there is no logical difference between the two techniques, the implementation of CoCreateInstanceEx will be more efficient when creating one instance, as it avoids the extra client-server round trips that would be required if CoGetClassObject were used. If, however, large numbers of instances will be created, the client can cache the class object pointer and simply call IClassFactory::CreateInstance multiple times. Since IClassFactory:: CreateInstance is just a method call and does not go through the SCM, it is somewhat faster than calling CoCreateInstanceEx. The threshold at which it becomes more efficient to cache the class object and bypass CoCreateInstanceEx will vary based on IPC and RPC performance on the host machines and network in use.

10 Technically, CoCreateInstance came first. CoCreateInstanceEx was added in Windows NT 4.0 when it became clear that some developers would want to pass security and host information to COM’s activation API functions. In the original prototype for CoGetClassObject, the third parameter was reserved, so NT 4.0 was able to steal the reserved parameter for the COSERVERINFO. Unfortunately, CoCreateInstance had no unused parameters, so CoCreateInstanceEx was created. One could argue that a version of CoGetClassObject that uses MULTI_QI to allow more than one interface to be bound would also be useful, but alas, there is no CoGetClassObjectEx at the time of this writing. A similar argument could be made regarding IMoniker::BindToObject and MULTI_QI.

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