Share via



March 2017

Volume 32 Number 3

[C++]

Simplify Safe Array Programming in C++ with CComSafeArray

By Giovanni Dicanio | March 2017

It’s common to develop complex software systems building components written in different languages. For example, you might have some high-performance C++ code embedded in a C-interface, dynamic-link library (DLL) or in a COM component. Or, you can write a Windows service in C++ that exposes some COM interfaces. Clients written in C# can interact with that service via COM interop.

Often, you want to exchange some data in the form of arrays between those components. For example, you might have some C++ components that interact with some hardware and produce an array of data, like an array of bytes representing the pixels of an image read from an input device, or an array of floating point numbers representing measurements read from a sensor. Or, you might have a Windows service written in C++ that interacts with other low-level modules and returns arrays of strings that you want to consume in GUI clients written in C#, or in a scripting language. Passing that data across module boundaries isn’t trivial and requires the use of well-designed and well-crafted data structures.

The Windows programming platform offers a convenient ready-to-use data structure that can be used for that purpose: the SAFEARRAY, whose definition can be found on the Windows Dev Center (bit.ly/2fLXY6K). Basically, the SAFEARRAY data structure describes a particular instance of a safe array, specifying attributes such as its number of dimensions and a pointer to the actual safe array’s data. A safe array is usually handled in code via a pointer to its SAFEARRAY descriptor, that is, SAFEARRAY*. There are also C-interface Windows APIs for manipulating safe arrays, such as SafeArrayCreate and SafeArrayDestroy for creation and destruction, and other functions to lock a safe array instance and safely access its data. For further details on the SAFEARRAY C data structure and some of its native C-interface APIs, see the online companion piece to this article, “Introducing the SAFE­ARRAY Data Structure” (msdn.com/magazine/mt778923).

However, for C++ programmers, instead of working at the C-interface level, it’s more convenient to use higher-level C++ classes, such as the Active Template Library (ATL) CComSafeArray.

In this article, I’ll discuss C++ code samples of increasing complexity to create safe arrays storing different kinds of data using ATL helper classes, including CComSafeArray.

Safe Array vs. STL Vector

Standard Template Library (STL) class templates such as std::vector are excellent containers for C++ code inside module boundaries, and I encourage you to use those in such contexts. For example, it’s more efficient to dynamically add content and grow a std::vector than a safe array, and std::vector is nicely integrated with algorithms from the STL and other third-party cross-platform C++ libraries (such as Boost), as well. Moreover, using std::vector lets you develop cross-platform standard C++ code; instead, safe arrays are Windows-platform specific.

On the other hand, when safe arrays win, it’s at the module boundaries. In fact, std::vectors can’t safely cross module boundaries and can’t be consumed by clients written in languages different than C++. Instead, these are the very contexts in which it makes sense to use safe arrays. A good coding pattern is one in which you perform the core processing using standard C++ and STL containers such as std::vector. Then, when you need to transfer such array data across module boundaries, you can project the content of std::vector to a safe array that’s an excellent candidate for crossing module boundaries and for being consumed by clients written in languages different than C++.

The ATL CComSafeArray Wrapper

The safe array “native” programming interface uses Win32 C-interface APIs, as described in the online companion piece to this article. While it’s possible to use those C functions in C++ code, they tend to lead to cumbersome and bug-prone code; for example, you have to pay attention to properly match safe array deallocations with every safe array allocation to properly match locks with unlocks, and so on.

Thankfully, with C++ it’s possible to simplify that C-style code quite a bit, using convenient coding patterns like RAII and destructors. For example, you can write a class to wrap raw SAFEARRAY descriptors, and have the constructor call SafeArrayLock, and the destructor call the matching SafeArrayUnlock function. In this way, the safe array is automatically unlocked once the wrapper object goes out of scope. It makes sense to shape this class as a template, as well, to enforce type safety on the safe array. In fact, while in C the safe array’s genericity is expressed using a void pointer for the SAFEARRAY::pvData field, in C++ you can do better type checking at compile time using templates.

