Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

IDispatch & Automation for COM Components


January 2002/IDispatch & Automation for COM Components

Using Visual C to create COM components for VB (Visual Basic) or VBScript development is a common task. Still, many C developers don’t understand how VB programmers code efficiently when using standard Microsoft components. For example, VB developers often use For Each instructions or optional parameter syntaxes that some C developers don’t provide with their components. In this article, I’ll describe some common techniques that make it easier to use components for non-C developers.

For the purposes of example, I’ll create a component that enables 16-color bitmap management by setting pixel colors in the bitmap, writing the bitmap into a stream (for example, as in an ASP Response object), storing the picture in a BMP file, and getting a collection of all the pixels in the file. The complete source code that implements the sample classes discussed in this article is available online.

Enum Types

The basic object I’ll use is the CPixel class (see Listing 1), which defines a pixel of some color in a bitmap at some coordinates using three properties: Row, Column, and Color. The Color property for the pixel could be one value between 0 and 15 for each of the 16 colors (the standard 4-bit Windows palette). Because it is difficult to remember the meaning of each color index, I define some constants for each color. The best way to do this is to create an enum type in your IDL file instead of creating it in your header file. Doing this makes the constants to be stored in the component’s type library visible in the VB Object Browser and IDE. The enumerator is also available under C in the header file generated by MIDL. Furthermore, if you define the Color property as returning a value declared with this enum type, the VB IDE automatically displays the list of correct values for the property (see Figure 1).

Enums are not compatible with scripting environments such as VBScript or Jscript — only environments that can read type libraries (VBA and VB, for example). You can, however, use enum typed properties in scripts by setting the corresponding integer values to the constant name.

Default Properties

Color is the most important property of the Pixel class. It therefore may be useful to declare it as a default property, letting VB developers omit typing the property name. You only have to change the DISPID of the property in your IDL file to DISPID_VALUE in order to make it become a default property.

[propput, id(DISPID_VALUE), helpstring("Pixel color")]
HRESULT Color([in] baseColors newVal);

In fact, DISPID values are usually used by scripting engines to invoke methods on objects based on the result of search-by-method names. They usually get them using the IDispatch interface, which is implemented by the ATL’s IDispatchImpl template.

Now Color is the Pixel class’s default property, myPixel.Color = Red and myPixel = Red are both valid and do the same thing.

Error Management

Once the Pixel object supports the Color property “in a VB way,” you should ensure that the new color passed in parameter is valid (that is, part of the 16 base colors).

Reporting errors in C is usually made using exceptions or special return values. COM uses HRESULT as a return value allowing component developers to return some status information on the completion of an invoked method. However, returning rich error information in a 32-bit value is difficult. This is why COM can associate an Error object supporting the IErrorInfo interface to each method call. This interface fully supports marshalling, and an error generated on a remote server can therefore be returned on the client. IErrorInfo presents the following five methods:

  • GetDescription, which returns a BSTR describing the error.
  • GetGUID, which returns the interface GUID defining the error.
  • GetHelpContext and GetHelpFile, which return a help file context where the error is explained in detail.
  • GetSource, which returns the name of the component that generated the error.

ATL provides a straightforward way to create objects that implement IErrorInfo using the strongly overloaded AtlReportError function. You can then associate a string to your error and optionally specify a help file context.

return AtlReportError(CLSID_Pixel, IDS_BAD_COLOR,
    IID_IPixel, 0, _Module.GetModuleInstance());

Once an ErrorInfo object is associated with the context, you then only need to return a DISP_E_EXCEPTION (returned by default by AtlReportError) to generate an exception in client scripting code. The client can then catch the exception and get information on the error by invoking the Err global object (see Example 1).

You can either set the error description string using a string pointer or a string table resource identifier. However, resource identifiers should lie between 0x200 and 0xFFFF, inclusively.

Once the pixel object is fully coded, you create a new class named Bitmap that manages the picture.

Boolean Type

The windows.h header file defines a type named BOOL that is in fact an integer value usually set to TRUE (1) or FALSE (0). VB or VBScript can also manage Booleans, but they do not allow them to be set to any value other than True or False. In VB, True equals -1 (not 1, like BOOL).

The VB Boolean type is encoded in C as a VARIANT_BOOL, and it can take the VARIANT_TRUE (-1) or VARIANT_FALSE (0) value. Defining some properties or parameters in an IDL file as VARIANT_BOOL instead of a simple BOOL results in a change on the VB/VBA IDE while coding. It displays the same list box as it does for the enum types and therefore helps prevent you from using values other than -1 and 0.

The CBitmap class has a property of this type named AutoSize set to True by default and specifying that if a pixel is set outside of the bitmap, the picture should be automatically resized.

VARIANT Arrays

