Полезная информация

Chapter 14
Creating ActiveX COM Objects and Custom Interfaces on Your Own


Creating ActiveX COM Objects and Custom Interfaces on Your Own

A variety of methods are available for creating COM classes based upon the MFC and ActiveX frameworks. In this chapter, you will examine methods for creating COM classes without MFC or ActiveX frameworks.

How the COM class was defined or implemented does not matter to the client application using the class. The use and function of the class are identical to the client. Defining and implementing COM classes without a framework such as MFC or ActiveX is an easy task, although additional work must be performed by the COM developer for object creation and termination.

Although application frameworks tend to make the creation of COM components easier, you will find several advantages to creating your own COM classes:

Creating COM components without the benefit of a framework also has some disadvantages. The biggest drawback is that you need to perform all of the detailed work such as creating IClassFactory interfaces, which are normally supplied by the framework.

Creating a Basic In-Process Server

In Chapter 12, two COM interfaces are defined: IFish and IBass. These interfaces are defined within a project called IFISH.DLL. The IFish and IBass interfaces are accessed through a COM class entitled CBass, which is derived from the MFC base class CCmdTarget.

In this chapter, the interfaces IFish and IBass will again be used along with a new class entitled COBass, which is used to access the interfaces. The COBass class will be implemented within a DLL and will not be derived from a framework class.

To create the basic in-process server, the New dialog will be used. To create the CUSTOMBASS project, perform the following steps:

  1. From within the Visual C++ development environment, select the command New from the File menu.

  2. Select Projects tab from the New dialog.

  3. From the Projects tab, select Win32 Dynamic Link Library. Enter the project name CUSTOMBASS into the Project name edit box. Select the OK button.

  4. The project CUSTOMBASS is now created.

Creating the Project Definition File

When creating a generic dynamic link library (DLL), the New dialog does not create a definition file (DEF). All DLLs must have a definition file that is used to define information about the project, such as functions that are exported from the library.

All DLLs that support COM classes must export some basic functions in order for the client applications to access the COM classes. From within the Visual C++ Developer Studio, create a file called CUSTOMBASS.DEF. Listing 14.1 illustrates the contents of this file.

Listing 14.1 CUSTOMBASS.DEF--DLL Library Definition File for CUSTOMBASS.DLL

