Data Transfer, COM Enums, and Windows Controls

Transferring information from COM enumerators to windows controls can be complex. Two methods are presented here: a C-API written in C, which sacrifices run-time efficiency for succinct client code and run-time flexibility, and a method using STL sequences and function objects, which is more elegant and also likely to be more efficient.


November 01, 2003
URL:http://www.drdobbs.com/data-transfer-com-enums-and-windows-cont/184416702

Data Transfer, COM Enums, and Windows Controls


These days, software engineering involves a lot of flexibility. We have different programming paradigms, component object models, cooperating languages, even different binary standards. Many Windows-based developers would probably say that component software development began with COM. Others might say DLLs (and exported functions). A few might even say Windows controls. In a way, they're all right.

One of the challenges of working within even modern frameworks is the cost of development and maintenance time (not to mention run time) in handling all this flexibility. Several years ago, in order to obviate these costs, I wrote a C-API for data exchange between COM enumerators (instances implementing one or more of the IEnumXXXX family of interfaces) and Windows controls. I've updated part of that API to present here.

C++ development has recently (and wisely) swung towards the STL model of compile-time development. Whilst STL is no real help in binary interoperability, it is extremely useful for the implementation of binary interoperable components (and for pretty much every other kind of C++ software development that you can think of). STL has become extremely popular because it supports reuse (in a lightweight manner — not more hefty hierarchies), maintainability, and very importantly, efficiency (or at least it does when used correctly). I've embraced STL in a big way: I have created the STLSoft project (http://stlsoft.org/) over the last couple of years in order to apply STL techniques to a broad range of technologies and operating systems (including Win32, UNIX, ATL, MFC, COM, WTL, XML, and .NET) in a robust and efficient manner. Two of the STLSoft subprojects, COMSTL (http://comstl.org/) and WinSTL (http://winstl.org/), contain components for manipulating COM objects and windows controls. I will show how collaboration between these components can provide an interesting alternative to the data exchange C-API.

C-API in C

Let's first look at the C-API. C++ aficionados might well wonder why anyone would bother writing a COM API in C. There are two answers. First, I like to write in C sometimes because it keeps the true understanding of the mechanics of COM to the fore in the brain. (Anyone who thinks that one can just use frameworks and never get into the guts has not written any real COM programs.) The second is that, when creating software that may be reused (in source form as well as binary), it is a good idea not to disenfranchise those developers who still prefer to exist in the C world. (Also, C affords you the opportunity to change the contents of the vtable at run time. It's very useful when you need it, but to be used very sparingly, and is not for the faint hearted.)

These observations notwithstanding, I acknowledge the masochism required to create such an API in C, and in your reading the code presented here. I would suggest that you persevere, mainly for the illumination previously alluded to.

Listing 1 shows the API flags, typedefs, structures, and function declarations. Skipping over what looks like a rather complex set of structures for the moment, we see there are two functions. The second one of these, ListExchange_PutItems(), has a manageable three arguments. They represent the window handle of the control, the enumerator interface, and flags, respectively. Hence, to populate, say, a list box with the contents of an IEnumVARIANT, one would call:

ListExchange_PutItems(hwndListBox, penVar,
 		SYLXF_RESETCONTENT);

The SYLXF_RESETCONTENT flag causes the list control to be emptied prior to populating with the enumerator contents. Hence, you can populate a list control with the contents of several enumerators, thus:

ListExchange_PutItems(hwndComboBox, 
    penVar1, SYLXF_RESETCONTENT);
ListExchange_PutItems(hwndComboBox, 
    penOleStr, 0);
ListExchange_PutItems(hwndComboBox, 
    penBSTR, 0);

That seems nice and straightforward. But how does it work? Naturally, the answer relates to the structures and the other function. In fact, if we look at Listing 2, we can see that ListExchange_PutItems() is actually implemented in terms of ListExchange_PutItems_Base(). Arrays of EnumeratorHandler structures and ControlHandler structures are defined and passed to ListExchange_PutItems_Base(). These structures describe the COM enumerators and windows controls that will be understood by the API. You can see that ListExchange_PutItems() understands the IEnumString, IEnumBSTR, IEnumVARIANT, IEnumGUID, and IEnumUnknown interfaces and the list-box, combo-box, list-view, and (multiline) edit controls that, for most requirements, more than suffices. (Note that there are two IEnumBSTR and IEnumGUID interfaces handled. This is because this API was defined and used with those Synesis-defined interfaces before the Microsoft interfaces of the same name were released; hence the need to support both. However, this dual support causes no problems since the interfaces are identically defined.)

The handlers are reasonably straightforward. The control handler contains the window class (an identifying type that all windows will provide via the GetClassName() API function), and functions to add an item and to reset the contents. The enumerator handler contains the Interface Identifier (IID) and functions to reset the enumerator, acquire an item's value (which it does via calling the Next() interface method), and to release an item's value. Because the functions operate with the Enum2WndItem item union and have an opaque handle (void*) for the value type (both defined in Listing 1), the API can be expanded to handle any enumerated value type simply by appropriately defining matching handler functions.