Fortunately, you don’t have to write such a class template from scratch. In fact, ATL already provides a convenient C++ class template to simplify safe array programming: CComSafeArray, declared in the <atlsafe.h> header. In the ATL CComSafeArray<T>, the T template parameter represents the type of data stored in the safe array. For example, for a safe array of BYTEs you would use CComSafeArray<BYTE>; a safe array of floats is wrapped using CComSafeArray<float> and so on. Note that the internal-wrapped safe array is still a polymorphic void-pointer-based C-style array. However, the C++ wrapping layer built by CComSafeArray offers a higher and safer level of abstraction that includes both safe array automatic lifetime management and better type safety than C void*.

CComSafeArray<T> has just one data member, m_psa, which is a pointer to a safe array descriptor (SAFEARRAY*) wrapped by the CComSafeArray object.

Constructing CComSafeArray Instances

The CComSafeArray default constructor creates a wrapper object that contains just a null pointer to a safe array descriptor; basically, it wraps nothing.

In addition, there are several constructor overloads that can come in handy. For example, you can pass an element count to create a safe array made by a given number of elements:

// Create a SAFEARRAY containing 1KB of data
CComSafeArray<BYTE> data(1024);

It’s also possible to specify a lower bound different than zero, which is the default:

// Create a SAFEARRAY containing 1KB of data
// with index starting from 1 instead of 0
CComSafeArray<BYTE> data(1024, 1);

However, when you write code to operate on safe arrays that are meant to be processed by C++ or C#/.NET clients, it’s better to stick with the usual convention of having a lower bound of zero (instead of one).

Note that CComSafeArray constructors automatically call SafeArrayLock for you; so, the wrapped safe array is already locked and ready for read-and-write operations in user code.

If you have a pointer to an existing safe array descriptor, you can pass it to another CComSafeArray constructor overload:

// psa is a pointer to an existing safe array descriptor
// (SAFEARRAY* psa)
CComSafeArray<BYTE> sa(psa);

In this case, CComSafeArray attempts to create a deep copy of the original safe array of BYTEs pointed by “psa.”

Automatic Resource Cleanup with CComSafeArray

When a CComSafeArray object goes out of scope, its destructor will automatically clean up the memory and resources allocated by the wrapped safe array. This is very convenient and simplifies the C++ code a lot. In this way, C++ programmers can focus on the core of their code instead of the details of safe array memory clean up, avoiding nasty memory leak bugs.

Spelunking inside the ATL CComSafeArray code, you’ll see that the CComSafeArray destructor calls SafeArrayUnlock first, and then SafeArrayDestroy, so there’s no need for client code to explicitly unlock and destroy the safe array; actually, that would be a double-destruction bug.

Convenient Methods for Common SafeArray Operations

In addition to automatic safe array lifetime management, the CComSafeArray class template offers some convenient methods to simplify operations on safe arrays. For example, you can simply call the GetCount method to get the element count of the wrapped safe array. To get access to the safe array’s items, you can call the CComSafeArray::GetAt and SetAt methods, simply specifying the item’s index.

For example, to iterate through the elements in an existing safe array, you can use code like this:

// Assume sa is a CComSafeArray instance wrapping an existing safe array.
// Note that this code is generic enough to handle safe arrays
// having lower bounds different than the usual zero.
const LONG lb = sa.GetLowerBound();
const LONG ub = sa.GetUpperBound();
// Note that the upper bound is *included* (<=)
for (LONG i = lb; i <= ub; i++)
{
  // ... use sa.GetAt(i) to access the i-th item
}

In addition, CComSafeArray overloads operator[] to offer even a simpler syntax for accessing safe array’s items. You’ll see some of those methods in action in the next sections of this article.

It’s also possible to append new items to an existing safe array, invoking the CComSafeArray::Add method. Moreover, CCom­SafeArray::Resize, as its name clearly suggests, can be used to resize the wrapped safe array. However, in general, I’d suggest you write C++ code that operates on std::vector, which is faster than safe arrays at resizing and is well integrated with other STL algorithms and other third-party cross-platform C++ libraries, as well. Then, once the operations on the content of std::vector are complete, the data can be copied to a safe array, which can safely cross module boundaries (unlike std::vector), and can be consumed also by clients written in languages different than C++.

Safe array copy operations are possible using the overloaded copy-assignment operator= or invoking methods such as CComSafeArray::CopyFrom.

‘Move Semantics’ in CComSafeArray