LIBRARY CUSTOMBASS
DESCRIPTION `CUSTOMBASS Dynamic Link Library'
EXPORTS
DllGetClassObject
DllCanUnloadNow
DllRegisterServer
DllUnregisterServer

The function DllGetClassObject is called to create an instance of a COM Object. DLLCanUnloadNow is called periodically by the operating system to remove unused DLLs from system memory. DllRegisterServer is used by the program regsvr32.exe to allow the DLL to register all COM Objects with the Windows Registry. The function DllUnregisterServer is called to remove all registry settings for the COM Objects from the Windows Registry.

Custom COM Server Architecture

When creating a COM server without the aid of an application framework such as MFC, you need to establish a system architecture for implementing the COM model. The architecture used in the CUSTOMBASS project consists of three classes:

NOTE: While the CUSTOMBASS project utilizes three classes as its basic architecture, many different approaches can be used. The only requirement is that in all COM architectures there must be a COM Object class and a class factory for each COM class.


When implementing COM Objects without the use of a framework, the interfaces supported by the class can be implemented through several methods:

Creating the COM Class COBass

In the CUSTOMBASS project, the COM class COBass, which will implement the IFish and IBass COM interfaces, is created. You may want to refer to Chapter 12, where the IFish and IBass interfaces are implemented in the MFC-derived COM class CBass.

The class COBass is derived directly from the IFish and IBass interfaces. By definition, this makes the interface methods of IFish, IBass, and IUnknown an integral part of COBass. The class Cbass is derived from the CCmdTarget class. The IFish and IBass interfaces are added as members of the COBass class (nested interfaces).

COBass is a C++ class that is derived from the IBass and IFish interfaces. Listing 14.2 shows the class definition for COBass. This is different from CBass, which was derived from the MFC class CCmdTarget.

Listing 14.2 COBASS.H--COM Object COBass that Implements the IFish and IBass Interfaces

#if !defined(COBASS_H)
#define COBASS_H
#ifdef __cplusplus
#include "..\ifish\ifish.h"
#include "..\ifish\ibass.h"
class COBass : public IFish , public IBass
{
public:
// Main Object Constructor & Destructor.
COBass(IUnknown* pUnkOuter, CServer* pServer);
~COBass(void);
// shared IUnknown methods. Main object, non-delegating.
STDMETHODIMP QueryInterface(REFIID, PPVOID);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IFish methods
STDMETHODIMP IsFreshwater(BOOL *);
STDMETHODIMP GetFishName( LPTSTR );
// IBass methods
STDMETHODIMP GetLocation( LPTSTR );
STDMETHODIMP SetLocation( LPTSTR );
STDMETHODIMP EatsOtherFish( BOOL *);
private:
// We declare nested class interface implementations here.
// Main Object reference count.
ULONG m_cRefs;
// Outer unknown (aggregation & delegation).
IUnknown* m_pUnkOuter;
// Pointer to this component server's control object.
CServer* m_pServer;
char m_zFishName[256];
char m_zLocation[256];
BOOL m_bEatsOtherFish;
BOOL m_bFreshwater;
};
typedef COBass* PCOBass;
#endif // __cplusplus
#endif // COBASS_H

When deriving COM classes from multiple interfaces, less coding is needed to implement the COM Object and the interfaces. One of the advantages of deriving a COM class from multiple interfaces is that only one implementation of the IUnknown interfaces is required. The delegation of the IUnknown interface is also avoided. Listing 14.3 illustrates the implementation of the IUnknown interface in the COBass class.

Listing 14.3 COBASS.CPP--IUnknown Implementation for COBass

#include <windows.h>
#include <ole2.h>
#include "comutil.h"
#include "..\ifish\ifish.h"
#include "..\ifish\ibass.h"
#include "custombassid.h"
#include "server.h"
#include "cobass.h"
COBass::COBass(
IUnknown* pUnkOuter,
CServer* pServer)
{
// Zero the COM object's reference count.
m_cRefs = 0;
// No AddRef necessary if non-NULL, as we're nested.
m_pUnkOuter = pUnkOuter;
// Assign the pointer to the server control object.
m_pServer = pServer;
lstrcpy (m_zFishName, "Bass");
lstrcpy (m_zLocation, "Lilly Pads");
m_bEatsOtherFish = TRUE;
m_bFreshwater = TRUE;
}
COBass::~COBass(void)
{
}
STDMETHODIMP COBass::QueryInterface(
REFIID riid,
PPVOID ppv)
{
HRESULT hr = ResultFromScode(E_NOINTERFACE);
*ppv = NULL;
if (IID_IUnknown == riid || IID_IFish == riid)
{
*ppv = (IFish *)this;
}
else if (IID_IBass == riid)
{
*ppv = (IBass *)this;
}
if (NULL != *ppv)
{
// We've handed out a pointer to the interface so obey the COM rules
// and AddRef the reference count.
((LPUNKNOWN)*ppv)->AddRef();
hr = NOERROR;
}
return (hr);
}

CAUTION:
One important fact to remember is that this pointer is not a direct object pointer to any interface, not even to the IUnknown interface. This fact means that the interface pointer returned must be explicitly type-cast to the correct pointer for each interface. Type-casting the pointer changes the pointer value. C++ overloads the type-cast operators to allocate the right vtable for the interface type. Failure to not type-cast to the explicit interface yields unpredictable results.



In looking at the QueryInterface implementation, the IFish interface pointer is returned for the interface IID's IFish and IUnknown. Since the IFish interface is derived from Iunknown, it is perfectly valid to return the IFish interface when the IUnknown interface is asked for. IFish is a matter of preference; the IBass interface can be chosen as well.

The other COM interfaces are implemented as methods of the COBass class. Listing 14.4 illustrates the COM interface implementations.

Listing 14.4 COBASS.CPP--COBass Interface Implementations of the IFish and IBass Interfaces

STDMETHODIMP_(ULONG) COBass::AddRef(void)
{
m_cRefs++;
return m_cRefs;
}
STDMETHODIMP_(ULONG) COBass::Release(void)
{
m_cRefs--;
if (0 == m_cRefs)
{
// We've reached a zero reference count for this COM object.
// So we tell the server housing to decrement its global object
// count so that the server will be unloaded if appropriate.
if (NULL != m_pServer)
m_pServer->ObjectsDown();
delete this;
}
return m_cRefs;
} STDMETHODIMP COBass::GetFishName( char *pStr)
{
LOG("COBass::GetFishName\n");
if (pStr)
lstrcpy((char *)pStr, m_zFishName);
return (HRESULT)NOERROR;
}
STDMETHODIMP COBass::IsFreshwater( BOOL *pBool )
{
LOG("COBass::IsFreshwater\n");
if (pBool)
{
*pBool = m_bFreshwater;
return S_OK;
}
return (HRESULT)NOERROR;
} STDMETHODIMP COBass::GetLocation( char *pStr)
{
LOG("COBass::GetLocation\n");
if (pStr)
strcpy((char *)pStr, (LPCTSTR)m_zLocation);
return (HRESULT)NOERROR;
}
STDMETHODIMP COBass::SetLocation( char *pStr)
{
LOG("COBass::SetLocation\n");
if (pStr)
strcpy(m_zLocation, (char *)pStr);
return (HRESULT)NOERROR;
}
STDMETHODIMP COBass::EatsOtherFish( BOOL *pBool )
{
LOG("COBass::EatsOtherFish\n");
if (pBool)
{
*pBool = m_bEatsOtherFish;
return S_OK;
}
return (HRESULT)NOERROR;
}

As shown in Listing 14.4, the interface implementations are straightforward C++ method implementations. Each method for the IFish and IBass interfaces are implemented as a method of the COBass class. Unlike the MFC-based Cbass implementation, there is no need to specify a specific interface for each method since the COBass class is derived directly from the IFish and IBass interfaces.

One final note on the COBass interface implementations concerns the AddRef and Release methods implemented for the IUnknown interface. The AddRef method simply increments a counter within the object, indicating that another user is referencing the object.

When Release is called three actions are taken:

The last piece of the COBass class is the unique CLSID. To create the CLSID, run the tool GUIDGEN. Once the CLSID is created, it must be placed in a header file that acts as a define for the class implementation. For the COBass object, the file CUSTOMBASSID.H is created. The CLSID is then pasted into the file and added to the macro DEFINE_GUID (see Listing 14.5).

Listing 14.5 CUSTOMBASSID.H--Header File CUSTOMBASSID.H, which Contains the Implementation of CLSID for the COBass Class

#ifndef _CLSID_CustomBass
#define _CLSID_CustomBass
//{A1C19FC0-66D6-11d0-ABE6-D07900C10000}
DEFINE_GUID(CLSID_CustomBass,0xA1C19FC0,
0x66D6,0x11d0,0xAB,0xE6,0xD0,0x79,0x00,0xC1,0x00,0x00);
#endif

The macro DEFINE_GUID assigns the name CLSID_CustomBass to the class ID that was created via GUIDGEN. This macro is placed in a header file that is used by all clients that need to invoke an instance of CLSID_CustomBass. This file is not used by the server that implements the COM Object.

Implementing the COBass Class Factory

Now that the COM class COBass is implemented, a class factory that creates the object must be implemented. A class factory is a COM interface (IClassFactory) that is responsible for creating instances of COBass. When using a framework such as MFC, the class factory for an object is implemented through the OLE_CREATE macro. Without the benefit of a framework, this interface must be implemented by the developer of the COM Object server.

The COBass class factory is implemented in the class CFBass (see Listing 14.6).

Listing 14.6 FACTORY.H--Class Factory Definition File, FACTORY.H, for CFBass

class CFBass : public IUnknown
{
public:
// Main Object Constructor & Destructor.
CFBass(IUnknown* pUnkOuter, CServer* pServer);
~CFBass(void);
// IUnknown methods. Main object, non-delegating.
STDMETHODIMP QueryInterface(REFIID, PPVOID);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
private:
// We declare nested class interface implementations here.
// We implement the IClassFactory interface (ofcourse) in this class
// factory COM object class.
class CImpIClassFactory : public IClassFactory
{
public:
// Interface Implementation Constructor & Destructor.
CImpIClassFactory(
CFBass* pBackObj,
IUnknown* pUnkOuter,
CServer* pServer);
~CImpIClassFactory(void);
// IUnknown methods.
STDMETHODIMP QueryInterface(REFIID, PPVOID);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IClassFactory methods.
STDMETHODIMP CreateInstance(IUnknown*, REFIID, PPVOID);
STDMETHODIMP LockServer(BOOL);
private:
// Data private to this interface implementation of IClassFactory.
ULONG m_cRefI; // Interface Ref Count (for debugging).
CFBass* m_pBackObj; // Parent Object back pointer.
IUnknown* m_pUnkOuter; // Outer unknown for Delegation.
CServer* m_pServer; // Server's control object.
};
// Make the otherwise private and nested IClassFactory interface
// implementation a friend to COM object instantiations of this
// selfsame CFBass COM object class.
friend CImpIClassFactory;
// Private data of CFBass COM objects.
// Nested IClassFactory implementation instantiation.
CImpIClassFactory m_ImpIClassFactory;
// Main Object reference count.
ULONG m_cRefs;
// Outer unknown (aggregation & delegation). Used when this
// CFBass object is being aggregated. Otherwise it is used
// for delegation if this object is reused via containment.
IUnknown* m_pUnkOuter;
// Pointer to this component server's control object.
CServer* m_pServer;
};
typedef CFBass* PCFBass;

The class CFBass uses the technique of nested interfaces to implement the IUnknown and the IClassFactory interfaces. With nested interfaces, the second class definition, in this case CImpIClassFactory, is defined within the definition of the class CFBass. This technique allows the CImpIClassFactory object to be created when the constructor of CFBass is called. Each class implements its own interface.

When the CFBass class constructor is called, the constructor for the class member m_ImpIClassFactory, which is of class CImpIClassFactory, is also called. Listing 14.7 illustrates the CFBass constructor as well as the IUnknown interfaces implemented in CFBass. Notice that if the IUnknown interface is called with an interface ID of IID_IClassFactory, the address of member CImpIClassFactory is returned.

Listing 14.7 FACTORY.CPP--Implementation of the CFBass Class and IUnknown Interface

#include <windows.h>
#include <ole2.h>
#include "comutil.h"
#include "server.h"
#include "factory.h"
#include "cobass.h"
CFBass::CFBass(
IUnknown* pUnkOuter,
CServer* pServer) :
m_ImpIClassFactory(this, pUnkOuter, pServer)
{
// Zero the COM object's reference count.
m_cRefs = 0;
// No AddRef necessary if non-NULL, as we're nested.
m_pUnkOuter = pUnkOuter;
// Init the pointer to the server control object.
m_pServer = pServer;
}
CFBass::~CFBass(void)
{
return;
}
STDMETHODIMP CFBass::QueryInterface(
REFIID riid,
PPVOID ppv)
{
HRESULT hr = E_NOINTERFACE;
*ppv = NULL;
if (IID_IUnknown == riid)
{
*ppv = this;
LOG("S: CFBass::QueryInterface. `this' pIUnknown returned.");
}
else if (IID_IClassFactory == riid)
{
*ppv = &m_ImpIClassFactory;
LOG("S: CFBass::QueryInterface. pIClassFactory returned.");
}
if (NULL != *ppv)
{
// We've handed out a pointer to the interface so obey the COM rules
// and AddRef the reference count.
((LPUNKNOWN)*ppv)->AddRef();
hr = NOERROR;
}
return (hr);
}
STDMETHODIMP_(ULONG) CFBass::AddRef(void)
{
m_cRefs++;
LOGF1("S: CFBass::AddRef. New cRefs=%i.", m_cRefs);
return m_cRefs;
}
STDMETHODIMP_(ULONG) CFBass::Release(void)
{
m_cRefs--;
LOGF1("S: CFBass::Release. New cRefs=%i.", m_cRefs);
if (0 == m_cRefs)
{
if (NULL != m_pServer)
m_pServer->ObjectsDown();
delete this;
}
return m_cRefs;
}

