Idea to Internet: Exposing an STL Vector via COM Interface Using ATL

Environment: VC6 SP2, NT4 SP4

Introduction

This tutorial is targeted to those beginning to use ATL that have some experience using ATL and STL (Standard Template Library) and to developers interested in exposing STL-based containers via COM using ATL 3.0.

One of the problems with samples is that they present a complete solution. Working with it, and spending some time stepping through the code can help you figure out what's going on in the sample, but it does not help you understand why the sample was implemented the way it is presented. In this tutorial I will attempt to address this issue.

The component in this sample collects and exposes the names of the services on the local system. The component stores the service names using Unicode strings (the preferred format for string on Windows NT) and exposes these strings to clients as BSTRs (the data type used by VB, VJ, and scripting-based clients). The complete project is included with this tutorial; however, the key feature is I will walk you through creating the component from start to end and give you other details along the way.

The idea behind this format is that it attempts to provide you with a starting point for using and working with ATL, STL, and the Win32 API. We start with (almost) nothing and finish with a complete component that can be used in a server process (like Microsoft's IIS) or in a user's browser.

The sample component has these features:

  • Enumerates local Windows NT Service names based on their present state
  • Stores the service names in a STL vector (similar to an array) of Unicode strings (wstring objects)
  • Exposes the contents of the vector using the ATL ICollectionOnSTLImpl class
  • Converts the contents of the vector, on demand, to the format expected by the client
  • Adds support for error handling using the ATL ISupportErrorInfoImpl class
  • Demonstrates the use of the component using ASP, in IE 4m and using VB
  • Demonstrates how to add custom properties to a component
  • Demonstrates how to package a component and sign it for distribution using the Internet
  • Demonstrates useful Dynamic HTML techniques

All of these features are implemented using a surprisingly small amount of code. Most of the typing is done while defining the interface and actually enumerating the services on the local system. The rest of the implementation is provided by ATL.

Demonstration

Before we start, here's a demonstration of using the component in VB, ASP, and in the browser:

Using VB:

Using ASP:

 
(other code) 
. . .for each serviceName in serviceEnumerator 
          response.Write(serviceName)
     next . . . 

Using IE 4.x:

. . .for iter=1 to services.Count 
        HTMLstr=HTMLstr+"<li>"+services.Item(iter)+"</li>" 
next . . . 

What is a Collection?

The component in this sample represents the names of local services as a collection of Unicode strings.

A "collection" of strings, objects, etc familiar to VB developers; its behavior is similar to of a self-managing array. A collection implements these default properties:

  • Item - provides access to an element of the collection
  • Count - represents the number of items in the collection

A collection also implements another property, called _NewEnum that initializes the collection. A typical use of a collection in VB is based on using the FOR EACH &hellip; NEXT construct. The problem with collections implemented using VB is these components need the support of the VB runtime and support limited threading capabilities. This limits their use in application servers like Microsoft's Internet Information Server because the threading incompatibilities introduce scalability issues.

Walk-Through

Creating the Project

We will be re-creating (most of) the sample that is available for download at the end of this article. I will explain or expand the more interesting points as we go along. Where the code is too long to present here, I will refer you to the source files in the project. The source files include comments to describe features and implementation details. Lets get started!

Create a new project; there is lots of information available on how to create a skeleton ATL project, so I won't go into the all of the details here. Use these basic settings for the project:

  1. Create a project using the ATL COM AppWizard and call it "serviceEnum"
  2. Accept the default settings on Step 1 of 1 of the wizard (we are creating a DLL without support for MFC, MTS, or merging of proxy/stub code)
  3. Add a "Simple Object" to the project using the "New ATL Object" wizard and call it "ServiceList"
  4. Change the "Threading Model", under the "Attributes" tab, to "Both"

** Edit the STDAFX.H and add the line, #include <windows.h> after the line that includes atlcom.h

We need support for some Win32 functions to support enumeration of the service on the local system; this file adds the support we need.

Interface Definition

We are going to define the interface for the component before ding anything else. Focusing on the interface before actually doing any coding will make implementation, distribution, and upgrading the component easier later on.

AcquireServices Method

Add a new method to the component called "AcquireServices" (right-click on the IServiceList interface in class view, and select "Add Method" from the pop-up menu). This method doesn't take any parameters and returns an HRESULT so you don't need to add anything else except for the method name.

EnumServiceState Property

The use of the component in VB showed an interesting view of a property called EnumServiceState with a pop-up of available options; here's the picture again:

Adding support of this feature takes a couple of steps; we need to edit the code in the IDL file first to name the supported options. Open the IDL file by double-clicking the IServiceList interface in class view, or open the file directly in file view (you'll find serviceEnum.idl under the Source Files folder). Just before the two "import" statements at the top of the file, add these lines:


typedef enum seEnumServiceStates {
	seActiveServices=1,
	seInActiveServices=2,
	seAllServices=3
} seEnumServiceStates;

Notice how similar to C/C++ this enum is. The IDL language can be thought of as annotated C++; the IDL compiler, called MIDL uses the information in this file to create a number of files that allow us to use this definition while creating the component and for using it in C++ and other languages.

We're halfway there (for this feature anyway). We now need to expose the enum using a property called EnumServiceState. In class view, expand the CServiceList class then expand the IServiceList interface. Add a new property by right-clicking on the IServiceList interface and selecting "Add Property". Fill in the form like this:

That's it!

Interface Declaration for Item, Count, and _NewEnum Prpoerties

Now we'll add support for the standard collection properties: Item, Count, and _NewEnum. As above in class view, expand the CServiceList class then expand the IServiceList interface. Add the properties as shown (note that all of the properties are read only, so make sure that only the "Get Function" option is checked):

Count:

Item:

Note that this property takes a parameter of type long named itemIndex and is marked as [in]. IDL requires the developer to define what the "direction" of a parameter is so that MIDL can generate the proxy/stub to marshal the parameters across process, apartment, or system boundaries.

To put it briefly, among the services that COM provides is a location and process independent means of moving data from object to object. A proxy/stub pair abstract the physical and/or logical location of an object transparently to allow this. The proxy/stub pair does this by marshaling, or packaging, method property parameters so that they can potentially be transmitted over the wire. The proxy/stub pair is based on relatively boilerplate code, therefore, the MIDL compiler can generate the pair for you based on the information you supply in the IDL file. There are four possible types of property or method parameters:

  1. [in] - the client supplies the memory and value for the parameter
  2. [out] - the server supplies the memory and value for the parameter; the client is responsible for releasing the memory
  3. [out, retval] - similar to [out] except that VB or scripting based clients don't need to supply a named value for the parameter; they use the value as a return value (eg: retVal=Obj.MyMethod() ).
  4. [in] [out] - similar to #3, except that the client will not use the parameter as a return value

_NewEnum:

This is a special property that underlying VB and scripting clients' runtime systems use to initialize the collection to use it in the FOR EACH . . . NEXT construct. Add the property as shown:

One last change before we get to the implementation; we need to edit the DSIP IDs of two of the three properties we added above. I'll explain as we go along. Open the IDL file as before (by double-clicking the IServiceList interface in class view, or open the file directly in file view (you'll find serviceEnum.idl under the Source Files folder)).