The CComSafeArray class doesn’t implement “move semantics” in the strict C++11 sense; in fact, this ATL class predates C++11, and at least up until Visual Studio 2015, C++11 move operations such as move constructor and move assignment operator haven’t been added to it. However, there’s a different kind of move semantics offered by CComSafeArray and it’s based on a couple of methods: Attach and Detach. Basically, if you have a pointer to a safe array descriptor (SAFEARRAY*), you can pass it to the Attach method, and the CComSafeArray will take ownership of that raw safe array. Note that any previously existing safe array data wrapped in the CComSafeArray object is properly cleaned up.

Similarly, you can call the CComSafeArray::Detach method and the CComSafeArray wrapper will release the ownership of the wrapped safe array to the caller, returning a pointer to the previously owned safe array descriptor. The Detach method comes in handy when you create a safe array in C++ code using CComSafeArray, and then you hand that as an output pointer parameter to a caller, for example in a COM interface method or in a C-interface DLL function. In fact, the CComSafeArray C++ class cannot cross COM or C-interface DLL boundaries; only raw SAFEARRAY* descriptor pointers can.

C++ Exceptions Cannot Cross COM and C DLL Boundaries

Some of CComSafeArray methods such as Create, CopyFrom, SetAt, Add, and Resize return HRESULTs to signal success or error conditions, as is customary in COM programming. However, other methods such as some CComSafeArray constructor overloads, or GetAt, or operator[], actually throw exceptions on errors. By default, ATL throws C++ exceptions of type CAtlException, which is a tiny wrapper around an HRESULT.

However, C++ exceptions cannot cross COM method or C-interface DLL function boundaries. For COM methods, it’s mandatory to return HRESULTs to signal errors. This option can be used in C-interface DLLs, as well. So, when writing C++ code that uses the CComSafeArray wrapper (or any other throwing component, as well), it’s important to guard that code using a try/catch block. For example, inside the implementation of a COM method, or inside a DLL boundary function that returns an HRESULT, you can write code like this:

try
{
  // Do something that can potentially throw exceptions as CAtlException ...
}
catch (const CAtlException& e)
{
  // Exception object implicitly converted to HRESULT,
  // and returned as an error code to the caller
  return e;
}
// All right
return S_OK;

Producing a Safe Array of Bytes

After the previous conceptual framework introducing safe arrays and the convenient C++ CComSafeArray ATL wrapper, I’d like to discuss some practical applications of safe array programming, showing CComSafeArray in action. I’ll start with a simple case: producing a safe array of bytes from C++ code. This safe array can be passed as an output parameter in a COM interface method or C-interface DLL function.

A safe array is typically referenced via a pointer to its descriptor, which translates to SAFEARRAY* in C++ code. If you have an output safe array parameter, you need another level of indirection, so a safe array passed as output parameter has the SAFEARRAY** form (double pointer to SAFEARRARY descriptor):

STDMETHODIMP CMyComComponent::DoSomething(
    /* [out] */ SAFEARRAY** ppsa)
{
  // ...
}

Inside the COM method, don’t forget to use a try/catch guard to catch exceptions and translate them to corresponding HRESULTs, as shown in the previous paragraph. (Note that the STDMETHODIMP preprocessor macro implies that the method returns an HRESULT.)

Inside the try block, an instance of the CComSafeArray class template is created, specifying BYTE as the template parameter:

// Create a safe array storing 'count' BYTEs
CComSafeArray<BYTE> sa(count);

Then you can simply access the elements in the safe array using CComSafeArray’s overloaded operator[], for example:

for (LONG i = 0; i < count; i++)
{
  sa[i] = /* some value */;
}

Once you’ve completely written your data to the safe array (for example, copying it from a std::vector), the safe array can be returned as an output parameter, invoking the CComSafeArray Detach method:

*ppsa = sa.Detach();

Note that after invoking Detach, the CComSafeArray wrapper object transfers the safe array ownership to the caller. So, when the CComSafeArray object goes out of scope, the safe array created in the previous code is not destroyed. Thanks to Detach, the safe array data was just transferred, or “moved” from the previous code that created the safe array in the first place, to the caller. It’s now the caller’s responsibility to destroy the safe array when it’s no longer needed.

