The previous example treated the IApeClass interface as a class-level interface that was specific to classes that expose the IApe interface from their instances. This interface allows clients to create new objects or find existing objects, but in either case, the resultant objects need to implement the IApe interface. If a new class wanted to allow clients to create or find non-IApe-compatible objects, its class object would need to implement a different interface. Because creating and finding objects are common requirements that most classes will want to support, COM defines standard interfaces for modeling object discovery and creation generically. One standard interface for object discovery is called IOleItemContainer:
// from oleidl.idl
[ object,uuid(0000011c-0000-0000-C000-000000000046) ]
interface IOleItemContainer : IOleContainer {
// ask for object named by pszItem
HRESULT GetObject(
[in] LPOLESTR pszItem, // which object?
[in] DWORD dwSpeedNeeded, // deadline
[in, unique] IBindCtx *pbc,// binding info
[in] REFIID riid, // which interface?
[out, iid_is(riid)] void **ppv); // put it here!
// remaining methods deleted for clarity
}
Note that the GetObject method allows the client to specify the type of the resultant interface pointer. The actual class of the resultant object is context sensitive and dependent upon the particular implementation of IOleItemContainer. The following example asks the Gorilla class object to find the object named “Ursus”:
HRESULT FindUrsus(IApe * &rpApe) {
// bind a reference to the Gorilla class object
rpApe = 0;
IOleItemContainer *poic = 0;
HRESULT hr = CoGetClassObject(CLSID_Gorilla, CLSCTX_ALL,0,
IID_IOleItemContainer, (void**)&poic);
if (SUCCEEDED(hr)) {
// ask Gorilla class object for Ursus
hr = poic->GetObject(OLESTR("Ursus"),
BINDSPEED_INDEFINITE, 0,
IID_IApe, (void**)&rpApe);
poic->Release();
}
return hr;
}
Although this usage is completely legal, the IOleItemContainer interface was designed to work in tandem with the Item Moniker, which is discussed later in this chapter.
COM also defines a standard interface for object creation. This interface is called IClassFactory:
// from unkwn.idl
[object,uuid(00000001-0000-0000-C000-000000000046)]
interface IClassFactory : IUnknown {
HRESULT CreateInstance([in] IUnknown *pUnkOuter,
[in] REFIID riid,
[out, iid_is(riid)] void **ppv);
HRESULT LockServer([in] BOOL bLock);
}
Although instances of a class could export the IClassFactory interface, this interface is usually exported by class objects only. Class objects are not required to implement IClassFactory, but, for uniformity, they often do. At the time of this writing, classes that need to integrate into the Microsoft Transaction Server environment must implement IClassFactory (in fact, no other class object interfaces will be recognized under MTS).
The IClassFactory interface has two methods: LockServer and CreateInstance. The LockServer method is called internally by COM during out-of-process activation requests and is discussed in detail in Chapter 6. The CreateInstance method is used to request that the class object create a new instance of the class. As was the case for IApeClass::CreateApe, the type of object that will be instantiated is determined by the class object to which the client is sending the CreateInstance request. The first CreateInstance parameter is used in COM aggregation and is discussed in Chapter 4. For the purposes of this chapter’s discussion, this parameter must always be null. The second and third parameters of CreateInstance allow the method to return a dynamically typed interface pointer to the client.
Assuming that the gorilla class object exports the IClassFactory inter-
face instead of IApeClass, clients must now use the IClassFactory:: CreateInstance method to create new Gorilla instances:
HRESULT CreateAGorillaAndEatBanana() {
IClassFactory *pcf = 0;
// find the class object
HRESULT hr = CoGetClassObject(CLSID_Gorilla, CLSCTX_ALL,0,
IID_IClassFactory, (void**)&pcf);
if (SUCCEEDED(hr)) {
IApe *pApe = 0;
// use the gorilla class object to create a gorilla
hr = pcf->CreateInstance(0, IID_IApe, (void**)&pApe);
// we’re done with the class object, so release it
pcf->Release();
if (SUCCEEDED(hr)) {
// tell the new gorilla to eat a banana
hr = pApe->EatBanana();
pApe->Release();
}
}
return hr;
}
This code is semantically identical to the version of the function that used the IApeClass interface instead of the IClassFactory interface.
To allow the previous example to work properly, the gorilla class object needs to implement IClassFactory:
class GorillaClass : public IClassFactory {
public:
IMPLEMENT_UNKNOWN_NO_DELETE(GorillaClass)
BEGIN_INTERFACE_TABLE(GorillaClass)
IMPLEMENTS_INTERFACE(IClassFactory)
END_INTERFACE_TABLE()
STDMETHODIMP CreateInstance(IUnknown *pUnkOuter,
REFIID riid, void **ppv) {
*ppv = 0;
if (pUnkOuter != 0) // we don’t support aggregation yet
return CLASS_E_NOAGGREGATION;
// create a new instance of our C++ class Gorilla
Gorilla *p = new Gorilla;
if (p == 0)
return E_OUTOFMEMORY;
// increment reference count by one
p->AddRef();
// store the resultant interface pointer into *ppv
HRESULT hr = p->QueryInterface(riid, ppv);
// decrement reference count by one, which will delete the
// object if QI fails
p->Release();
// return result of Gorilla::QueryInterface
return hr;
}
STDMETHODIMP LockServer(BOOL bLock);
};
The implementation of LockServer will be discussed later in this chapter. Note that the implementation of CreateInstance first creates a new C++ object based on the class Gorilla and asks the object if it supports the requested interface. If the object does support the requested interface, then the QueryInterface call will trigger a call to AddRef and the client will ultimately make the corresponding call to Release. If QueryInterface fails, then some mechanism is needed for deleting the newly created object. The preceding example uses the standard technique of bracketing the call to QueryInterface between an AddRef/Release pair. If the QueryInterface call fails, then the Release call will bring the reference count to zero, triggering the deletion of the object. If the call to QueryInterface succeeds, then the Release call will bring the reference count to one. The remaining reference belongs to the client, who will make the final call to Release when the object is no longer needed.