1. Find this line:


[propget, id(4), helpstring("property Item")] 
   HRESULT Item([in] long itemIndex, [out, retval] BSTR *pVal);

Change the value in brackets after the "propget" attribute ((4) in this case) to DISPID_VALUE.

2. Find this line:


[propget, id(5), helpstring("property _NewEnum")] 
HRESULT _NewEnum([out, retval] IUnknown** *pVal);

Change the value in brackets after the "propget" attribute ((5) in this case) to DISPID_NEWENUM. Also change the parameter such that it looks like this:


[out, retval] IUnknown** ppunkEnum

So, the whole line looks like this:


[propget, id(DISPID_NEWENUM), helpstring("property _NewEnum")]
HRESULT _NewEnum([out, retval] IUnknown** ppunkEnum);

The DSIP IDs are ID numbers used by the VB and scripting client's runtime systems to determine what methods and properties your object supports. This is a vast topic, but to put it briefly, when your object is instantiated by a client the client runtime system queries your component for the names and method/property parameters and types it supports. The DISP ID represents an ID number used by the Dispatch interface (the interface used by VB, VJ, and scripting clients) of your component. The DISPID_VALUE and DISPID_NEWENUM IDs tell the runtime system that the property marked with this value respectively represent the Value and initialization properties of this component.

