Using COM in PS/Workshop   

<<< A Short Tutorial Chapters Event Handling Within A Module >>>

Contents

[back to top]


3.1 What is a COM object?

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:

[back to top]


3.2 Creating a COM object

In C++, objects can either be

By 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.

[back to top]

3.2.1 Creating a COM object indirectly

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.

[back to top]

3.2.2 Creating a COM object directly

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.

[back to top]


3.3 Using COM interfaces

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.

[back to top]

3.3.1 Calling public methods

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(...);

[back to top]

3.3.2 Testing for success or failure

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
  }

[back to top]


3.4 Managing the lifetime of COM objects

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.

 

    Performance issue: Failing to release an interface is one of the most common ways of creating memory leaks in an application that uses COM interfaces. You must ensure that reference counting is handled properly in your PS/Workshop modules, since PS/Workshop will not exit correctly if the reference count for any of its interfaces is not zero.

[back to top]

3.4.1 Managing interfaces in method arguments

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.

[back to top]

3.4.2 IUnknown

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:

QueryInterface

 

HRESULT QueryInterface(
  REFIID riid,   // . 
  LPVOID* ppvObj // 
);

 

Received arguments

riid

Reference ID of the interface requested

Returned arguments

ppvObj

Address of interface pointer if successful

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.

 

Specific Errors

 

E_NOINTERFACE

No such interface supported.

E_POINTER

Invalid pointer.

AddRef

 

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.

Release

 

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.

[back to top]

3.4.3 ATL smart interface pointers

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:

With smart pointers

 

IPSWDrawList    *pDrawList    // Previously initialised pointer

// create our smart interface pointer to hold our interface
CComPtr<IPSWDrawOpts> pDrawOpts;
HRESULT hr = pDrawList->get_DrawOptions(&pDrawOpts);
if ( SUCCEEDED( hr ) )
{
  // Go off and do something
)

Without smart pointers

 

IPSWDrawList    *pDrawList    // Previously initialised pointer

// create a pointer to hold our interface
IPSWDrawOpts *pDrawOpts;
HRESULT hr = pDrawList->get_DrawOptions(&pDrawOpts);
if ( SUCCEEDED( hr ) )
{
  // Go off and do something

  // remember to release the interface when finished with it
  pDrawOpts->Release();
)

[back to top]


3.5 Converting between data-types

This section contains a number of approaches to converting between different datatypes that you might find useful when developing modules.

[back to top]

3.5.1 Converting from BSTR to CString

To convert from a BSTR to a CString:

 

BSTR bsDocTitle;        // Previously initialised BSTR
CString csTitle       = bsDocTitle;

[back to top]

3.5.2 Creating a BSTR

To create a BSTR, use one of the following methods:

Method 1:

 

CComBSTR bsStr;        // Unitialised BSTR
CString  csString    = _T("Test String");
BsStr  = csString;

Method 2:

 

CComBSTR bstrMenuItem( OLESTR("Test String") );

Method 3:

 

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.

 

[back to top]

<<< A Short Tutorial Chapters Event Handling Within A Module >>>