COM Tutorial Samples |
Tutorial Home |
Previous Lesson |
Lesson List |
Next Lesson |
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 free-threaded 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 in FRESERVE.HTM. For details on setting up the programmatic usage of FRESERVE, see the Usage section in FRESERVE.HTM. To read FRESERVE.HTM, run TUTORIAL.EXE in the main tutorial directory and click the FRESERVE lesson in the table of lessons. You can also achieve the same thing by clicking the FRESERVE.HTM file after locating the main tutorial directory in the Windows Explorer. See also FRECLIEN.HTM in the main tutorial 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 COM Tutorial series, see TUTORIAL.HTM. The supplied MAKEFILE is Microsoft NMAKE-compatible. To create a debug build, issue the NMAKE command in the Command Prompt window.
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 COM 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.
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 the server in the registry. You can manually initiate its self-registration by issuing the following command at the command prompt in the FRESERVE directory:
nmake register
This assumes that you have a compilation environment set up. If not, you can also directly invoke the REGISTER.EXE command at the command prompt while in the FRESERVE directory.
..\register\register.exe freserve.dll
These registration commands require a prior build of the REGISTER sample in this series, as well as a prior build of FRESERVE.DLL.
In this series, the makefiles use the REGISTER.EXE utility from the REGISTER sample. Recent releases of the Win32 Platform SDK and Visual C++® include a utility, REGSVR32.EXE, which can be used in a similar fashion to register in-process servers and marshaling DLLs.
The client sample and other related samples must be compiled before you can run the client. For more details on building the samples, see Building the Code Samples.
If you have already built the appropriate samples, FRECLIEN.EXE is the client executable to run for this sample. Click here to run FRECLIEN.EXE.
Depending on the security level of your browser you may see a dialog allowing you to either open the .EXE file or save it to disk. Click the "Open it" choice and then click the OK button.
Files Description FRESERVE.TXT Short sample description. 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.DEF The module definition file. Exports server housing functions. 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 in the sibling APPUTIL directory and APPUTIL.HTM in the main tutorial 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:
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 most recently moved the ball.
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 also 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 an in-process local 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 thread of that apartment. 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 often 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 (beyond that of normal interface marshaling) 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+32]; 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. Note that this value is a named value rather than 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, they are coded to support either the free-threaded model or the Apartment model. In this case, we specify 'Both'. This informs COM how it should act when either a free-threaded client or an apartment-threaded client calls on the server. This means that clients of this server can be initialized with COM as either COINIT_MULTITHREADED or COINIT_APARTMENTTHREADED. We will examine this for a free-threaded client in the next lesson, FRECLIEN.
The string values for ThreadingModel are 'Apartment', 'Free', and 'Both'. If the ThreadingModel value is missing from the InprocServer32 key, the single-threaded apartment model (in a single-threaded process) is assumed by COM as the default. In-process servers can be designated as single-threaded apartment, multi-threaded (ie, free-threaded) apartment, or both. COM objects created under each of these disignations can be used by any COM client, regardless of the threading model used by that client. All combinations of threading model interoperability are allowed between clients and in-process objects. Interaction between a client and an in-process object that use different threading models is exactly like the interaction between clients and out-of-process servers. For an in-process server, when the threading model of the client and in-process server differ, COM must be relied upon as intermediary between the client and the object to ensure the necessary marshaling and call synchronization.
When this is the case, both single and multi-threaded apartments will be present. Such a mixed model will have a single multi-threaded apartment (MTA), containing all threads initialized as COINIT_MULTITHREADED, and one or more single-threaded apartments (STAs). Interface pointers must be marshaled between any of these apartments but can be used within an apartment without marshaling.
Thus, if we wished to provide for a FRECLIEN client having multiple apartment threads (one or more single-threaded apartments and a single optional multi-threaded apartment), we would likely need to provide for marshaling of any interfaces for which pointers are passed between client apartment threads. COM offers some convenient functions to perform run-time marshaling of such interfaces (for example, CoCreateFreeThreadedMarshaler, CoMarshalInterface and CoMarshalInterThreadInterfaceInStream). If a more permanent solution is also appropriate, MIDL and the techniques for standard marshaling seen in the previous MARSHAL sample can also be used to create and register a marshaling server for the custom interfaces.
The FRESERVE and FRECLIEN samples do not require a standard marshaling server. If there was an expectation that FRESERVE would be used by a variety of apartment model clients, then standard marshaling support for the IBall interface would be appropriate. With such standard marshaling support for the IBall interface, it would also be appropriate to register FRESERVE's DllBall component with 'ThreadingModel=Both'. This would publish the component as fully supporting client control in either single-threaded or multi-threaded apartments.
The COBall COM objects manufactured by FRESERVE 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(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 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 an HRESULT value to notify any other threads that call Move if the ball is dead or alive. A return value of E_FAIL 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 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; CServer* pServer; if (OwnThis()) { cRefs = --m_cRefs; if (0 == cRefs) { // Save a local copy of the pointer to the server control object. pServer = m_pServer; // 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; // We've reached a zero reference count for this COM object and // we have deleted the 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 != pServer) pServer->ObjectsDown(); } 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.