Implementation

The hard part is over now! Having spent a lot of time defining our interface now allows us to write a surprisingly small amount of code to implement the component. The majority of the code is in the AcquireServices method. Before we get to that, lets get some "free" support for extended error information.

Supporting Extended Error Information

Almost all COM calls return a status of type HRESULT. This is a 32 bit value that represents Success/Failure, Facility code (what sub-system generated the HRESULT), and the actual status. While this support is good, some clients need more descriptive information.

The ISupportErrorInfo interface is used to tell clients that an object supports rich error information (using the Err object in VB for example). In ATL, the ISupportErrorInfoImpl class provides the default implementation of this interface. We can take advantage of this for free, courtesy of ATL, simply by deriving our implementation from the ISupportErrorInfoImpl class.

Open the ServiceLisst.h file and find these lines:


//////////////////////////////////////////////////////////
// CServiceList
class ATL_NO_VTABLE CServiceList : 
  public CComObjectRootEx<CComMultiThreadModel>,
  public CComCoClass<CServiceList, 
         &CLSID_ServiceList>,
  public IDispatchImpl<IServiceList, 
         &IID_IServiceList, 
         &LIBID_SERVICEENUMLib>

Add this line to the end (don't forget to add a comma at the end of the IDispatchImpl line:


public ISupportErrorInfoImpl<&IID_IServiceList >

The IID_IServiceList parameter tells ISupportErrorInfoImpl which interface it is being implemented for. We now support extended error information in our component. Pretty easy - all we need to do is raise errors where appropriate and clients can easily pick up our extended information (This is demonstrated in the source code for the project. There is a large section of comments in the ServiceList.cpp file; follow the directions in this file to add support for catching "out of bound" exceptions).

Supporting Item, Count, and _NewEnum Properties

 

We are going to get some more free implementation code for the Item, Count, and _NewEnum properties. We need to change a few things before we can get this because we the types we are using to store the collection's contents in are different from the types that our clients will use. In our case our STL vector is storing wstring objects and our clients are going to expect BSTR objects. I did this because most C++ developers have better things to do with their time than writing lots of conversion code to communicate with object clients. The Standard Template Library includes a basic_string class that provides functionality such as memory management, concatenation, searching, and sub-stringing. The basic_string calss comes in a couple of different flavors (through inheritance): a string object that represents characters in the ANSI character set and wstring that represents its contents as in Unicode. You can create your "flavor" of a basic_string object (for example a basic_string that supports the TCHAR format) by supplying one or more arguments to the declaration of the class. Discussion of this is rather involved however there are lots of samples and books that provide the details. Being a systems type of developer, I went with wstring objects; a key benefit of this choice is that the string won't have to be converted to Unicode when calling Win32 APIs since it is already in the preferred format.

With that said, lets get going and get to the point of this tutorial. I'm going to pick up the pace here so you'll need to pay close attention.

Open the ServiceList.h file and locate the line that reads #include "resource.h". Add this block of code:



#include <vector>
using namespace std;

// adapted from ATL Internals book (p 344)
struct _CopyVariantFromStlWString {
  static HRESULT copy(VARIANT* p1, wstring* p2) {
    USES_CONVERSION;
    p1->vt = VT_BSTR;
    p1->bstrVal = SysAllocString(W2OLE(
          const_cast<unsigned short*>(p2->c_str() ) ));
    return (p1->bstrVal ? S_OK : E_OUTOFMEMORY);
  }
  
  static void init(VARIANT* p)    { VariantInit(p); }
  static void destroy(VARIANT* p) { VariantClear(p); }
};

// adapted from ATL Internals book (p 345)
struct _CopyBstrFromStlWString {
  static HRESULT copy(BSTR* p1, wstring* p2) {
  CComBSTR bstr;
  bstr=p2->c_str();
  //*p1 = SysAllocString(W2OLE(
        const_cast<unsigned short*>(p2->c_str() ) ));
  *p1=bstr.Detach();
   return (p1 ? S_OK : E_OUTOFMEMORY);
  }
  
  static void init(BSTR* p)    { }
  static void destroy(BSTR* p) { SysFreeString(*p); }
};

 
// Next 3 lines based on information from ATL 
// Internals book [pp 341-346]
// provides implementation of Dispatch methods
typedef IDispatchImpl<IServiceList, 
        &IID_IServiceList> 
        IServiceListDualImpl;

 