You may call ListExchange_PutItems() and use the predefined handlers, or set up your own handler arrays and directly call ListExchange_PutItems_Base(), which is where all the action happens (see Listing 3). The first few lines validate the arguments, followed by the first task of identifying the type of the window. The function iterates through the array looking for a matching control handler. If it fails to find one, then no further processing is done and the function returns a fail code (and writes to the thread's error object). Next is the test for the enumerator. If no enumerator is specified, then the control is simply emptied. It's not exactly orthogonal programming, but I just found it a real convenience (see Listing 6) to be able to clear the contents of any list control, no matter what its type, so this feature creep was let by. If an enumerator is specified, then it is tested against the enumerator handler information. If recognized, the control is cleared when requested — note that this is not done earlier since a failure in a specified enumerator would change the list without populating it, and it is not generally a good idea to have a failing API effect changes if it can be avoided — and then the enumeration is conducted.

Enumeration begins with resetting the enumeration point. The enumerator handler's pfnReset function is passed the enumerator interface pointer, which calls the IEnumXxxx:: Reset() method on the interface. Since all IEnumXxxx enumerator interfaces have the same general definition, and identical Reset() and Skip() method definitions, we can simply define one handler, EnumXXXX_Reset(), to be used for all interfaces, as can be seen in Listing 4; available online (the choice to cast to IEnumString is arbitrary). Inside the loop, the enumerator's Next() method is called within the enumerator handler's pfnNextItem function, passing in the instances of Enum2WndItem and LPVOID. If the function call returns S_OK, then the value is passed to the control handler's pfnPutItem function, and is thus added to the control. The value is then cleared (with pfnClearItem), so that any allocation in the enumerator handler's pfnNextItem function can be released. If the value returned from Next() is S_FALSE, then there are no more items available (and the return code is changed to S_OK before breaking out of the loop because S_FALSE is not meaningful to callers of the API). Any error return codes will cause the loop to terminate and will be passed back to the caller.

Note that the enumeration loop retrieves and inserts one item at a time. This is necessary because items from an arbitrary enumerator could be of any size. While it is certainly possible to cater for retrieving multiple items at a time, it would complicate the implementation of the handlers to a degree that I determined would not be warranted. (The efficiencies gained from reduced round-trips to COM enumerators are associated with marshalling across apartment/process/machine, and most enumerators from which one would wish to retrieve will likely be in the calling apartment. If not, marshal-by-value is also commonly used.) Also, all of the standard window handlers cause each string item to be added after the previous one, which covers most cases, but may not be what you want.

That's the main function. What remains is to see how the handlers are implemented. Listing 4 (available online) shows a selection of the enumerator handlers, and Listing 5 shows some of the control handlers. I won't go into too much detail as I've already discussed their required semantics. There are, however, a few points of interest that I'll briefly discuss.

First, the standard controls have to do conversions of the text to ANSI if the windows are not Unicode (determined by the calls to IsWindowUnicode()); there is no need to do this for the list view because, like all the Common Controls, it can work with both ANSI and Unicode messages. Second, the edit control handler works by moving the selection to the end of the contents, pasting the value text, repeating the selection, and pasting CR-LF. This is done in two parts to remove the need to concatenate the value and CR-LF, but it is conceivably open to race conditions (you'd have to write an atomic version if this was a possibility in your application). The rest of the implementations are fairly obvious, except the IEnumUnknown handler, which is discussed in the sidebar titled "DISPID_VALUE" (available online).

C-API in STL

ListExchange_PutItems() is a good solution for handling a variety of cases, whether for easy prototyping where one may wish to change list control types, or for production code that needs to handle multiple enumerator interfaces. However, using it constrains you to the enumerator and control types understood by the provided handlers, which may not cover your particular needs, or requires you to define your own, which is a fair amount of effort.

It is also a bit difficult to modify the items as they go into the list controls. This would require using special enumerator and control handler functions to change the value. The API does cater for that — by defining the value type to be void * and not LPCOLESTR, and by providing a user-supplied parameter into ListExchange_PutItems_Base() that is passed through to the control handler's pfnPutItem — but it's not a trivial effort to use it in this way.

The C-API was written a long time before STL became popular, and there are now alternatives if you prefer to be in the STL world entirely. Perhaps you only need to populate one kind of control and from one kind of enumeration interface, or you want to modify the contents of the retrieved items with adaptor functionals (function objects). Two STLSoft subprojects, COMSTL and WinSTL, provide components that, when used in concert, provide a very simple solution. If you have an IEnumString enumerator and you want to populate a list-box, the following code will do that:

LPENUMSTRING  penStr      = . . . // An IEnumString instance
HWND          hwndListBox = . . . // and a window to put it in
// Declare an enumerator sequence for //IEnumString
comstl::enum_simple_sequence< IEnumString
    , LPOLESTR
    , comstl::LPOLESTR_policy
    , LPCOLESTR
    , comstl::input_cloning_policy<IEnumString>
    , 5 // Next() five at a time
    > strings(penStr, true); // borrows the reference
std::for_each(strings.begin(), strings.end(),
              winstl::listbox_back_inserter(hwndListBox));

Alternatively, if you have IEnumVARIANT and you want it to go into a combo box in reverse order:

comstl::enum_simple_sequence< IEnumVARIANT
    , VARIANT
    , comstl::VARIANT_policy
    , VARIANT const
    , comstl::input_cloning_policy<IEnumVARIANT>
    , 10 // Next() 10 at a time
    > strings(penVar, false); // eats the reference
std::for_each(strings.begin(), strings.end(),
              winstl::combobox_front_insert
			er(hwndComboBox));

(It looks like a lot of code, but almost all of it is the parameterization of the enumerator sequence template. In real code, these are usually typedef'd.) As well as not needing to compile and link in the C-API, this has some other advantages. The sixth parameter to the template is a number, which specifies the number of items to acquire in each call to the enumerator's Next() method. Where this is important (e.g., when marshalling between apartments or between hosts), it allows you to increase efficiency by reducing round-trips by parameterizing the template appropriately.

You could also provide modification of the items before they go into the list control by applying an adaptor function (e.g., to set to uppercase) to the control inserter functional, as in:

template <typename F>
struct upper_adaptor;
template <typename F>
upper_adaptor<F> make_upper_adaptor(F );
for_each( strings.begin(), strings.end(),
          make_upper_adaptor(combobox_front_inserter(hwnd 		ComboBox)));

Test client

Included in the online archive is the test program, shown in Figure 1. The dialog allows you to select one of five enumerator types (each of which are filled with sample data when they're created) and to select one of four list windows. Listing 6 shows some functions from the test program. MOCtlFns_Test_GetListWindow() is a helper function that retrieves the ID of the list control that is associated with a tab control item, and returns the corresponding window handle. It is used by MOCtlFns_Test_OnClear(), which demonstrates the use of ListExchange_PutItems() to clear out any list control, and by MOCtlFns_Test_OnPut(), which demonstrates the list population. MOCtlFns_Test_OnPut() obtains the appropriate COM enumerator from another function in the test program, MOCtlFns_Test_GetEnumerator(), which synthesizes an enumerator of the appropriate type (as determined by the "Source" radio group). In real code, you'd have received the enumerator from a source and would likely obtain the control from GetDlgItem(), so your code would be very succinct.

Listing 7 shows a solution based around the COMSTL enum_simple_sequence<> sequence class and the WinSTL control (standard and common) inserter functionals. Clearly, there is a lot of code to write, but this is just the demonstration program. In real-world scenarios, you'd use the C-API if you need this level of complexity, and the COMSTL/WinSTL components if you didn't.

Iterator concepts and IEnumXXXX::Clone()

A full description of the iterator concept is given at http://sgi.com/tech/stl/Iterators.html, and an in-depth treatment of the issues involved in writing iterators and sequences is given in my March 2003 WDN article (see References), so I'll try and be very brief here. An input iterator is something from which one may acquire its value only once. When copied, the ownership of the "range" is passed to the copy, making subsequent retrieval from the original invalid. A forward iterator is something from which one may take a copy and retrieve meaningful results from both copies.

COM programmers will recognize that the COM enumerator model describes a forward iterator semantic, by the Clone() method provided by all IEnumXxxx enumerator interfaces, which is defined to return a copy of the source enumerator that is at the same point in its enumeration as the source. However, it is legal for the Clone() method to fail, so were we to implement enum_simple_sequence's iterators as forward iterators, that could lead to trouble when it did so. Conversely, most enumerators succeed a call to Clone(), so were we to implement the iterators as input iterators, we would be restricting functionality in a large number of cases where that would not be necessary. Hence, the COMSTL enum_simple_sequence<> template takes a cloning policy so that users can select the desired iterator concept support based upon their needs. COMSTL provides several cloning policies, including:

In our code, we have used the input_cloning_policy<> since in all cases, we only need one byte at the cherry of the enumerator ranges.

Summary

This article has examined the common but programmatically complex situation of transferring information from COM enumerators to windows controls. In a sense, we've looked at the two ends of the spectrum of modernity in our two methods. The old way, a C-API written in C, sacrifices some run-time efficiency for succinct client code and flexibility in the things that it can handle at run time. It has the slight disadvantage that it must work with one enumerated item at a time. It has a nice by-product that the list control can be cleared by a single function call without needing to worry about the window class. This is a piece of flexibility that can only be achieved at run time, since all windows are identified at compile time by their HWND only, and are distinguished by their window class that is obtained by a function call (GetClassName()).

The new way, using STL sequences and function objects, is more appealing to those (of us) who revel in intellectual elegance. It's also likely to be more efficient, since there are no run-time checks carried out on enumerator and list-control types, and it can retrieve multiple items in each call to IEnumXxxx::Next(). As is the case with generic (template) programming, you do have polymorphism, but it is compile time (i.e., in the type resolution and template parameterization). If this suits your needs, this is great. If you need to deal with multiple potential interfaces at run time, then you should stick with the C-API.

As with all things in software engineering, there is no absolute best approach. Each of these two approaches reflects an optimal solution to a particular set of circumstances. In one set of circumstances, you would use one; in a different set, the other. It's just a case of filling your bag of tricks with enough tricks.

Finally, you may be wondering whether I was going to discuss data exchange in the other direction? Well I'm sure it won't surprise you to learn that I've got both C-API and STL techniques for doing this, but that's another (longer) article.

References

Generic Programming and the STL, Matt Austern, Addison-Wesley, 1998.

STLSoft is an open-source organization applying STL techniques to a variety of technologies and operating systems. It is located at http://stlsoft.org/. COMSTL and WinSTL are subprojects, and are located at http://comstl.org/ and http://winstl.org/, respectively.

"Adapting Win32 Enumeration APIs to STL Iterator Concepts," Matthew Wilson, Windows Developer Magazine, March 2003.

"Inserter Function Objects for Windows Controls," Matthew Wilson, Windows Developer Network, November 2003.


Matthew Wilson is a software development consultant for Synesis Software, specializing in robustness and performance in C, C++, C#, and Java. Matthew is the author of the STLSoft libraries, and the forthcoming book Imperfect C++ (to be published by Addison-Wesley, 2004). He can be contacted via matthew @synesis.com.au or at http:// stlsoft.org/.

Data Transfer, COM Enums, and Windows Controls

Data Transfer, COM Enums, and Windows Controls

Listing 1 Definitions of the ListExchange structures and functions


/* /////////////////////////////////////////////////////////////
 * ...
 *
 * Extract from MOCtlFns.h
 *
 * Copyright (C) 1998-2003, Synesis Software Pty Ltd.
 * (Licensed under the Synesis Software Standard Public License:
 *  http://www.synesis.com.au/licenses/ssspl.html)
 *
 * ...
 * ////////////////////////////////////////////////////////// */

/* Flags */

#define SYLXF_RESETCONTENT  (0x00000001) /* Empties control first */

/* Typedefs */

typedef union _Enum2WndItem
{
  LPOLESTR    olestr; /* OLE string */
  BSTR        bstr; /* COM BSTR */
  VARIANT     var;  /* COM VARIANT type */
  GUID        guid; /* GUID */
  LPUNKNOWN   punk; /* Object */
  LPDISPATCH  pdisp;  /* Automation object */
  void        *pv;  /* any other type */

} Enum2WndItem;
__SyPtrType1(Enum2WndItem)

typedef HRESULT (*PFnEnum_Reset)(   LPVOID          itf);

typedef HRESULT (*PFnEnum_NextItem)(LPVOID          itf,
                                    LPEnum2WndItem  item,
                                    LPVOID          *pvalue);

typedef void (*PFnEnum_ClearItem)(  LPVOID          itf,
                                    LPEnum2WndItem  item,
                                    LPVOID          value);

typedef struct _EnumeratorHandler
{
  REFIID            iid;
  PFnEnum_Reset     pfnReset;
  PFnEnum_NextItem  pfnNextItem;
  PFnEnum_ClearItem pfnClearItem;

} EnumeratorHandler;

typedef HRESULT (*PFnCtrl_PutItem)( HWND      hwndCtrl,
                                    LPVOID    str,
                                    UInt32    flags,
                                    UInt32    param);

typedef void (*PFnCtrl_Reset)(      HWND  hwndCtrl);

typedef struct _ControlHandler
{
  char const      *className;
  PFnCtrl_PutItem pfnPutItem;
  PFnCtrl_Reset   pfnReset;

} ControlHandler;

/* Functions */

SInt32 ListExchange_PutItems_Base(HWND                    hwndList,
                                  LPUNKNOWN               punkEnumItems,
                                  UInt32                  flags,
                                  UInt32                  param,
                                  EnumeratorHandler const *enumerators,
                                  UInt32                  cEnumerators,
                                  ControlHandler const    *controls,
                                  UInt32                  cControls);

SInt32 ListExchange_PutItems( HWND      hwndList,
                              LPUNKNOWN punkEnumItems,
                              UInt32    flags);

Data Transfer, COM Enums, and Windows Controls

Listing 2 Implemenation of ListExchange_PutItems()


/* /////////////////////////////////////////////////////////////
 * ...
 *
 * Extract from MOCtlFns.c
 *
 * Copyright (C) 1998-2003, Synesis Software Pty Ltd.
 * (Licensed under the Synesis Software Standard Public License:
 *  http://www.synesis.com.au/licenses/ssspl.html)
 *
 * ...
 * ////////////////////////////////////////////////////////// */

SInt32 ListExchange_PutItems( HWND      hwndList, 
                              LPUNKNOWN punkEnumItems,
                              UInt32    flags)
{
  static const IID IID_IEnumBSTR_Synesis  = { . . . };
  static const IID IID_IEnumBSTR_MS       = { . . . };
  static const IID IID_IEnumGUID_Synesis  = { . . . };
  static const IID IID_IEnumGUID_MS       = { . . . };

  static const EnumeratorHandler  enumerators[] = 
  {
      {   &IID_IEnumString,     EnumXXXX_Reset
        , EnumString_NextItem,  EnumString_ClearItem  }
    , { &IID_IEnumBSTR_Synesis, EnumXXXX_Reset
        , EnumBSTR_NextItem,    EnumBSTR_ClearItem    }
    , { &IID_IEnumBSTR_MS,      EnumXXXX_Reset
        , EnumBSTR_NextItem,    EnumBSTR_ClearItem    }
    , { &IID_IEnumVARIANT,      EnumXXXX_Reset
        , EnumVARIANT_NextItem, EnumVARIANT_ClearItem }
    , { &IID_IEnumGUID_Synesis, EnumXXXX_Reset
        , EnumGUID_NextItem,    EnumGUID_ClearItem    }
    , { &IID_IEnumGUID_MS,      EnumXXXX_Reset
        , EnumGUID_NextItem,    EnumGUID_ClearItem    }
    , { &IID_IEnumUnknown,      EnumXXXX_Reset
        , EnumUnknown_NextItem, EnumUnknown_ClearItem }
  };
  static const ControlHandler   controls[] = 
  {
      { "LISTBOX",        ListBox_PutItem,  ListBox_Reset   }
    , { "COMBOBOX",       ComboBox_PutItem, ComboBox_Reset  }
    , { "SysListView32",  ListView_PutItem, ListView_Reset  }
    , { "EDIT",           Edit_PutItem,     Edit_Reset      }
  };

  return ListExchange_PutItems_Base(hwndList, punkEnumItems,
          flags, 0, enumerators, NUM_ELEMENTS(enumerators),
          controls, NUM_ELEMENTS(controls));
}

Data Transfer, COM Enums, and Windows Controls

Listing 3 Implemenation of ListExchange_PutItems_Base()


/* /////////////////////////////////////////////////////////////
 * ...
 *
 * Extract from MOCtlFns.c
 *
 * Copyright (C) 1998-2003, Synesis Software Pty Ltd.
 * (Licensed under the Synesis Software Standard Public License:
 *  http://www.synesis.com.au/licenses/ssspl.html)
 *
 * ...
 * ////////////////////////////////////////////////////////// */

SInt32 ListExchange_PutItems_Base(
        HWND                    hwndList,
        LPUNKNOWN               punkEnumItems,
        UInt32                  flags,
        UInt32                  param,
        EnumeratorHandler const *enumerators,
        UInt32                  cEnumerators,
        ControlHandler const    *controls,
        UInt32                  cControls)
{
  HRESULT hr;

  _ASSERTE(IsWindow(hwndList));

  if( !(flags & SYLXF_RESETCONTENT) &&
      ( NULL == punkEnumItems ||
        NULL == enumerators ||
        0 == cEnumerators))
  {
    hr = E_INVALIDARG;
  }
  else if(!IsWindow(hwndList) ||
          0 == cControls ||
          NULL == controls)
  {
    hr = E_INVALIDARG;
  }
  else
  {
    LPUNKNOWN punkEnum  = NULL;
    UInt32    i;

    /* Find the window class */
    for(hr = E_FAIL, i = 0; FAILED(hr) && i < cControls; ++i)
    {
      if(IsWindowClass(hwndList, controls[i].className))
      {
        hr = S_OK;
        break;
      }
    }

    if(FAILED(hr))
    {
      ErrorInfo_SetDescW(L"Window not of recognised class");

      hr = E_INVALIDARG;
    }
    else
    {
      ControlHandler const  *control  = &controls[i];

      if(NULL == punkEnumItems)
      {
        /* Only want to clear the window */

        _ASSERTE((flags & SYLXF_RESETCONTENT) == SYLXF_RESETCONTENT);

        control->pfnReset(hwndList);
      }
      else
      {
        /* Find the interface */
        for(hr = E_NOINTERFACE, i = 0; i < cEnumerators; ++i)
        {
#ifdef __cplusplus
          hr = punkEnumItems->QueryInterface(
#else
          hr = punkEnumItems->lpVtbl->QueryInterface( punkEnumItems,
#endif /* __cplusplus */
                                enumerators[i].iid,
                                (LPPVoid)(&punkEnum));

          if(SUCCEEDED(hr))
          {
            break;
          }
        }

        if(FAILED(hr))
        {
          ErrorInfo_SetDescW(L"Enumerator not of recognised type");

          hr = E_INVALIDARG;
        }
        else
        {
          EnumeratorHandler const *enumerator = &enumerators[i];

          /* Erase the existing content */
          if((flags & SYLXF_RESETCONTENT) == SYLXF_RESETCONTENT)
          {
            control->pfnReset(hwndList);
          }

          /* Now enumerate over the sequence, inserting as we go. */
          for(hr = S_OK, enumerator->pfnReset(punkEnum); SUCCEEDED(hr); )
          {
            Enum2WndItem  item;
            LPVOID      value;

            hr  = enumerator->pfnNextItem(punkEnum, &item, &value);

            if(hr == S_OK)
            {
              /* Add to the control ... */
              hr = control->pfnPutItem(hwndList, value, flags);

              /* ... and release any resources from enumerator */
              enumerator->pfnClearItem(punkEnum, &item, value);
            }
            else if(hr == S_FALSE)
            {
              hr = S_OK;
              break;
            }
          }

#ifdef __cplusplus
          punkEnum->Release();
#else
          punkEnum->lpVtbl->Release(punkEnum);
#endif /* __cplusplus */
        }
      }
    }
  }

  return hr;
}

Data Transfer, COM Enums, and Windows Controls

Listing 4

// Listing 4: Implementation of enumerator handlers

/* /////////////////////////////////////////////////////////////
 * ...
 *
 * Extract from MOCtlFns.c
 *
 * Copyright (C) 1998-2003, Synesis Software Pty Ltd.
 * (Licensed under the Synesis Software Standard Public License:
 *  http://www.synesis.com.au/licenses/ssspl.html)
 *
 * ...
 * ////////////////////////////////////////////////////////// */

/* IEnumXXXX */

HRESULT EnumXXXX_Reset(LPVOID itf)
{
  LPENUMSTRING  pen = (LPENUMSTRING)itf;

  return pen->lpVtbl->Reset(pen);
}

/* IEnumString */

HRESULT EnumString_NextItem(LPVOID itf, LPEnum2WndItem pitem, LPVOID *pvalue)
{
  LPENUMSTRING  pen = (LPENUMSTRING)itf;
  HRESULT       hr  = pen->lpVtbl->Next(pen, 1, &pitem->olestr, NULL);

  if(hr == S_OK)
  {
    *pvalue = pitem->olestr;
  }

  return hr;
}

void EnumString_ClearItem(LPVOID pen, LPEnum2WndItem pitem, LPVOID value)
{
  CoTaskMemFree(pitem->olestr);
}

/* IEnumBSTR */

HRESULT EnumBSTR_NextItem(LPVOID itf, LPEnum2WndItem pitem, LPVOID *pvalue)
{
  LPENUMBSTR  pen = (LPENUMBSTR)itf;
  HRESULT     hr  = pen->lpVtbl->Next(pen, 1, &pitem->bstr, NULL);

  if(hr == S_OK)
  {
    *pvalue = pitem->bstr;
  }

  return hr;
}

void EnumBSTR_ClearItem(LPVOID pen, LPEnum2WndItem pitem, LPVOID value)
{
  SysFreeString(pitem->bstr);
}

/* IEnumVARIANT */

HRESULT EnumVARIANT_NextItem(LPVOID itf, LPEnum2WndItem pitem, LPVOID *pvalue)
{
  LPENUMVARIANT pen = (LPENUMVARIANT)itf;
  HRESULT       hr  = pen->lpVtbl->Next(pen, 1, &pitem->var, NULL);

  if(hr == S_OK)
  {
    hr = VariantChangeType(&pitem->var, &pitem->var, VARIANT_ALPHABOOL, VT_BSTR);

    if(SUCCEEDED(hr))
    {
      *pvalue = pitem->var.bstrVal;
    }
    else
    {
      VariantClear(&pitem->var);
    }
  }

  return hr;
}

void EnumVARIANT_ClearItem(LPVOID pen, LPEnum2WndItem pitem, LPVOID value)
{
  VariantClear(&pitem->var);
}

/* IEnumGUID */

HRESULT EnumGUID_NextItem(LPVOID itf, LPEnum2WndItem pitem, LPVOID *pvalue)
{
  LPENUMGUID  pen = (LPENUMGUID)itf;
  HRESULT     hr  = pen->lpVtbl->Next(pen, 1, &pitem->guid, NULL);

  if(hr == S_OK)
  {
    hr = StringFromCLSID(&pitem->guid, SyCastRaw(LPOLESTR*, pvalue));
  }

  return hr;
}

void EnumGUID_ClearItem(LPVOID pen, LPEnum2WndItem pitem, LPVOID value)
{
  OleString_Destroy(SyCastRaw(LPOLESTR*, &value));
}

/* IEnumUnknown */

HRESULT EnumUnknown_NextItem(LPVOID itf, LPEnum2WndItem pitem, LPVOID *pvalue)
{
  LPENUMUNKNOWN pen = (LPENUMUNKNOWN)itf;
  HRESULT       hr  = pen->lpVtbl->Next(pen, 1, &pitem->punk, NULL);

  if(hr == S_OK)
  {
    /* Use the VariantChangeType() mechanism to get the value property of 
     * the object.
     */
    VARIANT dest;

    VariantInit(&dest);

    hr = Dispatch_GetProperty(pitem->punk, DISPID_VALUE, &dest);

    if(SUCCEEDED(hr))
    {
      hr = VariantChangeType(&dest, &dest, VARIANT_ALPHABOOL, VT_BSTR);

      if(SUCCEEDED(hr))
      {
        *pvalue = OleString_Dup(dest.bstrVal);
      }

      VariantClear(&dest);
    }

    if(NULL != pitem->punk)
    {
      pitem->punk->lpVtbl->Release(pitem->punk);
    }
  }

  return hr;
}

void EnumUnknown_ClearItem(LPVOID pen, LPEnum2WndItem pitem, LPVOID value)
{
  OleString_Destroy(SyCastRaw(LPOLESTR*, &value));
}

Data Transfer, COM Enums, and Windows Controls

Listing 5 Implementation of control handlers


/* /////////////////////////////////////////////////////////////
 * ...
 *
 * Extract from MOCtlFns.c
 *
 * Copyright (C) 1998-2003, Synesis Software Pty Ltd.
 * (Licensed under the Synesis Software Standard Public License:
 *  http://www.synesis.com.au/licenses/ssspl.html)
 *
 * ...
 * ////////////////////////////////////////////////////////// */

/* Listbox */

HRESULT ListBox_PutItem(HWND hwnd, LPVOID value, UInt32 flags, UInt32 param)
{
  HRESULT hr;

  if(IsWindowUnicode(hwnd))
  {
    hr = (ListBox_AddString(hwnd, (LPCOLESTR)value) < 0)
            ? E_FAIL : S_OK;
  }
  else
  {
    . . . // Do conversion to ANSI and add
  }

  return hr;
}

void ListBox_Reset(HWND hwnd)
{
  ListBox_ResetContent(hwnd);
}

/* Combobox */

HRESULT ComboBox_PutItem(HWND hwnd, LPVOID value, UInt32 flags, UInt32 param)
{
  HRESULT hr;

  if(IsWindowUnicode(hwnd))
  {
    hr = (ComboBox_AddString(hwnd, (LPCOLESTR)value) < 0)
            ? E_FAIL : S_OK;
  }
  else
  {
    . . . // Do conversion to ANSI and add
  }

  return hr;
}

void ComboBox_Reset(HWND hwnd)
{
  ComboBox_ResetContent(hwnd);
}

/* Listview */

HRESULT ListView_PutItem(HWND hwnd, LPVOID value, UInt32 flags, UInt32 param)
{
  LV_ITEMW  item;

  item.mask     = LVIF_TEXT;
  item.iItem    = ListView_GetItemCount(hwnd); /* add to end */
  item.iSubItem = 0;
  item.pszText  = (LPWSTR)value;

  return (int)SendMessage(hwnd, LVM_INSERTITEMW, 0, (LPARAM)&item) < 0
            ? E_FAIL : S_OK;
}

void ListView_Reset(HWND hwnd)
{
  ListView_DeleteAllItems(hwnd);
}

/* Edit */

HRESULT Edit_PutItem(HWND hwnd, LPVOID value, UInt32 flags, UInt32 param)
{
  HRESULT hr;

  SendMessage(hwnd, EM_SETSEL, (WPARAM)-1, (LPARAM)-1);

  if(IsWindowUnicode(hwnd))
  {
    hr = SendMessage(hwnd, EM_REPLACESEL, 0, (LPARAM)(LPCOLESTR)value)
              ? S_OK : E_FAIL;
    if(SUCCEEDED(hr))
    {
      SendMessage(hwnd, EM_SETSEL, (WPARAM)-1, (LPARAM)-1);
      hr = SendMessage(hwnd, EM_REPLACESEL, 0, (LPARAM)L"\r\n") 
                ? S_OK : E_FAIL;
    }
  }
  else
  {
    . . . // Do conversion to ANSI and add
  }


  return hr;
}

void Edit_Reset(HWND hwnd)
{
  SetWindowText(hwnd, "");
}

Data Transfer, COM Enums, and Windows Controls

Listing 6 Use of ListExchange_PutItems() in test program


static HWND MOCtlFns_Test_GetListWindow(HWND hwnd)
{
  HWND  hwndTab   = ::GetDlgItem(hwnd, IDC_TAB);
  int   iCurSel   = TabCtrl_GetCurSel(hwndTab);
  int   idList    = Tab_GetItemData(hwndTab, iCurSel);
  HWND  hwndList  = ::GetDlgItem(hwnd, idList);

  _ASSERTE(idList != 0);
  _ASSERTE(hwndList != 0);

  return hwndList;
}

static void MOCtlFns_Test_OnClear(HWND hwnd)
{
  HWND  hwndList  = MOCtlFns_Test_GetListWindow(hwnd);

  ListExchange_PutItems(hwndList, NULL, SYLXF_RESETCONTENT);
}

static void MOCtlFns_Test_OnPut(HWND hwnd)
{
  LPUNKNOWN penItems;
  HRESULT   hr        = MOCtlFns_Test_GetEnumerator(hwnd,
                              IID_IUnknown,
                              reinterpret_cast<void**>(&penItems));
  HWND      hwndList  = MOCtlFns_Test_GetListWindow(hwnd);

  if(SUCCEEDED(hr))
  {
    UInt32 flags = IsDlgButtonChecked(hwnd, IDC_CLEAR_EXISTING)
                                            ? SYLXF_RESETCONTENT
                                            : 0;

    /* Simply pass the window, enumerator (as IUnknown) and
     * flags to the function. 
     */
    ListExchange_PutItems(hwndList, penItems, flags);

    penItems->Release();
  }
}

Data Transfer, COM Enums, and Windows Controls

Listing 7 Permutations of enum_simple_sequence<> and functionals in test program.


using comstl::enum_simple_sequence;
using comstl::input_cloning_policy;
using comstl::LPOLESTR_policy;
using comstl::BSTR_policy;
using winstl::listbox_back_inserter;
using winstl::combobox_back_inserter;
using winstl::listview_back_inserter;

static void MOCtlFns_Test_Put(HWND hwnd)
{
  LPUNKNOWN punk;
  HRESULT   hr        = MOCtlFns_Test_GetEnumerator(hwnd,
                              IID_IUnknown,
                              reinterpret_cast(&punk));
  HWND      hwndList  = MOCtlFns_Test_GetListWindow(hwnd);

  if(SUCCEEDED(hr))
  {
    HWND  hwndTab   = ::GetDlgItem(hwnd, IDC_TAB);
    int   iCurSel   = TabCtrl_GetCurSel(hwndTab);
    int   idList    = Tab_GetItemData(hwndTab, iCurSel);

    if(::IsDlgButtonChecked(hwnd, IDC_STRING))
    {
      LPENUMSTRING  penum;

      hr = punk->QueryInterface(IID_IEnumString, 
                        reinterpret_cast(&penum));

      if(SUCCEEDED(hr))
      {
typedef enum_simple_sequence
                    < IEnumString
                    , LPOLESTR
                    , LPOLESTR_policy
                    , LPCOLESTR
                    , input_cloning_policy
                    , 5
                    >   enum_sequence_t;

        // Declare the sequence (and eat the reference)
        enum_sequence_t   values(penum, false);

        switch(idList)
        {
          case  IDC_LISTBOX:
            std::for_each(values.begin(), values.end(), 
                            listbox_back_inserter(hwndList));
            ListBox_ResetContent(hwndList);
            break;
          case  IDC_COMBOBOX:
            std::for_each(values.begin(), values.end(), 
                            combobox_back_inserter(hwndList));
            ComboBox_ResetContent(hwndList);
            break;
          case  IDC_LISTVIEW:
            std::for_each(values.begin(), values.end(), 
                            listview_back_inserter(hwndList));
            ListView_DeleteAllItems(hwndList);
            break;
        }
      }
    }
    else if(::IsDlgButtonChecked(hwnd, IDC_BSTR))
    {
      LPENUMBSTR penum;

      hr = punk->QueryInterface(IID_IEnumBSTR,
                        reinterpret_cast(&penum));

      if(SUCCEEDED(hr))
      {
typedef enum_simple_sequence
                    < IEnumBSTR
                    , BSTR
                    , BSTR_policy
                    , BSTR const
                    , input_cloning_policy
                    , 5
                    >   enum_sequence_t;

        // Declare the sequence (and eat the reference)
        enum_sequence_t   values(penum, false);

        switch(idList)
        {
          case  IDC_LISTBOX:
            std::for_each(values.begin(), values.end(), 
                            listbox_back_inserter(hwndList));
            ListBox_ResetContent(hwndList);
            break;
          case  IDC_COMBOBOX:
            std::for_each(values.begin(), values.end(), 
                            combobox_back_inserter(hwndList));
            ComboBox_ResetContent(hwndList);
            break;
          case  IDC_LISTVIEW:
            std::for_each(values.begin(), values.end(), 
                            listview_back_inserter(hwndList));
            ListView_DeleteAllItems(hwndList);
            break;
        }
      }
    }
    else
    {
      . . . // etc. etc.
    }

    punk->Release();
  }
}

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.