Safe arrays are convenient to exchange array data across DLL module boundaries, as well. Think, for example, of a C-interface DLL written in C++ that produces a safe array that’s consumed by some C# client code. If the boundary function exported by the DLL has this prototype:

extern "C" HRESULT __stdcall ProduceByteArrayData(/* [out] */ SAFEARRAY** ppsa)

the corresponding C# PInvoke declaration is:

[DllImport("NativeDll.dll", PreserveSig = false)]
public static extern void ProduceByteArrayData(
  [Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)]
  out byte[] result);

UnmanagedType.SafeArray means that the actual native array type at the function boundary is a SAFEARRAY. The Var­Enum.VT_UI1 value assigned to SafeArraySubType specifies that the type of the data stored in the safe array is BYTE (which is an unsigned integer of size exactly of one byte). For a safe array storing 4-byte-signed integers, on the C++ side you’d have CComSafe­Array<int>, and the corresponding PInvoke VarEnum type would be VT_I4 (meaning signed integer of 4-byte size). The safe array is mapped to a byte[] array in C#, and that’s passed as an out parameter.

The PreserveSig = false attribute tells PInvoke to translate the error HRESULTs returned by the native function to exceptions in C#.

Figure 1 shows a complete sample code snippet for producing a safe array of bytes in C++ using CComSafeArray. The code is part of a hypothetical COM method; however, the same CComSafeArray-based code can be used for C-interface DLL boundary functions, as well.

Figure 1 Producing a Safe Array of Bytes Using CComSafeArray

STDMETHODIMP CMyComComponent::DoSomething(/* [out] */ SAFEARRAY** ppsa) noexcept
{
  try
  {
    // Create a safe array storing 'count' BYTEs
    const LONG count = /* some count value */;
    CComSafeArray<BYTE> sa(count);
    // Fill the safe array with some data
    for (LONG i = 0; i < count; i++)
    {
      sa[i] = /* some value */;
    }
    // Return ("move") the safe array to the caller
    // as an output parameter
    *ppsa = sa.Detach();
  }
  catch (const CAtlException& e)
  {
    // Convert ATL exceptions to HRESULTs
    return e;
  }
  // All right
  return S_OK;
}

Producing a Safe Array of Strings

So now you’ve learned how to create a safe array of bytes and even the PInvoke declaration signature to use in C# when the safe array is passed as an output parameter in a C-interface DLL function. This coding pattern works well for safe arrays storing other scalar types such as ints, floats, doubles and so on. Those types have the same binary representations in both C++ and C#, and are easily marshalled between these two worlds and across COM component boundaries, as well.

However, some additional attention should be paid to safe arrays that store strings. Strings require special care, as they’re more complex than just single scalars such as bytes or integers or floating-point numbers. The BSTR type is a convenient one used to represent strings that can safely cross module boundaries. While this type is quite versatile, you can think of it as a length-prefixed Unicode UTF-16 string pointer. The default Marshaller knows how to copy BSTRs and how to make them cross COM or C-interface function boundaries. An interesting and detailed description on BSTR semantics can be read in an Eric Lippert blog post at bit.ly/2fLXTfY.

To create a safe array storing strings in C++, the CComSafeArray class template can be instantiated using the BSTR type:

// Creates a SAFEARRAY containing 'count' BSTR strings
CComSafeArray<BSTR> sa(count);

While the safe array type specified here is the raw BSTR C type, in C++ code it’s much better (that is, easier and safer) to use an RAII wrapper around raw BSTRs. ATL offers such a convenient wrapper in the form of the CComBSTR class. In Win32/C++ code compiled with Microsoft Visual C++ (MSVC), Unicode UTF-16 strings can be represented using the std::wstring class. So, it makes sense to build a convenient helper function to convert from a std::wstring to ATL::CComBSTR:

// Convert from STL wstring to the ATL BSTR wrapper
inline CComBSTR ToBstr(const std::wstring& s)
{
  // Special case of empty string
  if (s.empty())
  {
    return CComBSTR();
  }
  return CComBSTR(static_cast<int>(s.size()), s.data());
}

To fill a CComSafeArray<BSTR> with strings copied from an STL vector<wstring>, the vector can be iterated through, and for each wstring in the vector, a corresponding CComBSTR object can be created invoking the aforementioned helper function:

