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

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

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

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read