// provides implementation of get_NewEnum
typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,
        _CopyVariantFromStlWString, vector<wstring> >
        CComEnumVariantOnVectorOfWString;

// provides implementation Item and Count properties
typedef ICollectionOnSTLImpl<IServiceListDualImpl,
                             vector< wstring >,
                             BSTR,
                             _CopyBstrFromStlWString,
                             CComEnumVariantOnVectorOfWString>
        IServiceListCollImpl;

 

This looks complicated, but it's essentially pretty straightforward. The two structs handle the conversion from our internal representation of the collection to what our clients are expecting.

The IDispatchImpl typedef is simply a convenience as it reduces typing and increases clarity later.

The typedef for CComEnumOnSTL is there to create an enumerator object that enumerates objects of type vector<wstring> and converts the contents vector<wstring> to a VARIANT on demand (only when accessed). This object implements the Next, Skip, Reset, Clone, etc methods. We are actually only interested in the implementation of the get__NewEnum method of this object. We use the VARIANT data type because this is what clients expect to use when using the enumeration interface.

The typedef for ICollectionOnSTLImpl implements the Count, Item properties for us. As with the last typedef, this one also handles conversion on demand, this time from wstring to BSTR.

Go to the declaration of the CServiceList class, it presently looks like this:


////////////////////////////////////////////////////////////////
// CServiceList
class ATL_NO_VTABLE CServiceList : 
  public CComObjectRootEx<CComMultiThreadModel>,
  public CComCoClass<CServiceList, 
         &CLSID_ServiceList>,
  public IDispatchImpl<IServiceList, 
         &IID_IServiceList, 
         &LIBID_SERVICEENUMLib>,
public ISupportErrorInfoImpl<&IID_IServiceList >

Change it to look like this:


///////////////////////////////////////////////////////////////
// CServiceList
class ATL_NO_VTABLE CServiceList : 
  public CComObjectRootEx<CComMultiThreadModel>,
  public CComCoClass<CServiceList, 
         &CLSID_ServiceList>,
  public ISupportErrorInfoImpl<&IID_IServiceList>,
  public IServiceListCollImpl

This derives our class from the implementation classes we just defined.

Now that we've got all of this free implementation code, we need to REMOVE a bunch of declarations and definitions that were added when we were using the IDE to add properties to our object.

Open ServiceList.cpp, and remove the definitions of get_Count, get_Item, and get__NewEnum. Now open the ServiceList.h file and remove the declarations for the same three methods. Not so bad?!

Turning our focus to actually getting the running services from our computer, add two private member variables to the CServiceList class. Both are DWORDs, their names are m_dwEnumServiceType (this maintains the resultant value mapping what the user requests to their Win32 equivalents) and m_ulServiceTypeRequested (used to store what type of services the user requested).

The implementations of the get and put methods for the EnumServiceState property need to be updated. Here's the code:


STDMETHODIMP CServiceList::get_EnumServiceState(
                          seEnumServiceStates *pVal)
{
  Lock();
  *pVal=static_cast<seEnumServiceStates>(
                          m_ulServiceTypeRequested);
  Unlock();
  return S_OK;
}

STDMETHODIMP CServiceList::put_EnumServiceState(
                          seEnumServiceStates newVal)
{
  Lock();
  m_ulServiceTypeRequested=newVal;
  Unlock();
  return S_OK;
}