The IClassFactory interface is analogous to the C++ new operator. IClassFactory is an interface used for creating COM classes. Each COM class is created through an IClassFactory interface. The IClassFactory interface is derived from IUnknown and contains the following methods:


// IClassFactory methods.
STDMETHODIMP CreateInstance(IUnknown*, REFIID, PPVOID);
STDMETHODIMP LockServer(BOOL);

The method CreateInstance is used to create an instance of a COM class. LockServer method increments or decrements a reference count within the COM server. If the count is greater than 0, the server cannot be removed from memory. Within the CFBass class, the IClassFactory is implemented through the class CImpIClassFactory. The IUnknown and IClassFactory implementations of CImpIClassFactory are shown in Listing 14.8.

Listing 14.8 FACTORY.CPP--IUnknown and IClassFactory Implementations in CImpIClassFactory

CFBass::CImpIClassFactory::CImpIClassFactory(
CFBass* pBackObj,
IUnknown* pUnkOuter,
CServer* pServer)
{
// Init the Interface Ref Count (used for debugging only).
m_cRefI = 0;
// Init the Back Object Pointer to point to the parent object.
m_pBackObj = pBackObj;
// Init the pointer to the server control object.
m_pServer = pServer;
// Init the CImpIClassFactory interface's
//delegating Unknown pointer.
// We use the Back Object pointer for
// IUnknown delegation here if we are
// not being aggregated. If we are being
// aggregated we use the supplied
// pUnkOuter for IUnknown delegation.
// In either case the pointer
// assignment requires no AddRef
// because the CImpIClassFactory lifetime is
// quaranteed by the lifetime of the parent object in which
// CImpIClassFactory is nested.
if (NULL == pUnkOuter)
{
m_pUnkOuter = pBackObj;
LOG("S: CFBass::CImpIClassFactory Constructor.
Non-Aggregating.");
}
else
{
m_pUnkOuter = pUnkOuter;
LOG("S: CFBass::CImpIClassFactory Constructor.
Aggregating.");
}
return;
}
CFBass::CImpIClassFactory::~CImpIClassFactory(void)
{
LOG("S: CFBass::CImpIClassFactory Destructor.");
return;
}
STDMETHODIMP CFBass::CImpIClassFactory::QueryInterface(
REFIID riid,
PPVOID ppv)
{
LOG("S: CFBass::CImpIClassFactory::QueryInterface.
Delegating.");
// Delegate this call to the outer object's QueryInterface.
return m_pUnkOuter->QueryInterface(riid, ppv);
}
STDMETHODIMP_(ULONG) CFBass::CImpIClassFactory::AddRef(void)
{
// Increment the Interface Reference Count.
++m_cRefI;
LOGF1("S: CFBass::CImpIClassFactory::Addref.
Delegating. New cI=%i.",m_cRefI);
// Delegate this call to the outer object's AddRef.
return m_pUnkOuter->AddRef();
}
STDMETHODIMP_(ULONG) CFBass::CImpIClassFactory::Release(void)
{
// Decrement the Interface Reference Count.
--m_cRefI;
LOGF1("S: CFBass::CImpIClassFactory::Release.
Delegating. New cI=%i.",m_cRefI);
// Delegate this call to the outer object's Release.
return m_pUnkOuter->Release();
}
STDMETHODIMP CFBass::CImpIClassFactory::CreateInstance(
IUnknown* pUnkOuter,
REFIID riid,
PPVOID ppv)
{
HRESULT hr = E_FAIL;
COBass* pCob = NULL;
LOGF1("S: CFBass::CImpIClassFactory::CreateInstance.
pUnkOuter=0x%X.",pUnkOuter);
// NULL the output pointer.
*ppv = NULL;
// If the creation call is requesting aggregation
// (pUnkOuter != NULL),
// the COM rules state the IUnknown interface
// MUST also be concomitantly
// be requested. If it is not so requested
// (riid != IID_IUnknown) then
// an error must be returned indicating that
// no aggregate creation of
// the CFBass COM Object can be performed.
if (NULL != pUnkOuter && riid != IID_IUnknown)
hr = CLASS_E_NOAGGREGATION;
else
{
pCob = new COBass(pUnkOuter, m_pServer);
if (NULL != pCob)
{
// We initially created the new COM object
// so tell the server
// to increment its global server object
// count to help ensure
// that the server remains loaded until
// this partial creation
// of a COM component is completed.
m_pServer->ObjectsUp();
// We QueryInterface this new COM Object not
// only to deposit the
// main interface pointer to the caller's
// pointer variable, but to
// also automatically bump the Reference
// Count on the new COM
// Object after handing out this reference to it.
hr = pCob->QueryInterface(riid, (PPVOID)ppv);
if (FAILED(hr))
{
m_pServer->ObjectsDown();
delete pCob;
}
}
else
hr = E_OUTOFMEMORY;
}
if (SUCCEEDED(hr))
{
LOGF1("S: CFBass::CImpIClassFactory::CreateInstance
Succeeded. *ppv=0x%X.",*ppv);
}
else
{
LOG("S: CFBass::CImpIClassFactory::CreateInstance Failed.");
}
return hr;
}
STDMETHODIMP CFBass::CImpIClassFactory::LockServer(
BOOL fLock)
{
HRESULT hr = NOERROR;
LOG("S: CFBass::CImpIClassFactory::LockServer.");
if (fLock)
m_pServer->Lock();
else
m_pServer->Unlock();
return hr;
}

Three parameters are passed into the method CreateInstance:

The CreateInstance method is not complicated. When this method is called, an object of class COBass is created. After the object is created, QueryInterface is called on the object using the interface ID passed to CreateInstance. If the requested interface is contained within COBass, the interface pointer is returned to the client. If the requested interface is not contained within COBass, the COBass object is deleted and NULL is returned.

Implementation of the Server Application

The implementation of the COM server requires some global housekeeping. The class CServer does this housekeeping. CServer, a global variable much like an MFC CApplication object, keeps track of the number of object references within the server (see Listing 14.9).

Listing 14.9 SERVER.H--CServer Class Definition

class CServer
{
public:
CServer(void);
~CServer(void);
void Lock(void);
void Unlock(void);
void ObjectsUp(void);
void ObjectsDown(void);
// A place to store the handle to loaded instance of this DLL module.
HINSTANCE m_hDllInst;
// Global DLL Server living Object count.
LONG m_cObjects;
// Global DLL Server Client Lock count.
LONG m_cLocks;
};
extern CServer* g_pServer;

The server application object is passed into the constructor of all classes implemented within the COM server. The classes that accept the server application object in their constructor include the COM Object COBass, the class factory CFBass, and CImpIClassFactory. Using a central application object allows all object instances to reference global counters within the COM server. The CServer implementation is shown in Listing 14.10.

Listing 14.10 SERVER.CPP--CServer Object Implementation

CServer::CServer(void)
{
// Zero the Object and Lock counts for this attached process.
m_cObjects = 0;
m_cLocks = 0;
return;
}
CServer::~CServer(void)
{
return;
}
void CServer::Lock(void)
{
InterlockedIncrement((PLONG) &m_cLocks);
LOGF1("S: CServer::Lock. New cLocks=%i.", m_cLocks);
return;
}
void CServer::Unlock(void)
{
InterlockedDecrement((PLONG) &m_cLocks);
LOGF1("S: CServer::Unlock. New cLocks=%i.", m_cLocks);
return;
}
void CServer::ObjectsUp(void)
{
InterlockedIncrement((PLONG) &m_cObjects);
LOGF1("S: CServer::ObjectsUp. New cObjects=%i.", m_cObjects);
return;
}
void CServer::ObjectsDown(void)
{
InterlockedDecrement((PLONG) &m_cObjects);
LOGF1("S: CServer::ObjectsDown. New cObjects=%i.", m_cObjects);
return;
}

Two variables used within the CServer object, m_cObjects and m_cLocks, are accessed by the COM class factory and COM Object. The variable m_cObjects is used to keep track of the number of interfaces requested from the COBass objects. The variable m_cLocks is used to track the number of locks issued on the COM server. When both of these variables are set to zero, the COM server is removed from memory.

NOTE: The variables m_cLocks and m_cObjects are incremented and decremented through the Win32 functions InterlockedIncrement and InterlockedDecrement. These functions are atomic operations that add and subtract 1 from the current value of the variable passed to the function. These functions are designed specifically for multithreaded applications to prevent multiple threads from modifying a value simultaneously.

Implementation of the Server Access Functions

Now that the COM server internals are implemented, the server access functions must be implemented. The code for all of the server access functions is shown in Listing 14.11.

Five functions must be implemented:

Listing 14.11 CUSTOMBASS.CPP--Server Access Function Implementation

#include <ole2.h>
#include <initguid.h>
#include "..\ifish\ifish.h"
#include "..\ifish\ibass.h"
#include "custombassid.h"
#include "comutil.h"
#define _DLLEXPORT_
#include "custombass.h"
#include "server.h"
#include "factory.h"

// We encapsulate the control of this
// COM server (eg, lock and object
// counting) in a server control C++ object.
// Here is it's pointer.
CServer* g_pServer = NULL;
BOOL WINAPI DllMain(
HINSTANCE hDllInst,
DWORD fdwReason,
LPVOID lpvReserved)
{
BOOL bResult = TRUE;
// Dispatch this main call based on the reason it was called.
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
// The DLL is being loaded for the first time
// by a given process.
// Perform per-process initialization here.
// If the initialization
// is successful, return TRUE;
// if unsuccessful, return FALSE.
bResult = FALSE;
// Instantiate the CServer utility class.
g_pServer = new CServer;
if (NULL != g_pServer)
{
// Remember the DLL Instance handle.
g_pServer->m_hDllInst = hDllInst;
bResult = TRUE;
}
break;
case DLL_PROCESS_DETACH:
// The DLL is being unloaded by
// a given process. Do any
// per-process clean up here,
// such as undoing what was done in
// DLL_PROCESS_ATTACH.
// The return value is ignored.
DELETE_POINTER(g_pServer);
break;
case DLL_THREAD_ATTACH:
// A thread is being created in a process
// that has already loaded
// this DLL. Perform any per-thread
// initialization here. The
// return value is ignored.
break;
case DLL_THREAD_DETACH:
// A thread is exiting cleanly in a
// process that has already
// loaded this DLL. Perform any
// per-thread clean up here. The
// return value is ignored.
break;
default:
break;
}
return (bResult);
}
STDAPI DllGetClassObject(
REFCLSID rclsid,
REFIID riid,
PPVOID ppv)
{
HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
IUnknown* pCob = NULL;
if (CLSID_CustomBass == rclsid)
{
LOG("S: DllGetClassObject: Requesting COBass.");
hr = E_OUTOFMEMORY;
pCob = new CFBass(NULL, g_pServer);
}
if (NULL != pCob)
{
g_pServer->ObjectsUp();
hr = pCob->QueryInterface(riid, ppv);
if (FAILED(hr))
{
g_pServer->ObjectsDown();
DELETE_POINTER(pCob);
}
}
return hr;
}
STDAPI DllCanUnloadNow(void)
{
HRESULT hr;
LOGF2("S: DllCanUnloadNow. cObjects=%i,
cLocks=%i.", g_pServer->m_cObjects, g_pServer->m_cLocks);
// We return S_OK of there are
//no longer any living objects AND
// there are no outstanding client locks on this server.
hr = (0L==g_pServer->m_cObjects &&
0L==g_pServer->m_cLocks) ? S_OK : S_FALSE;
return hr;
}
STDAPI DllRegisterServer(void)
{
HRESULT hr = NOERROR;
TCHAR szID[GUID_SIZE+1];
TCHAR szCLSID[GUID_SIZE+1];
TCHAR szModulePath[MAX_PATH];
// Obtain the path to this module's
// executable file for later use.
GetModuleFileName(
g_pServer->m_hDllInst,
szModulePath,
sizeof(szModulePath)/sizeof(TCHAR));
// Create some base key strings.
StringFromGUID2(CLSID_CustomBass, szID, GUID_SIZE);
lstrcpy(szCLSID, TEXT("CLSID\\"));
lstrcat(szCLSID, szID);
// Create entries under CLSID.
SetRegKeyValue(
szCLSID,
NULL,
TEXT("CustomBass Class"));

SetRegKeyValue(
szCLSID,
TEXT("InprocServer32"),
szModulePath);
return hr;
}
STDAPI DllUnregisterServer(void)
{
HRESULT hr = NOERROR;
TCHAR szID[GUID_SIZE+1];
TCHAR szCLSID[GUID_SIZE+1];
TCHAR szTemp[GUID_SIZE+1];
//Create some base key strings.
StringFromGUID2(CLSID_CustomBass, szID, GUID_SIZE);
lstrcpy(szCLSID, TEXT("CLSID\\"));
lstrcat(szCLSID, szID);
wsprintf(szTemp, TEXT("%s\\%s"),
szCLSID, TEXT("InprocServer32"));
RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);
RegDeleteKey(HKEY_CLASSES_ROOT, szCLSID);
return hr;
}
BOOL SetRegKeyValue(
LPTSTR pszKey,
LPTSTR pszSubkey,
LPTSTR pszValue)
{
BOOL bOk = FALSE;
LONG ec;
HKEY hKey;
TCHAR szKey[MAX_STRING_LENGTH];
lstrcpy(szKey, pszKey);
if (NULL != pszSubkey)
{
lstrcat(szKey, TEXT("\\"));
lstrcat(szKey, pszSubkey);
}
ec = RegCreateKeyEx(
HKEY_CLASSES_ROOT,
szKey,
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
NULL,
&hKey,
NULL);
if (NULL != pszValue && ERROR_SUCCESS == ec)
{
ec = RegSetValueEx(
hKey,
NULL,
0,
REG_SZ,
(BYTE *)pszValue,
(lstrlen(pszValue)+1)*sizeof(TCHAR));
if (ERROR_SUCCESS == ec)
bOk = TRUE;
RegCloseKey(hKey);
}
return bOk;
}

Compiling and Testing the COM Server

Now that the COM server is complete, it is time to compile and test the server. Compiling the server is a trivial task. Simply select the command Build CUSTOMBASS.DLL from the Build menu. This command will build the entire project.

After the server is built, it needs to be registered in the Windows Registry. You can accomplish this by selecting the command Register Control from the Tools menu. Register Control will invoke the application regsvr32.exe, which will then call the function DllRegisterServer in CUSTOMBASS.DLL.

After registration is complete, the server is ready for use. In Chapter 12, a COM test application was created for testing the IFish and IBass interfaces. This application can be used to test the IFish and IBass interfaces built in CUSTOMBASS. To test CUSTOMBASS, change the CLSID referenced from CLSID_Bass to CLSID_CustomBass. When the COM test application is run, the new server will be used for accessing the IFish and IBass interfaces.

From Here...

Creating your own COM classes without using a framework requires more programming than when using a framework. However, no excessive baggage from the framework is carried within the COM server, and several implementation strategies can be used during implementation. For an in-depth look at using a lightweight COM framework, refer to Chapter 13, which illustrates the development of COM Objects using the ActiveX Template Library. The ATL framework is composed of lightweight templates for designing and implementing COM Objects. Conversely, in Chapter 12, COM Objects are developed using the MFC application framework. The MFC framework is feature rich but can be overkill for COM Objects that do not need to support a user interface.