The VARIANT automation type can encapsulate almost any kind of data including arrays. Variant arrays are just like C arrays that could be set to a new dimension (redim). The vt member of such VARIANTs is equal to the type of the value in the array added to VT_ARRAY. Be advised that variant arrays are always passed by reference in parameters and that you should use arrays of VT_VARIANT (for instance, vt = VT_VARIANT | VT_ARRAY) type to work with script environments.

The bitmap class in Example 2 has a method named SetPoints that lets you define an array of pixel objects in the bitmap. I simply get a pointer to the SAFEARRAY from the referenced VARIANT and use the SafeArrayGetLBound and SafeArrayGetUBound functions to get the lower and upper bound of the array. A lower-bound value is required because of the fact that VB allows arrays not to start at index 0.

Once the array size is known, you need to call SafeArrayGetElement to copy the VARIANT structure of each item of the array and analyze it.

Collections

Collections are container objects that can enumerate child objects via the IEnumVARIANT interface. Just like for default properties, the way to present a collection is to use a special DISPID named DISPID_NEWENUM for a get property named _NewEnum. This property returns an IEnumVARIANT interface that enumerates the child items.

The bitmap object has a Pixels property returning an IPixels interface implementing this _NewEnum property. _NewEnum sends back to the client the pixels collection for each pixel of the current bitmap.

Just like for IErrorInfo, ATL provides a feature that lets you avoid implementing the IEnumVARIANT interface (see Example 3). This can be accomplished by using the CComEnum class template.

Each time a collection is requested by calling get__NewEnum, the function creates a CComEnum templatized for IEnumVARIANT. It then initializes the collection by calling Init and specifying a pointer to the beginning and another one to the end of the VT_DISPATCH array pointing to each of my created pixels in the IDispatch interface. The AtlFlagCopy tells CComEnum to make its own local copy of the VARIANT. (You could also have specified AtlFlagTakeOwnership.)

Collection objects usually implement properties like Count to return the number of child items in the collection. ATL provides some other automatic mechanisms to code this property not described here.

Now that you have a pixels collection on the pixels property of the bitmap, VB developers can use the For Each syntax to query each pixel of the bitmap (see Example 4).

IStream Support

Supporting Stream parameters can be useful for components used in ASP where you should write to the Response object (note that only the IIS 5.0 Response object supports IStream). A stream is a way to read and/or write data at some place whether it goes on a hard disk, a network connection, or anything else.

The IIS Response object is a stream object that you can write text or binary data to. Therefore, you can implement a function that lets you write a bitmap file into the ASP Response object to create dynamic bitmaps (for example, to create real-time analysis graphics).

Example 5 shows calling Write on an IStream interface to generate a dynamic page using ASP. The form in test.asp posts a list of pixels to colorize to GenBmp.asp that uses the Bitmap component and its Save method to generate the picture (see Figure 2).

Optional Parameters

MIDL supports a special attribute named [optional] that only can be applied on VARIANT-typed parameters. Setting this attribute results in a parameter VB developers can omit. For example, the SaveToFile method of CBitmap takes two optional parameters: the first one setting the filename where to write the bitmap and the second indicating a parent window handle if no filename is specified showing the Save As dialog box.

[id(6), helpstring(
 "Save bitmap to a file on a partition")]
HRESULT SaveToFile([in, optional] 
                      VARIANT vbstrFileName,
                   [in, optional] 
                      VARIANT 
                      vhWndParentWindow);

If no parameter is specified under VB, the VARIANT can then take two different vt values:

  • VT_EMPTY, which means the parameter has been bypassed (for instance, some later parameters have been specified after this one).
  • VT_ERROR with an scode equal to DISP_E_PARAMNOTFOUND: the VB code did not specify the parameter at all (i.e., no further parameters are set).

Optional parameters define default behaviors for omitted parameters and often simplify VB code readability.

if (vbstrFileName.vt == VT_EMPTY ||
   (vbstrFileName.vt == VT_ERROR && 
                 vbstrFileName.scode ==
                 DISP_E_PARAMNOTFOUND) )

Another simpler but more restricted method can be used using the defaultvalue attribute in your IDL file. The value specified in the parameter to this attribute represents the value that will be passed to the function if the parameter has been bypassed. This may be inconvenient at times, but one advantage of this technique is that the defaultvalue will be displayed by the IDE of the component consumer. For example, to declare a method that adds one and two by default, the IDL should specify the following:

[id(7), helpstring(
    "Adds two numbers (1+2) by default")]
HRESULT SaveToFile(
 [in, defaultvalue(1)] lone lFirstVal,
 [in, defaultvalue(2)] long lSecondVal,
 [out, retval] long* pRetVal);

Conclusion

The C++-based Bitmap component that creates the picture uses some IDispatch features to make development for VB or ASP developers a lot easier. You can view a running demonstration of the GenBmp.asp image generator online at www.arnaudaubert.com, under the Articles section.


Arnaud Aubert is the R&D manager of IS Decisions. He can be reached at [email protected] or http://www.arnaudaubert.com.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.