// 'v' is a std::vector<std::wstring>
for (LONG i = 0; i < count; i++)
{
  // Copy the i-th wstring to a BSTR string
  // safely wrapped in ATL::CComBSTR
  CComBSTR bstr = ToBstr(v[i]);

Then, the returned bstr can be copied in the safe array object, invoking the CComSafeArray::SetAt method:

hr = sa.SetAt(i, bstr);

The SetAt method returns an HRESULT, so it’s a good programming practice to check its value and throw an exception in case of errors:

//within loop
  if (FAILED(hr))
  {
    AtlThrow(hr);
  }
} // For loop

The exception will be converted to an HRESULT at the COM method or C-interface DLL function boundary. As an alternative, the error HRESULT could be returned directly from the previous code snippet.

The main difference of this BSTR safe array case with respect to the previous CComSafeArray<BYTE> sample is the creation of an intermediate CComBSTR wrapper object around the BSTR strings. There’s no need for such wrappers for simple scalar types such as bytes, integers or floating-point numbers; but for more complex types such as BSTRs that require proper lifetime management, those C++ RAII wrappers come in handy. For example, wrappers such as CComBSTR hide calls to functions such as SysAllocString, which is used to create a new BSTR from an existing C-style string pointer. Similarly, the CComBSTR destructor automatically calls SysFreeString to release the BSTR memory. All these BSTR lifetime management details are conveniently hidden inside the CCom­BSTR class implementation, so C++ programmers can focus their attention on the higher-level logic of their code.

‘Move Semantics’ Optimization for Safe Arrays of BSTRs

Note that the previous CComSafeArray<BSTR>::SetAt method call performs a deep copy of the input BSTR into the safe array. In fact, the SetAt method has an additional BOOL bCopy third parameter, which is defaulted to TRUE. This bCopy flag parameter isn’t important for scalar types such as bytes or integers of floating-point numbers, as they’re all deep copied in the safe array. However, it’s important for more complex types, such as BSTRs that have a non-trivial lifetime management and non-trivial copy semantics. For example, in this case of CComSafeArray<BSTR>, if FALSE is specified as third parameter to SetAt, the safe array simply takes ownership of the input BSTR, instead of deep copying it. It’s kind of a snappy optimized move operation, instead of a deep copy. This optimization also requires that the Detach method is invoked on the CComBSTR wrapper to properly transfer the BSTR ownership from CComBSTR to the CComSafeArray:

hr = sa.SetAt(i, bstr.Detach(), FALSE);

As already shown for the CComSafeArray<BYTE> sample, once the creation of the CComSafeArray<BSTR> is completed, the safe array can be passed (moved) to the caller with code like this:

// Return ("move") the safe array to the caller
// as an output parameter (SAFEARRAY **ppsa)
*ppsa = sa.Detach();

A C-interface DLL function that builds a safe array of BSTR strings and passes that to the caller can be PInvoked in C# like this:

