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 COMThe 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:
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 COMvoid Listing14_2() { CoUninitialize(); cout _T("COM Uninitialized") endl; } Creating a COM ObjectOnce COM has been initialized, component objects can be created using calls to CoCreateInstance (Table 14.2). A call to CoCreateInstance specifies the following:
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.
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 FunctionsEach interface has functions that can be called through the interface pointer returned from CoCreateInstance. The IPOutlookApp interface has three important functions:
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 methodsvoid 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 TypeThe 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:
Releasing COM InterfacesCOM 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 interfacesvoid 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:
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 AddressSo 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:
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 addressvoid 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 QueryInterfaceThe 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 QueryInterfacevoid 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.
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 ContactWhen 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 contactvoid 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.
|