Using COM Components

< BACK  NEXT >
[oR]

This section shows how to use COM interfaces by means of the standard COM functions and techniques. These techniques create COM objects and manage interfaces using standard COM functions (such as CoCreateInstance) and interface functions (such as IUnknown's AddRef, Release, and QueryInterface functions). These techniques are the easiest to understand; however, memory leaks can easily be introduced if, for example, calls to Release are omitted. In the next section smart pointers are described that are initially more complex but eventually lead to easier and safer programming.

Initializing and Uninitializing COM

The COM library should be initialized before any COM functions or objects areused. In Windows CE COM initialization is not strictly required but should be included for compatibility with desktop programming practices. COM is initialized through a call to CoInitializeEx, and this function is passed two parameters:

  • NULL The first parameter is ignored and should always be passed as NULL.

  • COINIT_MULTITHREADED The threading model to be used by components created on this thread. In Windows CE the only supported threading model is "multi-threaded."

CoInitializeEx returns an HRESULT indicating success or failure. Listing 14.1 shows a call to CoInitializeEx, with a test of the returned HRESULT. You need to include objbase.h when using COM functions and include the libraries ole32.lib and oleaut32.lib in the project.

Listing 14.1 Initializing COM
 #include <objbase.h> void Listing14_1() {   HRESULT hr;   hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);   if(FAILED(hr))     cout   _T("Failed to initialize COM")   endl;   else     cout   _T("COM Initialized")   endl; } 

When an application has finished using COM, it should be uninitialized through a call to CoUninitialize. This function takes no arguments and has no return value (Listing 14.2).

Listing 14.2 Uninitializing COM
 void Listing14_2() {   CoUninitialize();   cout   _T("COM Uninitialized")   endl; } 

Creating a COM Object

Once COM has been initialized, component objects can be created using calls to CoCreateInstance (Table 14.2). A call to CoCreateInstance specifies the following:

  • The CLSID of the component specifying the object to create

  • The IID of the interface to be returned

  • A pointer to an interface pointer in which the requested interface pointer will be returned

You can decide which interface you want to start working with any other interface can be obtained at a later stage by calling QueryInterface. The CLSID is the only reference an application makes to the component or an object. Every subsequent COM call always uses the interface pointer returned by CoCreateInstance.

The dwClsContext parameter allows an application to specify where the component can be created. CLSCTX_INPROC_SERVER specifies that the COM component must be implemented in a DLL and will be loaded into the address space of the client application. If the COM component is implemented, in an EXE, for example, the call to CoCreateInstance would fail since the component would be out-of-process, being in another process.

Listing 14.3 shows a call to CoCreateInstance that creates a POOM object. The header pimstore.h must be included since this contains the interface definitions, the CLSID, and IID definitions. The call to CoCreateInstance returns an interface pointer represented by the IID IID_IPOutlookApp. This is the "top-level" interface in POOM and is used to logon to Pocket Outlook.

Table 14.2. CoCreateInstance Creates a COM component object
CoCreateInstance
REFCLSID rclsid Reference to a CLSID a GUID that identifies the class of object to be created.
LPUNKNOWN pUnkOuter This parameter is used by COM component developers and can generally be passed as NULL.
DWORD dwClsContext Pass as CLSCTX_INPROC_SERVER for Windows CE.
REFIID riid Reference to a IID (Interface ID) that should be returned in ppv. This can be any interface identifier supported by the class of object defined by rclsid.
LPVOID * ppv Pointer to an interface pointer in which the pointer defined by rrid is returned.
STDAPI Return Value STDAPI defines that the function returns an HRESULT. S_OK indicates that the object was created successfully, and REGDB_E_CLASSNOTREG indicates that the class identifier could not be located.

Listing 14.3 Creating a COM object
 #include <pimstore.h> IPOutlookApp *g_poomApp; void Listing14_3() {   HRESULT hr;   hr = CoCreateInstance(CLSID_Application,         NULL,         CLSCTX_INPROC_SERVER,         IID_IPOutlookApp,         (LPVOID *)&g_poomApp);   if (FAILED(hr))     cout   _T("Could not create POOM");   else     cout   _T("POOM Object created")   endl; } 

As described earlier, the CLSID and IIDs are structures, and your application needs variables declared to hold the CLSID and IIDs used by your application. This is not done by default in the header files such as pimstore.h, since this would lead to duplicate variable declarations in the various source files, including the header file. You can have these variables declared correctly from the header file by using the following define:

 #define INITGUID 

This define should be placed before any of the standard header files (such as windows.h or objbase.h) are included. As you will see later, you do not need to explicitly delete the object created with CoCreateInstance interface reference counting does this automatically.

Calling COM Functions

Each interface has functions that can be called through the interface pointer returned from CoCreateInstance. The IPOutlookApp interface has three important functions:

  • Logon To logon to Pocket Outlook

  • Logoff To logout of Pocket Outlook

  • get_Version To obtain the POOM version number

Calling these functions is quite straightforward they are called through the interface pointer returned from CoCreateInstance and stored in g_poomApp. Listing 14.4 shows a call to the functions Logon (which is passed the application's window handle) and to get_Version (which is passed a string using the BSTR data type in which the version number is returned). The BSTR data type represents a variable-length string suitable for passing between a client application and a component, as described in the next section.

Listing 14.4 Calling COM methods
 void Listing14_4(HWND hWnd) {   HRESULT hr;   BSTR szVersion;   hr = g_poomApp->Logon((long) hWnd);   if (FAILED(hr))     cout   _T("Could not login")   endl;   else   {     g_poomApp->get_Version(&szVersion);     cout   _T("POOM Version: ")            szVersion   endl;     SysFreeString(szVersion);   } } 

The BSTR Data Type

The BSTR data type allows variable-length, dynamically created strings to be passed between a client application and a COM component. A BSTR variable points at the string it contains, so the string content can be accessed like a constant pointer to a string, that is, a LPCTSTR. The bytes preceding the BSTR pointer contain the character count. Only the standard BSTR functions should be used to change the string's length to ensure that the character count is maintained correctly. While the string may contain a NULL-terminating character, it is not required. Therefore, do not rely on one being present. Also, a BSTR can contain embedded NULL characters, so wcslen may give the incorrect length. The function SysStringLen can be used to obtain the current length of a BSTR.

The function SysAllocString is used to create a BSTR and initialize it with a string:

 BSTR bStr; bStr = SysAllocString(_T("My String")); 

A string must eventually be de-allocated, and calling SysFreeString does this:

 SysFreeString(bStr); 

Some of the commonly used BSTR functions include the following:

  • SysAllocString Returns a BSTR created from a NULL-terminated string

  • SysAllocStringLen Returns a BSTR allocated to the specified length

  • SysFreeString Frees the memory associated with the BSTR

  • SysReAllocString Changes the length of the BSTR by reallocation

  • SysStringLen Returns the length of the string in characters

Releasing COM Interfaces

COM component objects take up memory, and sooner or later this memory has to be freed. COM components keep a reference count on each interface pointer returned to a client application. When CoCreateInstance is called, an interface pointer is returned, so the reference count on that interface is incremented. Calling the IUnknown interface's AddRef function does this. AddRef must be implemented by every interface, since all interfaces inherit from IUnknown.

When a client application has finished with an interface, it must call the IUnknown interface's Release function. This function decrements the interface's reference count, and when all reference counts for all interfaces implemented by a component reach zero, the component object deletes itself.

Listing 14.5 shows calling the IPOutlookApp interface's Logoff function to logout of Pocket Outlook, and then calling Release. Since this is the only reference to the only interface in the component, the component object deletes itself at this point.

Listing 14.5 Releasing COM interfaces
 void Listing14_5() {   // First log-off then release interface   g_poomApp->Logoff();   g_poomApp->Release(); // Object deleted   cout   _T("POOM Object released")   endl; } 

If a client application passes an interface pointer to another function, it should itself call AddRef, since there would now be another reference to the interface. The function receiving the interface pointer should call Release when it has finished with the interface. Failure to call AddRef and Release at the correct times can have undesirable results such as the following:

  • If Release is called without a corresponding AddRef, the component object will delete itself prematurely, and other interface pointers to the component will be invalid.

  • If Release is not called, the interface reference count will never get to zero, and the component object will never delete itself. This will result in a memory leak.

Keeping track of AddRef and Release calls is tricky and can easily result in bugs. Because of this, it is best to use smart pointers to automate the reference count. These techniques are described later in this chapter. The component determines how the reference count is implemented it can either have a single reference count for all interfaces or have a separate reference count for each interface. From the client application's standpoint, this is an implementation detail and is of no importance.

Finding a Contact's Email Address

So far, a single pointer to the interface IPOutlookApp has been used. To access data from Pocket Outlook you need to select the folder to use (Calendar, Tasks, and Contacts) and then work with a collection of items in that folder. This involves using POOM interfaces other than IPOutlookApp. The IUnknown interface function "QueryInterface" can be used to obtain other interfaces in a component object. However, when using object models, it is more usual for an interface function to return a pointer to another interface that is set up to refer to a data item, or whatever is appropriate.

As an example, Listing 14.6 shows how to locate the contact item for a specified contact by performing the following steps:

  • Call the IPOutlookApp interface's GetDefaultFolder function, passing a constant indicating which folder to return (for example, olFolderContacts). This returns an IFolder interface pointer.

  • Call the IFolder interface function get_Items to return an IPOutlookItemCollection interface pointer. This interface allows access to all items in the contacts folder.

  • Use the IPOutlookItemCollection interface function Find to locate a single contact and return an IContact interface pointer to the specified contact.

  • Use IContact interface functions such as get_FirstName to access contact information.

  • Call SysFreeString on each of the BSTR data items returned from IContact functions.

  • Call Release on each of the interface pointers returned in this function.

The IPOutlookItemCollection interface is a collection class that allows access to a group of items. The interface allows a single item to be returned using the Find function (which supplies the search criteria) or the Item method that returns an item given a 1-based index. The get_Count function returns the number of items in the collection.

Listing 14.6 Finding a contact's email address
 void Listing14_6() {   IFolder *pFolder;   IPOutlookItemCollection *pItems;   IContact *pContact;   BSTR szFirstName, szLastName, szEmail;   int nItems;   g_poomApp->GetDefaultFolder(olFolderContacts,         &pFolder);   if(pFolder == NULL)   {     cout   _T("Could not get contacts folder")            endl;     return;   }   pFolder->get_Items(&pItems);   pItems->get_Count(&nItems);   cout   _T("Number of contacts: ")   nItems   endl;   pItems->Find(     _T("[LastName] =  \"Grattan\" AND \             [FirstName] =  \"Nick\""),         (IDispatch**)&pContact);   pContact->get_FirstName(&szFirstName);   pContact->get_LastName(&szLastName);   pContact->get_Email1Address(&szEmail);   cout   szFirstName   _T(" ")               szLastName   _T(" ")               szEmail   endl;   SysFreeString(szFirstName);   SysFreeString(szLastName);   SysFreeString(szEmail);   pContact->Release();   pItems->Release();   pFolder->Release(); } 

POOM functions that return an interface pointer require the pointer variable to be cast to the IDispatch** data type. The IDispatch interface is a standard interface used to support Automation calls and is described later in this chapter.

Calling QueryInterface

The IUnknown QueryInterface function allows an application to obtain a pointer to another interface from a component object interface pointer. As shown in the previous section, many interfaces support specialized functions for getting interface pointers, but there are times when QueryInterface is essential. For example, POOM supports the IPOlItems interface derived from IPOutlookItemCollection and is optimized to provide fast, efficient, read-only access to a collection of items. A IPOlItems interface pointer is obtained by calling QueryInterface, as shown in Listing 14.7.

Listing 14.7 Calling QueryInterface
 void Listing14_7() {   IFolder *pFolder;   IPOutlookItemCollection *pItems;   IPOlItems *pItems2;   IContact *pContact;   BSTR szFirstName, szLastName;   int nItems;   g_poomApp->GetDefaultFolder(olFolderContacts,         &pFolder);   if(pFolder == NULL)   {     cout   _T("Could not get contacts folder")            endl;     return;   }   pFolder->get_Items(&pItems);   pFolder->Release();   pItems->QueryInterface(IID_IPOlItems,       (LPVOID *) &pItems2);   pItems->Release();   if(pItems2 == NULL)     cout   _T("Query Interface Failed")   endl;   else   {     pItems2->SetColumns(_T("LastName, FirstName"));     pItems2->get_Count(&nItems);     cout   _T("Contacts: ")   nItems   endl;     for(int i = 1; i = nItems; i++) // NB: 1 Based!     {       pItems2->Item(i, (IDispatch**)&pContact);       if(pContact == 0)       {         cout   _T("Could not get contact")                endl;         break;       }       else       {         pContact->get_FirstName               (&szFirstName);         pContact->get_LastName(&szLastName);         cout   szFirstName   _T(" ")                szLastName    endl;         SysFreeString(szFirstName);         SysFreeString(szLastName);       }     }     pItems2->Release();   } } 

QueryInterface (Table 14.3) is called through an existing interface pointer. The function is passed the IID (Interface ID of the interface, for example, IID_IPOlItems) and a pointer to a pointer variable to receive the interface pointer (pItems2).

Notice how the code in Listing 14.7 calls Release on an interface pointer as soon as it has finished with the function and not at the end of the function. So long as there is a single outstanding reference on the interfaces in a component, the component will continue to exist, and so this is safe. Some programmers prefer to call Release when they have finished with the interface, while others prefer to call Release at the end of the function where it may be easier to check that all the interfaces have Release called on them.

Table 14.3. QueryInterface Returns a pointer to another interface
QueryInterface
REFIID iid, Reference to the interface identifier for the interface to be returned
void ** ppvObject Pointer to a pointer variable in which the interface pointer will be returned
HRESULT Return Value Returns E_NOINTERFACE if the interface is not supported, or S_OK on success

The IPOlItems implements a function called SetColumns that specifies the column names to be retrieved. This is efficient because only the named columns will be returned rather than all the columns, as is the case with IPOutlookItemCollection. In Listing 14.7 a "for" loop is used to retrieve each contact (using the "Item" function) in the collection. The first name (using get_FirstName) and the last name (using get_LastName) are retrieved and displayed. Note that Item returns the contact using a 1-based rather than a 0-based index.

Adding a Contact

When an interface pointer is passed to a function, AddRef should be called on the interface pointer by the caller function. The called function should call Release when it has finished using the interface pointer. This is illustrated in Listing 14.8, in which the function AddContact is called to add a new contact to the Contacts folder. The pItems interface function calls AddRef before calling AddContact, which itself calls Release before returning.

POOM allows new contacts to be added using the IContact interface. The IPOutlookItemCollection has an Add function that returns an IContact interface pointer, and the put_ functions are used to set the data for the new contact. Finally, the IContact interface's Save function saves the data into the Contacts folder.

Listing 14.8 Adding a contact
 void AddContact(IPOutlookItemCollection *pItems,     LPTSTR szFirstName, LPTSTR szLastName) {   IContact *pContact;   pItems->Add((IDispatch**)&pContact);   if(pContact == NULL)   {     cout   _T("Could not get IContact interface")            endl;     return;   }   pContact->put_FirstName(szFirstName);   pContact->put_LastName(szLastName);   if(FAILED(pContact->Save()))     cout   _T("Could not save contact")   endl;   pContact->Release();   pItems->Release(); } void Listing14_8() {   IPOutlookItemCollection *pItems;   IFolder *pFolder;   g_poomApp->GetDefaultFolder(olFolderContacts,          &pFolder);   if(pFolder == NULL)   {     cout   _T("Could not get contacts folder")            endl;     return;   }   pFolder->get_Items(&pItems);   pItems->AddRef();   AddContact(pItems, _T("XXXXX"), _T("ZZZZZ"));   pItems->Release();   pFolder->Release(); } 

The IPOutlookApp interface function CreateItem can be used to create new contact, appointment, and other types of items without opening the folder or obtaining an IPOutlookItemCollection. Listing 14.10, in the section "Creating a Recurring Appointment," shows an example of using the CreateItem function.


< BACK  NEXT >


Windows CE 3. 0 Application Programming
Windows CE 3.0: Application Programming (Prentice Hall Series on Microsoft Technologies)
ISBN: 0130255920
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net