[DllImport("NativeDll.dll", PreserveSig = false)]
public static extern void BuildStringArray(
  [Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
  out string[] result);

Note the use of VarEnum.VT_BSTR to indicate the presence of a safe array storing BSTR strings. The SAFEARRAY of BSTRs produced in native C++ code is marshalled to C# using a string[] array type, passed as an out parameter.

Producing a Safe Array of Variants That Contain Strings

Let’s further increase the level of complexity by another step. In addition to safe array storing elements of types such as byte, integer and BSTR strings, it’s also possible to create a safe array of a “generic” type—the VARIANT type. A variant is a polymorphic type that can store values of a wide variety of different types, ranging from integers, to floating-point numbers, to BSTR strings and so on. The VARIANT C type is basically a gigantic union; its definition can be found at bit.ly/2fMc4Bu. Just like for the BSTR C type, ATL offers a convenient C++ wrapper around the raw C VARIANT type: the ATL::CComVariant class. In C++ code, it’s simpler and safer to handle variants using the CComVariant wrapper, instead of directly invoking C functions for allocating, copying and clearing variants.

While clients written in C++ and C# understand safe arrays storing “direct” types (like shown in previous examples of BYTE and BSTR), there are some scripting clients that only understand safe array storing variants. So, if you want to build array data in C++ and you want to make that data consumable by such scripting clients, this data must be packed in a safe array storing variants, adding a new level of indirection (and some additional overhead, as well). Each variant item in the safe array will in turn store either a BYTE, a floating-point number, a BSTR or a value of whatever supported type.

Suppose that you want to modify the previous code that builds a safe array of BSTR strings, using a safe array of variants instead. The variants will in turn contain BSTRs, but what will be produced is a safe array of variants, not a direct safe array of BSTR strings.

First, to create a safe array of variants, the CComSafeArray class template can be instantiated this way:

// Create a safe array storing VARIANTs
CComSafeArray<VARIANT> sa(count);

Then, you can iterate through a set of strings stored, for example, in an STL vector<wstring>, and for each wstring you can create a CComBSTR object, just like in the previous code sample :

// 'v' is a std::vector<std::wstring>
for (LONG i = 0; i < count; i++)
{
  // Copy the i-th wstring to a BSTR string
  // safely wrapped in ATL::CComBSTR
  CComBSTR bstr = ToBstr(v[i]);

Now there comes a difference from the previous case of safe arrays of BSTRs. In fact, because this time you’re building a safe array of VARIANTs (not a direct safe array of BSTRs), you can’t store the BSTR object directly in the CComSafeArray calling SetAt. Instead, first a variant object must be created to wrap that bstr; and then that variant object can be inserted into the safe array of variants:

// First create a variant from the CComBSTR
  CComVariant var(bstr);
  // Then add the variant to the safe array
  hr = sa.SetAt(i, var);
  if (FAILED(hr))
  {
    AtlThrow(hr);
  }
} // For loop

Note that CComVariant has an overloaded constructor taking a const wchar_t* pointer, so it would be possible to directly build a CComVariant from a std::wstring, invoking the wstring c_str method. However, in this case the variant will store only the initial chunk of the original wstring up until the first NUL-terminator; while both wstring and BSTR can potentially store strings with embedded NULs. Therefore, building an intermediate CComBSTR from the std::wstring, as previously done in the custom ToBstr helper function, covers this more generic case, as well.

As usual, to return the created safe array to the caller as a SAFEARRAY** output parameter, the CComSafeArray::Detach method can be used:

// Transfer ownership of the created safe array to the caller
*ppsa = sa.Detach();

If the safe array is passed via a C-interface function like this:

extern "C" HRESULT __stdcall BuildVariantStringArray(/* [out] */ SAFEARRAY** ppsa)

the following C# PInvoke declaration can be used:

[DllImport("NativeDll.dll", PreserveSig = false)]
pubic static extern void BuildVariantStringArray(
  [Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)]
  out string[] result);

Note the use of VarEnum.VT_VARIANT for the SafeArraySubType, as this time the safe array created in C++ contains variants (that in turn wrap BSTR strings), but not BSTR directly.

In general, I’d suggest you export data using safe arrays of direct types instead of safe arrays storing variants, unless you’re constrained by making your data accessible by scripting clients that can only handle the safe array of variants.

Wrapping Up

The safe array data structure is a convenient tool to exchange array data across different module and language boundaries. The safe array is a versatile data structure, and it’s possible to store in it simple primitive types such as bytes, integers or floating-point numbers, but also more complex types such as BSTR strings, or even generic VARIANTs. This article showed how to simplify the programming of such data structures in C++ with concrete sample cases using ATL helper classes.


Giovanni Dicanio is a computer programmer specializing in C++ and the Windows OS, a Pluralsight author (bit.ly/GioDPS) and a Visual C++ MVP. Besides programming and course authoring, he enjoys helping others on forums and communities devoted to C++. He can be contacted at giovanni.dicanio@gmail.com. He also blogs on msmvps.com/gdicanio.

Thanks to the following technical experts for reviewing this article: David Cravey and Marc Gregoire
David Cravey is an Enterprise Architect at GlobalSCAPE, leads several C++ user groups, and was a four-time Visual C++ MVP.
Marc Gregoire is a senior software engineer from Belgium, the founder of the Belgian C++ Users Group, author of “Professional C++” (Wiley), co-author of “C++ Standard Library Quick Reference” (Apress), technical editor on numerous books, and since 2007, has received the yearly MVP award for his VC++ expertise. Marc can be contacted at marc.gregoire@nuonsoft.com.


Discuss this article in the MSDN Magazine forum