| <<< A Short Tutorial | Chapters | Event Handling Within A Module >>> |
COM (Component Object Model) is a binary method for defining objects whose functionality can be used by an application regardless of the source code language in which either the object or the application are written. It allows code to be shared at a binary level rather than a source code level. A COM object is a binary object (usually implemented as a DLL) that exposes a set of methods that an application such as a PS/Workshop module can call.
Applications interact with COM objects in a similar way to C++ objects, although there are some clear differences:
PS/Workshop exposes a number of interfaces to provide access to its core functionality. The use of COM means that you do not have to develop your module code in the same language that PS/Workshop was itself developed in, though examples throughout this manual are given in C++.
A complete reference to the interfaces provided by PS/Workshop can be found in Appendix A, "Interface Functions".
Further details about some of the concepts and use of COM can be found from the following sources:
new
operator, in which case their lifetime is controlled when the requisite
delete
operator is called, orBy contrast, COM objects are created either
The first of these is the simplest approach, and is the one that you should use for the vast majority of interfaces exposed for PS/Workshop. The exception to this is the IPSWDrawOpts interface, which can be created directly in order to specify a set of drawing options to pass to the IPSWDrawList interface.
Creating a COM object using a public object creation method is straightforward. You pass the method the address of an interface pointer, and the method then creates the object and returns an interface pointer. When you use this approach, the type of interface that is returned is defined by the method, though you can often specify a number of things about how the object should be created.
The following example shows how to create a COM object indirectly:
IPSWApp* pApp; // address of interface pointer IPSWDoc* pDoc = NULL; // returned interface pointer HRESULT hr = pApp->OpenNewDocument(&pDoc); // call to object creation method |
The pointer to the new interface is contained in
pDoc
. You can use that pointer to access any of the interface's methods, as described in Section 3.3.1. The result of the call to the method is contained in
hr
, which can be tested for success or failure, as described in Section 3.3.2.
To create a COM object directly, you must do the following:
If you create a new module using the PS/Workshop AppWizard, then initializing and uninitializing COM is handled by the supplied framework.
In addition, you need to know the Class ID (CLSID) of the object you want to create. If this CLSID is not publicly available, you cannot create the object directly.
The following example shows how you can create an IPSWDrawOpts object, using ATL smart interface pointers, as described in Section 3.4.3.:
CComPtr<IPSWDrawOpts> pDrawOpts = NULL; if ( FAILED( pDrawOpts.CoCreateInstance( CLSID_PSWDrawOpts ) ) ) { // recovery code } |
This creates a single uninitialized object of the class associated with the specified CLSID.
To simplify the use of COM interfaces in PS/Workshop, as much of the COM complexity as possible is hidden behind the scenes. If you use the PS/Workshop AppWizard to create a new module, supporting code that you do not need to use explicitly is placed in areas of the source code that you do not need to alter. If you do not use the PS/Workshop AppWizard, then you can find the definitions of the PS/Workshop interfaces using the type library supplied in the PS/Workshop installation directory.
Unlike C++, you do not access a COM object's methods directly. Instead, you must obtain a pointer to the interface that exposes the method. To call the method, you use essentially the same syntax that you would to invoke a pointer to a C++ method. For example, to invoke the IPSWDrawOpts::Reset method, you would use the following syntax.
IPSWDrawOpts *pDrawOpts; ... pDrawOpts->Reset(...); |
All public methods exposed by an interface return a 32-bit integer called an HRESULT. For the interfaces exposed by PS/Workshop, this is used to indicate the return status of the method. Success codes are given names with an S_ prefix (such as S_OK), and failure codes are given names with an E_ prefix (such as E_FAIL). Specific error codes are documented with the reference documentation for each method. General error codes are taken from the standard set defined in
Winerror.h
.
The fact that methods may return a variety of success or failure codes means that you need to be careful when testing whether a call to a given method has been successful or not. If you need detailed information about the outcome of a call to a method, you need to test against individual return values. However, if you want to implement a robust method for detecting the general success or failure of a method call, you should use the following two macros, which are defined in
Winerror.h
:
These macros give you a simple way of testing for general success or failure, as shown in the following example:
if(FAILED(hr)) { //Code to handle failure } else { //Code to handle success } |
When an object is created, the system allocates the necessary memory resources. When that object is no longer needed, you should ensure that the resources it has used are freed up once again. To ensure this happens, each object is responsible for deleting itself. However, COM does not let you destroy objects directly, because a given COM object may be used by several applications; if one application destroyed an object, any other applications using that object would fail. Instead, the lifetime of a COM object is managed using a reference count system.
An object's reference count is the number of times one of its interfaces has been requested by an application. Each time an interface is requested, the reference count is incremented by 1. When an application has finished with an interface, it releases it, decrementing the reference count by 1. Once an object's reference count has reached zero, it is removed from memory.
Management of an object's lifetime is done primarily through an interface called IUnknown, or through the object creation methods exposed by other interfaces. All COM interfaces must inherit the IUnknown interface in order to manage the lifetime of objects they are exposed in.
Incrementing an object's reference count is done in one of the following ways:
Decrementing an object's reference count is done by calling IUnknown::Release. You must release all interface pointers, regardless of how the object reference counter was incremented. Using ATL smart interface pointers, as described in Section 3.4.3, can help to make this task simpler.
If one of the received arguments for a method is an interface then you do not need to call either AddRef or Release on the interface pointer. The only exception to this is if your module wishes to keep a copy of the interface - in this case you should call AddRef on the interface pointer, and you must also release the interface later.
If an interface is a return argument for a function, it is the module's responsibility to ensure that the interface is released at some later date. Using ATL smart interface pointers, as described in Section 3.4.3, can help to make this task simpler.
Every COM object must inherit a standard interface called IUnknown, which contains a number of methods that implement the reference count system. IUnknown exposes the following methods:
HRESULT QueryInterface( REFIID riid, // . LPVOID* ppvObj // ); |
riid |
|
ppvObj |
|
Determines whether an object supports a particular interface. If it does, QueryInterface returns the interface and increments the object's reference count.
Use this method to request additional interfaces to the one returned by an object's creation method.
ULONG AddRef(); |
Increments the object's reference count.
This method should be called whenever a new interface pointer is obtained. However, you should rarely need to use this method, since the object's reference count is automatically incremented whenever an interface is obtained by calling an object creation method, or when QueryInterface is called.
ULONG Release(); |
Releases an interface pointer, and decrements the object's reference count.
As soon as the reference count reaches 0 the object destroys itself.
ATL smart interface pointers are used to encapsulate PS/Workshop interfaces in COM objects. Using smart interface pointers simplifies both using and managing COM interfaces; the encapsulated interface is correctly released once the smart pointer goes out of scope, so you do not need to worry about releasing it explicitly.
The following example illustrates the difference between using smart pointers to encapsulate COM interfaces, and using standard pointers:
This section contains a number of approaches to converting between different datatypes that you might find useful when developing modules.
To convert from a BSTR to a CString:
BSTR bsDocTitle; // Previously initialised BSTR CString csTitle = bsDocTitle; |
To create a BSTR, use one of the following methods:
CComBSTR bsStr; // Unitialised BSTR CString csString = _T("Test String"); BsStr = csString; |
CComBSTR bstrMenuItem( OLESTR("Test String") ); |
CString csString = _T("Test String"); CComBSTR bsStr = csString.AllocSysString(); |
The CComBSTR is a special ATL (Active Template Library) object that encapsulates a BSTR. Using this construct simplifies the process of handling a BSTR. In particular, it means that the resources of the encapsulated BSTR are correctly freed once the object has gone out of scope.
| <<< A Short Tutorial | Chapters | Event Handling Within A Module >>> |