As I mentioned earlier, both the DKoala1 and the EKoala1 server contain exactly the same implementation of the Koala object. In fact, their class factory implementations are almost identical as well, and their registry entries differ only by the InprocServer32 and LocalServer32 entries.
As you know by now, each server has to maintain a count of its current objects so it can properly control unloading. Typically this will be a global variable. But because I don't want the Koala object tied to a global variable in some other source file, I instead have the class factory pass Koala's constructor (CKoala::CKoala) a pointer to an "object destroyed" function with the following prototype:
void ObjectDestroyed(void)
The Koala object stores the pointer in m_pfnDestroy and calls the function when destroying itself in Release:
STDMETHODIMP_(ULONG) CKoala::Release(void)
{
if (0L!=--m_cRef)
return m_cRef;
if (NULL!=m_pfnDestroy)
(*m_pfnDestroy)();
delete this;
return 0;
}
This little trick eliminates any compile-time relationship between the server code and the object code, making it a run-time relationship instead. Koala doesn't call ObjectDestroyed by name; it calls a function that matches the prototype, a pointer it receives when constructed. This trick makes such an object rather portable between different server modules, which you might find useful.
The second point about the Koala object is that it is also given a pUnkOuter pointer if it's created as part of an aggregation. Because Koala implements only IUnknown, this is pretty useless in and of itself. I included it here not only to show how the IClassFactory implementation should handle the aggregation scenario but also to make a clean object framework that can be easily extended with additional interfaces that might be used in aggregation. If you use this code as such a base, any additional interfaces' IUnknown members should delegate to the pUnkOuter pointer passed to Koala's constructor, which was demonstrated in Chapter 2.
Both KOALA servers define for their class factory objects a C++ class CKoalaClassFactory, which singly inherits from IClassFactory. The definitions (in DKOALA1.H and EKOALA1.H) are identical, with the only member variable being a reference count. In the same manner, the IUnknown members in both implementations (DKOALA1.CPP and EKOALA1.CPP) are also identical: the class factory deletes itself when its reference count goes to 0, as usual. Nothing else special happens in Release because the class factory's existence has little to do with the server's own lifetime.
The CKoalaClassFactory members CreateInstance and LockServer appear as follows in DKoala1:
//From DKOALA1.CPP
STDMETHODIMP CKoalaClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
, REFIID riid, PPVOID ppvObj)
{
PCKoala pObj;
HRESULT hr;
*ppvObj=NULL;
hr=ResultFromScode(E_OUTOFMEMORY);
if (NULL!=pUnkOuter && IID_IUnknown!=riid)
return ResultFromScode(CLASS_E_NOAGGREGATION);
pObj=new CKoala(pUnkOuter, ObjectDestroyed);
if (NULL==pObj)
return hr;
if (pObj->Init())
hr=pObj->QueryInterface(riid, ppvObj);
if (FAILED(hr))
delete pObj;
else
g_cObj++;
return hr;
}
STDMETHODIMP CKoalaClassFactory::LockServer(BOOL fLock)
{
if (fLock)
g_cLock++;
else
g_cLock--;
return NOERROR;
}
Here you can see a number of features. First CreateInstance verifies that aggregation rules are being followed correctly if pUnkOuter is non-NULL. We then create an instance of the Koala object (the C++ class CKoala), passing to the constructor the outer unknown and a pointer to the object destroyed function named ObjectDestroyed. If the C++ new operator succeeds, we have a new C++ object but not an interface pointer, so we initialize the object and query for an interface pointer if initialization succeeds. The QueryInterface call conveniently calls AddRef as well. If all of this succeeds, we increment the global counter g_cObj and return. Along similar lines the LockServer function increments or decrements another global counter, g_cLock. We could have LockServer fiddle with g_cObj instead of a separate counter, but having two counters greatly simplifies debugging objects and locks separately.
Again, EKoala1's implementation is almost identical. Can you spot the differences?
//From EKOALA1.CPP
STDMETHODIMP CKoalaClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
, REFIID riid, PPVOID ppvObj)
{
PCKoala pObj;
HRESULT hr;
*ppvObj=NULL;
hr=ResultFromScode(E_OUTOFMEMORY);
if (NULL!=pUnkOuter && IID_IUnknown!=riid)
return ResultFromScode(CLASS_E_NOAGGREGATION);
pObj=new CKoala(pUnkOuter, ObjectDestroyed);
if (NULL==pObj)
{
g_cObj++;
ObjectDestroyed();
return hr;
}
if (pObj->Init())
hr=pObj->QueryInterface(riid, ppvObj);
g_cObj++;
if (FAILED(hr))
{
delete pObj;
ObjectDestroyed();
}
return hr;
}
STDMETHODIMP CKoalaClassFactory::LockServer(BOOL fLock)
{
if (fLock)
g_cLock++;
else
{
g_cLock--;
g_cObj++;
ObjectDestroyed();
}
return NOERROR;
}
The differences exist because EKoala1 was launched for no reason other than to create an object. If creation fails, the server shuts itself down—no reason to keep hogging memory uselessly! As we'll see shortly, an EXE server has to start its own shutdown when its last object is destroyed or the last lock removed. This requirement lets us isolate shutdown initiation code inside a function such as ObjectDestroyed, which tests for the right conditions and posts a WM_CLOSE message to the main hidden window to generate a PostQuitMessage call:
//From EKOALA1.CPP
void ObjectDestroyed(void)
{
g_cObj--;
//No more objects and no locks; shut the application down.
if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd))
PostMessage(g_hWnd, WM_CLOSE, 0, 0L);
return;
}
So anywhere else that we need to start shutdown, we can fake an object destruction by incrementing g_cObj and calling ObjectDestroyed directly as when new fails, when object initialization or QueryInterface fails, and inside LockServer as well. ObjectDestroyed worries about testing the conditions.
This is actually the full extent of how EKoala1 terminates itself when the last lock is removed and g_cObj is zero or when g_cLock is zero and the last Koala object goes away, in which case Koala incorrectly calls ObjectDestroyed.
DKoala1, on the other hand, doesn't unload itself but implements DllCanUnloadNow instead to return S_OK or S_FALSE for the same conditions under which EKoala1 terminates. Correspondingly, its ObjectDestroyed function does nothing more than decrement g_cObj:
//From DKOALA1.CPP
STDAPI DllCanUnloadNow(void)
{
SCODE sc;
sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
return ResultFromScode(sc);
}
void ObjectDestroyed(void)
{
g_cObj--;
return;
}
To expose its class factory, a DLL needs only to implement and export DllGetClassObject. This function looks quite similar to IClassFactory::CreateInstance:
//From DKOALA1.CPP
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, PPVOID ppv)
{
HRESULT hr;
CKoalaClassFactory *pObj;
if (CLSID_Koala!=rclsid)
return ResultFromScode(E_FAIL);
pObj=new CKoalaClassFactory();
if (NULL==pObj)
return ResultFromScode(E_OUTOFMEMORY);
hr=pObj->QueryInterface(riid, ppv);
if (FAILED(hr))
delete pObj;
return hr;
}
The only special note to make here is that DllGetClassObject should always validate the CLSID passed to it, as done here. You don't want to return a class factory for the wrong CLSID!
An EXE server, on the other hand, must first check for the -Embedding flag and register its class factories using CoRegisterClassObject. In EKoala1, we should do all of this in CApp::Init, which registers the class factory for multiple use. (Note that -Embedding always appears in ANSI characters, as does the entire command line.)
BOOL CApp::Init(void)
{
HRESULT hr;
//Fail if we're run outside of CoGetClassObject.
if (lstrcmpiA(m_pszCmdLine, "-Embedding")
&& lstrcmpiA(m_pszCmdLine, "/Embedding"))
return FALSE;
if (FAILED(CoInitialize(NULL)))
return FALSE;
m_fInitialized=TRUE;
[Register window class and create main hidden window.]
m_pIClassFactory=new CKoalaClassFactory();
if (NULL==m_pIClassFactory)
return FALSE;
m_pIClassFactory->AddRef();
hr=CoRegisterClassObject(CLSID_Koala, m_pIClassFactory
, CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &m_dwRegCO);
if (FAILED(hr))
return FALSE;
return TRUE;
}
If EKoala1 served more than one CLSID, it would repeat the process of creating a class factory and calling CoRegisterClassObject for each CLSID. In the preceding code, the m_dwRegCO variable receives the registration key for our single class factory. If we had multiple class factories, we'd need to have an array of such keys, of course. Also, EKoala1 does not allow itself to run stand-alone: if -Embedding does not appear, EKoala1 terminates immediately. If you are writing a server that can run stand-alone, use -Embedding to determine whether you must register all your class factories or skip them all. A server will generally register its multiple-use class factories with or without -Embedding, but it only registers single-use ones when the flag is present.
Because this class factory is registered as REGCLS_MULTIPLE_USE, this same class factory is used to create objects for any number of separate clients. To demonstrate this, run two or more instances of ObjectUser, and in each choose Use EXE Object followed by Create (CoCreateInstance). Now run a tool like PView and look at the loaded modules. Because EKoala1 registers itself for multiple use, only one instance of the EXE will appear in the list. Notice that the time it takes to create the first object is longer than the time to create subsequent instances. EKoala1 is already running, so we don't have to incur the overhead of launching another instance. Now change EKoala1 to use REGCLS_SINGLE_USE, and run the same test with multiple instances of ObjectUser. This time you'll see multiple instances of EKoala1, each servicing only a single object.
The final piece of EKoala1's implementation is revoking and releasing its class factory on shutdown. This happens in the application destructor CApp::~CApp:
CApp::~CApp(void)
{
if (0L!=m_dwRegCO)
CoRevokeClassObject(m_dwRegCO);
if (NULL!=m_pIClassFactory)
m_pIClassFactory->Release();
if (m_fInitialized)
CoUninitialize();
return;
}
Take a moment to reflect on the amount of code that we've seen in the last two sections. Even if we include the implementations of the IUnknown members of CKoalaClassFactory and CKoala, there are really only a few dozen lines of code in DKoala1 and EKoala1 that deal with the class factory and the objects that it creates.
Such code is the core of any custom component in OLE, which includes OLE Automation objects, OLE Document objects, OLE Controls, and any other type of component you want to make with OLE and access using a CLSID. The core functionality for exposing an object in a component is very simple and occupies little code. As we have seen in the last few pages, none of this code is at all complex—it's just as simple as writing any other exported functions or creating something such as a window class. If you take the time to understand these fundamental mechanisms, most everything else you might implement with OLE will make a lot more sense and will cease to be something overly complex and foreign. More complex objects simply have more interfaces than the KOALA object has.
OLE has been called "difficult" and "hard," a myth perpetuated largely by competitive marketing efforts and by people who stand to win big if you believe the myth. Certainly the more OLE technologies you use and the more complex the problems you're trying to solve, the more complex your programming will become. But this is true of any programming.
So understand that any belief you might have that OLE is just too damned hard is just that—a belief. And beliefs often do not correspond to reality.