home *** CD-ROM | disk | FTP | other *** search
- FRESERVE - Free-threaded COM objects in an in-process server
-
-
- SUMMARY
- =======
-
- The FRESERVE sample shows how to construct a COM object in a free-threaded
- in-process server. This sample departs from the sport utility vehicle
- metaphor and associated interfaces used in other samples of this series.
- FRESERVE introduces a new custom interface, IBall, and a new COM object,
- COBall. COBall implements the IBall interface. Both COBall and its
- in-process server are coded to support COM free threading in anticipation
- of their use by the free-threaded client, FRECLIEN, in the next lesson.
-
- The CThreaded facility in APPUTIL is used to achieve thread safety as it
- was in the previous APTSERVE sample. COBall objects are derived from the
- CThreaded class and so inherit its OwnThis and UnOwnThis methods. These
- methods enforce mutually exclusive access to the FRESERVE server and to
- COBall objects managed by the server.
-
- FRESERVE works with the FRECLIEN code sample to illustrate FRESERVE's COM
- server facilities in a free-threaded server and the subsequent manipulation
- of its components by a free-threaded client.
-
- For functional descriptions and a tutorial code tour of FRESERVE, see the
- Code Tour section below. See also FRECLIEN.TXT in the sibling FRECLIEN
- directory for more details on the FRECLIEN client application and how it
- works with FRESERVE.DLL. You must build FRESERVE.DLL before building or
- running FRECLIEN.
-
- FRESERVE's makefile automatically registers FRESERVE's DllBall component
- in the registry. This component must be registered before FRESERVE is
- available to outside COM clients as a server for that component. This
- self-registration is done using the REGISTER.EXE utility built in the
- previous REGISTER lesson. To build or run FRESERVE, you should build the
- REGISTER code sample first.
-
- For details on setting up your system to build and test the code samples
- in this OLE Tutorial series, see TUTORIAL.TXT. The supplied MAKEFILE is
- Microsoft NMAKE-compatible. To create a debug build, issue the NMAKE
- command in the Command Prompt window.
-
- Usage
- -----
-
- FRESERVE is a DLL that is intended primarily as a free-threaded COM
- server. Although it can be implicitly loaded by linking to its associated
- .LIB file, it is normally used after an explicit LoadLibrary call, usually
- from within COM's CoGetClassObject function. FRESERVE is a
- self-registering in-process server. The makefile that builds this sample
- automatically registers this server in the registry. You can manually
- initiate its self registration by issuing the following command at the
- command prompt:
-
- nmake register
-
- This registration process requires a prior build of the REGISTER sample
- in this series.
-
- To use FRESERVE, a client program does not need to include FRESERVE.H or
- link to FRESERVE.LIB. A COM client of FRESERVE obtains access solely
- through its component's CLSID and OLE services. For FRESERVE, that CLSID
- is CLSID_DllBall (defined in file BALLGUID.H in the INC sibling
- directory). The FRECLIEN code sample shows how the client obtains this
- access.
-
-
- CODE TOUR
- =========
-
- Files Description
-
- FRESERVE.TXT This file.
- MAKEFILE The generic makefile for building the FRESERVE.DLL
- code sample of this lesson.
- FRESERVE.H The include file for declaring as imported or defining as
- exported the service functions in FRESERVE.DLL.
- FRESERVE.CPP The main implementation file for FRESERVE.DLL. Has DllMain
- and the COM server functions (for example, DllGetClassObject).
- FRESERVE.RC The DLL resource definition file for the executable.
- FRESERVE.ICO The icon resource for the executable.
- SERVER.H The include file for the server control C++ object.
- SERVER.CPP The implementation file for the server control object.
- FACTORY.H The include file for the server's class factory COM objects.
- FACTORY.CPP The implementation file for the server's class factories.
- BALL.H The include file for the COBall COM object class.
- BALL.CPP The implementation file for the COBall COM object class.
-
-
- FRESERVE uses many of the utility classes and services provided by
- APPUTIL. For more details on APPUTIL, study the APPUTIL library's source
- code and APPUTIL.TXT, located in the sibling \APPUTIL directory.
-
- This sample is part of a graduated series of tutorial samples. This tour
- assumes that you have some exposure to those previous samples. It does not
- revisit earlier topics of basic interface implementation techniques, COM
- object construction, in-process server construction, and class factory
- construction. For information on these topics, study the earlier tutorial
- samples.
-
- The major topics covered in this code tour are an overview of how FRESERVE
- works; thread-safe mechanisms in FRESERVE; an overview of COM free
- threading; self-registration of COM objects in a free-threaded server; the
- IBall interface; the construction of the COBall COM object; and issues in
- class factory construction in a free-threaded server.
-
- The COBall COM object is the single object type managed by this FRESERVE
- in-process server. COBall is constructed as an aggregatable COM object
- with a native implementation of the IBall interface. COBall exposes the
- IBall interface to allow clients to perform a small set of operations on
- an instance of COBall. The COBall object encapsulates data that defines a
- moving ball. Data such as position, size, and color are encapsulated. No
- graphical images are managed in this object. When a COBall object is
- initialized with a call to its Reset method, it is given a rectangle (a
- standard Win32 RECT structure) that defines the boundaries within which
- the ball may move. The COBall object contains coded logic to move the ball
- within those boundaries, to bounce the ball off any boundary when
- appropriate, and to provide clients with current data on the ball's
- location, size, and color. The object maintains a current color property
- indicating the particular executing thread that most recently moved the
- ball by calling the ball's Move method.
-
- A client can then use the IBall interface to move the ball and to obtain
- the data necessary to paint an image of the moving ball. The COBall object
- is coded to support the free-threaded COM model, in which any number of
- threads may freely make asynchronous calls directly to the object's
- interface methods. In the following FRECLIEN code sample, several threads
- running concurrently attempt to move a single COBall object. An
- independent client thread runs concurrently to continuously obtain ball
- position and color data and to paint snapshot images that graphically
- reflect the COBall's changing data. Many different threads may
- concurrently attempt to change and read the ball's data. The ball is kept
- "alive" by one set of client threads, while another client thread
- passively paints images of the moving ball. Because the ball updates its
- own data to reflect the executing threads, the ball image painted by the
- client changes color as it moves to continuously reflect which thread is
- currently executing.
-
- Aside from the COBall logic needed to maintain the bouncing ball as a
- mathematical entity, FRESERVE also requires some special code to support
- free threading. We will start with this code, because it borrows from
- techniques previously shown in the APTSERVE sample. Like APTSERVE, the
- FRESERVE server housing is constructed to guard shared data in the server
- from conflicting access by multiple concurrent threads of execution. The
- technique used to enforce serialized access to this server data is based
- on the use of Win32 mutexes. APPUTIL's CThreaded utility base class
- encapsulates the mutex protection mechanism. This utility is presented in
- detail in the APTSERVE lesson.
-
- In the FRESERVE server housing, the CServer C++ class is derived from the
- CThreaded base class to inherit the OwnThis and UnOwnThis methods. These
- methods are used in bracketed pairs to protect access to CServer's data,
- such as the server's object and lock counts. One new addition to this
- in-process server housing is the CServer::CanUnloadNow method. It uses the
- CThreaded facility. Here it is from SERVER.CPP.
-
- HRESULT CServer::CanUnloadNow(void)
- {
- HRESULT hr = S_FALSE;
- LONG cObjects, cLocks;
-
- if (OwnThis())
- {
- cObjects = m_cObjects;
- cLocks = m_cLocks;
-
- hr = (0L==cObjects && 0L==cLocks) ? S_OK : S_FALSE;
-
- UnOwnThis();
- }
-
- return hr;
- }
-
- The OwnThis, UnOwnThis pair is used to protect access to the server's
- m_CObjects and m_cLocks variables. Within the range of this protection,
- copies of the variable values are made on the local stack, and the copies
- are then used for most logic. The logic implemented here is to support the
- in-process server's familiar DllCanUnloadNow exported function. This
- arrangement is convenient because DllCanUnloadNow is not a method of a
- class that is derived from CThreaded, such that it can benefit from the
- OwnThis protection mechanism. Yet such protection is needed, because the
- server data is vulnerable to concurrent access by multithreaded clients.
- In fact, the server data is potentially vulnerable to concurrent access by
- multiple threads that could be spawned within the server or its
- components. The CanUnloadNow method benefits from CThreaded not only for
- the protection it offers, but also for the simple programming
- encapsulation it provides. Such encapsulation will pay off later if the
- free-threaded server evolves to one that spawns multiple threads within it.
-
- The default model for multithreaded programming with COM is the apartment
- model. This model was presented in the previous APTSERVE and APTCLIEN
- samples, where multiple apartment threads were provided within a server.
- COM's support of the apartment model ensures that calls to interface
- methods on objects created within an apartment will always be on the same
- thread as that of the class factory that created the object. The first
- point of recognition by COM in this regard is the occasion of the object's
- first marshaled interface when the object is created. This is usually the
- IClassFactory interface pointer requested in the CoGetClassObject call. At
- this point of recognition COM associates the object with its apartment
- thread.
-
- The apartment model supported by COM is convenient and largely transparent
- to the client. It enforces a serialized access among contending threads to
- a COM object. The object is always called on the thread that "owns" it,
- even if the caller is on a different thread. This model entails some
- overhead as COM performs thread switching during such cross-thread calls.
-
- Performance can be significantly improved by using the free-threaded
- model. In this model, COM does not make thread switches on behalf of
- cross-thread calls to interface methods. Instead, COM freely ushers the
- call through on the same thread originating the call. This means that the
- thread-safe serialized access to the object that was enforced by the
- apartment model is not enforced by the free threading model. COM objects
- must therefore provide their own serialization for access by multiple
- threads. Earlier in this lesson we saw the CThreaded mechanism that
- provides such access safety in this sample.
-
- The way that COM is informed that this in-process server and its managed
- components support the free-threaded model is by an additional entry in
- the component's registration in the registry. As with all the previous
- in-process servers of this series, the FRESERVE server self-registers the
- components it houses. Here is the DllRegisterServer function from
- FRESERVE.CPP.
-
- 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 registry entries for the DllBall Component.
- --------------------------------------------------------------------*/
- // Create some base key strings.
- StringFromGUID2(CLSID_DllBall, szID, GUID_SIZE);
- lstrcpy(szCLSID, TEXT("CLSID\\"));
- lstrcat(szCLSID, szID);
-
- // Create ProgID keys.
- SetRegKeyValue(
- TEXT("DllBall1.0"),
- NULL,
- TEXT("DllBall Component - FRESERVE Code Sample"));
- SetRegKeyValue(
- TEXT("DllBall1.0"),
- TEXT("CLSID"),
- szID);
-
- // Create VersionIndependentProgID keys.
- SetRegKeyValue(
- TEXT("DllBall"),
- NULL,
- TEXT("DllBall Component - FRESERVE Code Sample"));
- SetRegKeyValue(
- TEXT("DllBall"),
- TEXT("CurVer"),
- TEXT("DllBall1.0"));
- SetRegKeyValue(
- TEXT("DllBall"),
- TEXT("CLSID"),
- szID);
-
- // Create entries under CLSID.
- SetRegKeyValue(
- szCLSID,
- NULL,
- TEXT("DllBall Component - FRESERVE Code Sample"));
- SetRegKeyValue(
- szCLSID,
- TEXT("ProgID"),
- TEXT("DllBall1.0"));
- SetRegKeyValue(
- szCLSID,
- TEXT("VersionIndependentProgID"),
- TEXT("DllBall"));
- SetRegKeyValue(
- szCLSID,
- TEXT("NotInsertable"),
- NULL);
- SetRegKeyValue(
- szCLSID,
- TEXT("InprocServer32"),
- szModulePath);
- AddRegNamedValue(
- szCLSID,
- TEXT("InprocServer32"),
- TEXT("ThreadingModel"),
- TEXT("Free"));
-
- return hr;
- }
-
- The important new code here is the call to AddRegNamedValue. A new named
- value must be added to the InprocServer32 key. This value is not a subkey
- of InprocServer32. Registry keys can have a series of named values
- associated with them. The definition of the AddRegNamedValue is
- straightforward and is defined in FRESERVE.CPP. The named value added for
- the DllBall component is "ThreadingModel=Free". Since our component and
- server are fully thread-safe and are coded to support the free-threaded
- model, we specify "Free". This means that clients of this server must be
- initialized with COM as free-threaded client processes. We will see how
- this is done the next lesson, FRECLIEN.
-
- Other string values for ThreadingModel are "Both" and "Apartment".
- "Apartment" means that the client process must be initialized with COM as
- apartment-threaded before COM will employ the server on the client's
- behalf. "Both" means that client processes can be initialized with COM as
- either free-threaded or apartment-threaded. COM will employ the server on
- their behalf in either case. If the ThreadingModel value is missing from
- the InprocServer32 key, the apartment model is the default.
-
- The COBall COM objects manufactured by this server implement the IBall
- custom interface. Here is the IBall interface declaration from IBALL.H,
- located in the INC sibling directory.
-
- DECLARE_INTERFACE_(IBall, IUnknown)
- {
- // IUnknown methods.
- STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
- STDMETHOD_(ULONG,AddRef) (THIS) PURE;
- STDMETHOD_(ULONG,Release) (THIS) PURE;
-
- // IBall methods.
- STDMETHOD(Reset) (THIS_ RECT*, SHORT) PURE;
- STDMETHOD(GetBall) (THIS_ POINT*, POINT*, COLORREF*) PURE;
- STDMETHOD_(BOOL,Move) (THIS_ BOOL) PURE;
- };
-
- Here are the matching implementation declarations within the COBall object
- class. See the CImpIBall nested class declaration in BALL.H.
-
- ...
- ...
- STDMETHODIMP Reset(RECT* pNewRect, short nBallSize);
- STDMETHODIMP GetBall(POINT* pOrg, POINT* pExt, COLORREF* pcrColor);
- STDMETHODIMP_(BOOL) Move(BOOL bAlive);
- ...
- ...
-
- The Reset method accepts a RECT structure, which specifies the initial
- boundaries of the rectangle within which the ball entity is permitted to
- move. This rectangle typically matches the client area of a client
- application's window. Reset also accepts an initial ball diameter,
- specified in pixels.
-
- The GetBall method is the way the client obtains current properties of the
- COBall object: its diameter, its color, and the location of its origin.
-
- The Move method simply directs the COBall to move itself. The semantics of
- this method are left up to the COBall object. In the current sample,
- internal calculation logic is employed to give some continuity to the
- ball's motion. The ball moves in the current direction until it hits a
- boundary, at which point it bounces. This bounce is a reflection of its
- angle of incidence with the boundary. However, the angle of reflection
- does not always equal the angle of incidence. A small random skew factor
- is used both for the reflection angle and for the speed of the motion.
- This skew factor makes the ball's movement appear more natural.
-
- When the bAlive parameter is set to FALSE, the Move method destroys the
- ball. In this sample, multiple threads give life to the ball. Because one
- thread could issue this termination command before another could know
- about it, Move also returns a Boolean value to notify any other threads
- that call Move if the ball is dead or alive. A return value of FALSE
- indicates that the ball is dead. A dead ball no longer moves. Any thread
- can move the ball, and any thread can kill the ball.
-
- The construction of the COBall COM object is based on techniques seen in
- earlier samples of this series. The familiar technique of nested class
- declarations are used for the multiple interface implementations. Here is
- the COBall class declaration in BALL.H.
-
- class COBall : public IUnknown, public CThreaded
- {
- public:
- // Main Object Constructor & Destructor.
- COBall(IUnknown* pUnkOuter, CServer* pServer);
- ~COBall(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.
-
- class CImpIBall : public IBall, public CThreaded
- {
- public:
- // Interface Implementation Constructor & Destructor.
- CImpIBall(COBall* pBackObj, IUnknown* pUnkOuter);
- ~CImpIBall(void);
-
- // IUnknown methods.
- STDMETHODIMP QueryInterface(REFIID, PPVOID);
- STDMETHODIMP_(ULONG) AddRef(void);
- STDMETHODIMP_(ULONG) Release(void);
-
- // IBall methods.
- STDMETHODIMP Reset(RECT* pNewRect, short nBallSize);
- STDMETHODIMP GetBall(POINT* pOrg, POINT* pExt, COLORREF* pcrColor);
- STDMETHODIMP_(BOOL) Move(BOOL bAlive);
-
- private:
- // Data private to this COBall interface implementation of IBall.
- COBall* m_pBackObj; // Parent Object back pointer.
- IUnknown* m_pUnkOuter; // Outer unknown for Delegation.
-
- // The following private data and methods constitute the working
- // heart of COBall as an actual application object.
- BOOL m_bAlive;
- RECT m_WinRect;
- int m_nWidth;
- int m_nHeight;
- int m_xDirection;
- int m_yDirection;
- BOOL m_bNewPosition;
- int m_xPosition;
- int m_yPosition;
- short m_xSkew;
- short m_ySkew;
- COLORREF m_crColor;
- CXForm m_XForm;
- CBallThread m_BallThreads[MAX_BALLTHREADS];
-
- // Private methods for internal use.
- void GetDimensions(POINT*);
- void SetDimensions(int,int);
- void GetDirection(POINT*);
- void SetDirection(int,int);
- void GetPosition(POINT*);
- void SetPosition(int,int);
- void CheckBounce(void);
- void FindThread(void);
- };
-
- // Make the otherwise private and nested IBall interface
- // implementation a friend to COM object instantiations of this
- // COBall COM object class.
- friend CImpIBall;
-
- // Private data of COBall COM objects.
-
- // Nested IBall implementation instantiation. This IBall interface
- // is instantiated inside this COBall object as a native interface.
- CImpIBall m_ImpIBall;
-
- // 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;
- };
-
- The heart of the COBall object is coded inside the CImpIBall nested
- interface implementation. The advantage of this design is that it doesn't
- burden the main COBall object class with the details of the ball's motion.
- It encapsulates the coding logic of the IBall interface entirely within
- the interface implementation. Another advantage of this is that, if you
- were evolving something like a CBall C++ class from legacy code into a COM
- COBall implementation, the transition from CBall to CImpIBall is somewhat
- less complicated than it would be from CBall to the outer COBall class,
- where the nested interface implementations tend to dominate attention. In
- this sample the issue is not as pronounced as a case where the outer
- COBall class might have many nested interface class declarations.
-
- Notice that the outer COBall class and the nested CImpIBall class are both
- derived from CThreaded to inherit the OwnThis thread safety mechanism. The
- methods of both these classes need this mechanism to protect data
- encapsulated in their C++ objects.
-
- The CImpIBall class implements many internal private methods, such as
- SetPostion and FindThread. Of all these CImpIBall methods, only the
- IUnknown and IBall interface methods are exposed as public in the C++
- sense. The IBall interface exposes only the public Reset, GetBall, and
- Move methods. Other of the private methods, such as SetPosition, could be
- promoted to the status of public members of some new IBall2 evolution of
- the IBall interface. Should such an evolution occur, the COM contract
- requires that the new interface adopt a name and a new interface
- identifier (IID). However, it must be derived from IBall to inherit and
- retain its prior semantics.
-
- The data that defines the ball is declared in the private area of
- CImpIBall. The m_BallThreads array is maintained by the object to map
- color attributes to the threads that call the object's Move method. In
- conjunction with the FindThread method, program logic assigns colors to
- passing threads and reuses those colors when previous threads revisit the
- object. As new threads are added to the array, each is assigned a new
- color. In the current sample, a random selection of 64 such thread colors
- is accommodated using the compile-time macro MAX_BALL_THREADS.
-
- The CXForm class is also declared in BALL.H. It is part of the inner
- algorithms that govern ball behavior and is not relevant to the threading
- model presented in this lesson.
-
- The class factory for the DllBall component, CFBall, is declared in
- FACTORY.H and implemented in FACTORY.CPP. This code is borrowed from many
- previous samples in this series. Like COBall, CFBall is derived from
- IUnknown and CThreaded using multiple inheritance. CThreaded gives the
- class factory its thread safety using the OwnThis mechanism seen earlier.
- There is one special issue worth mentioning.
-
- The CFBall::Release has an odd arrangement of the OwnThis, UnOwnThis pair.
- Here is the code from FACTORY.CPP.
-
- STDMETHODIMP_(ULONG) CFBall::Release(void)
- {
- ULONG cRefs;
-
- if (OwnThis())
- {
- cRefs = --m_cRefs;
-
- if (0 == 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();
-
- // We artificially bump the main reference count to prevent
- // reentrancy via the main object destructor. Not really needed
- // in this CFBall but a good practice because we are aggregatable
- // and may at some point in the future add something entertaining
- // like some Releases to the CFBall destructor.
- m_cRefs++;
- UnOwnThis();
- delete this;
- }
- else
- UnOwnThis();
- }
-
- return cRefs;
- }
-
- The extra call to UnOwnThis is needed because of the "delete this"
- statement. The UnOwnThis call must precede the delete statement so that
- the currently owning thread will relinquish ownership of this object
- before an attempt is made to destroy the entire object. The object must
- remain in existence as long as OwnThis is using the governing mutex, but
- no longer.
-