Notice the calls to Lock and Unlock. These methods are provided by CcomObjectRootEx and provide basic thread synchronization (basic in the sense that we are using them here. Support for the various threading models in very comprehensive in ATL and in the Win32 API). The Threading Model used in our project determines if actual locking and unlocking (using a critical section) takes place. You can change the behavior of these methods by un/defining pre-processor symbols that are approprate for your server's threading needs. Our project is compatible with the client's threading model, therefore, I am calling the Lock and Unlock methods to ensure that the object's state remains consistent.

The final bit of code deals with actually enumerating the services on the system. The code for the AcquireServices method is about 90 lines long (including comments!) and is relatively straightforward as well. It essentially opens the service control manager, allocates a certain amount of memory, and goes through a buffer filled by the EnumServicesStatus API. For each service, its name is added to the m_coll member variable.

Where did m_coll come from?! This is provided by the Impl classes we defined earlier. It is a STL container of the type we specified in the typedefs; in this case m_coll represents a vector of wstrings (as specified in the arguments to the templates).

Before we compile, make sure exception handling is turned on for all configurations and that you are using a UNICODE build. Go ahead, compile! If it doesn't work, you have two choices: go through all of this again, or (the easier route) use the sample project.

Using the component in . . .

Now for the interesting stuff! Here's the output in VB (I've included the application in the zip file):

ASP:

IE 4.x:

 

Internet Notes and other points of interest

File Size

As a result of indirectly using the C Runtime library and using exception handling, this component weighs in at about 72Kb. The component could have been made smaller by, for example, not using the STL as the underlying container, but we would have had to write a lot of code to support that and done a lot more testing.

Code Download

The html pages that are included with the sample project use the <object> tag to facilitate downloading of the CAB file. Initially, creating a CAB file can look pretty hard, but it's easy once you've got the tools and some guidelines.

I've included the tools I've used below for your convenience only. Note that these tools are distributed by Microsoft; if you have any problems using the ones provided in the project, visit Microsoft's MSDN site to download them.

The first thing you need is a digital signature for code signing. If you don't have once (note: they cost 400 UDS per year), you can use a test certificate created by some tools issued by Microsoft.

Next, you'll need a CAB file; this file is created using the CabArc utility. You need to create an INF file for the CabArc utility and code download to work. There's an INF and CAB file included in the sample project. The project has a dependency on ATL.DLL (that I've also included for convenience only). I've included the version of the file as provided with VC++, SP2. You can update this file or save your present version before testing with the one that I've provided here.

Put the two DLLs and the INF file into the same directory and type the follwing command to create the CAB file:


cabarc -s 6144 N serviceEnum.cab atl.dll 
         serviceEnum.dll serviceEnumCab.inf

This command creates the CAB file and reserved 6144 bytes for the digital signature (this is the recommended size to reserve).

You'll then have to sign the CAB file as shown (the sample CAB file is already signed):

1. Create a test certificate using these steps:


MakeCert -sv myNew.pvk -ss myNewStore myNew.cer  
(enter a password when prompted. Remember or record this password as you will need it when signing the CAB file)

Cert2spc myNew.cer myNew.spc

Then sign the CAB file:


Signcode -spc myNew.spc -v myNew.pvk  serviceEnum.cab 
(enter the password used when creating the test certificate when prompted)

You'll be prompted once the code download starts. Here's the prompt:

Conclusion

This tutorial developed a complete ATL control that exposes the contents of an STL vector object holding wstring objects as a collection of strings exposed as BSTRs. This tutorial also included a VB client, a Web client, and demonstrated how to create and distribute the sample for use on the Internet. The sample source code also included a number of useful techniques that can be used using DHTML.

Note: the sample developed in the tutorial is a sample and is not intended for production code or use. Test the component thoroughly and modify as necessary to suit your needs. Please contact me regarding bugs or comments at eahmed@bigfoot.com.

Downloads

Includes sample component source and binaries (x86), VB Project, Web Client source (HTML, ASP files), CAB file, and tools for CAB creation and code signing.

Download Sample - 280 Kb

History



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

Most Popular Programming Stories

More for Developers

RSS Feeds