Chapter 6. Advanced Topics for Using COM Components

In This Chapter

Do-It-Yourself Marshaling

Threading and Apartments

Troubleshooting an InvalidCastException

Garbage Collection

Securing Unmanaged Calls

Using COM+ and DCOM Objects

Inheriting from COM Classes

Debugging into COM Components

Monitoring Performance

The previous three chapters covered a lot of information that should help you use most COM components within .NET components and applications, especially if the author of the COM component has produced a customized Primary Interop Assembly. This chapter focuses on a handful of advanced topics that should help you use any COM components in .NET:

• Do-it-yourself marshaling

• Threading and apartments

• Troubleshooting an InvalidCastException

• Garbage collection

• Securing unmanaged calls

• Using COM+ and DCOM objects

• Inheriting from COM classes

• Debugging into COM components

• Monitoring performance

Do-It-Yourself Marshaling

The Interop Marshaler knows what to do only by examining a .NET signature described in metadata. Often, the metadata created by the type library importer needs to be modified to work around limitations of the importer or of the marshaler itself. Chapter 7, “Modifying Interop Assemblies,” describes how to change signatures created by the type library importer, but this section describes what kind of signature changes can be beneficial. What I call “do-it-yourself marshaling” is different from what’s considered custom marshaling, which uses the same kind of do-it-yourself marshaling encapsulated inside a custom marshaling class. Custom marshaling is described in Chapter 20, “Custom Marshaling.”

The data type typically involved in “do-it-yourself marshaling” is System.IntPtr, a CLS-compliant type that can be used to represent a pointer to anything (in other words, void*), because it’s a value that’s the size of a pointer. For IntPtr types, the Interop Marshaler does nothing more than expose the value, just as it would for any other integral type. There’s nothing intrinsic to IntPtr that makes it contain the value of a pointer; it’s just a good use of the type because it’s always the correct size for the underlying platform. Therefore, when a pointer to a COM data type is passed as a System.IntPtr, managed code gets a chance to manipulate and use the COM data type’s raw memory representation without the marshaler’s intervention. COM-style marshaling still happens if and when appropriate, as shown in Figure 6.1, an update to Figure 3.10 in Chapter 3, “The Essentials for Using COM in Managed Code.”

Figure 6.1. Interop marshaling depends upon the metadata signature.

Image

The first case represents typical Interop Marshaling, for which a pointer to a BSTR containing “abc” is marshaled as a reference to a System.String type. The only difference in the second case is that the parameter in the .NET signature is System.IntPtr. In this case, the raw pointer to the COM-marshaled BSTR is exposed to managed code. Although it isn’t too compelling to work with raw BSTR types in managed code because .NET strings can express the same information (and have tons of manipulation methods), this can be a great technique for types such as VARIANT for which the Interop Marshaler doesn’t handle all possible varieties. Or, it can become a necessary technique for COM objects that expose parameters as void* types that would have otherwise been able to be marshaled automatically had their type information been more specific.

The primary means for manipulating System.IntPtr types as if they are pointers is the Marshal class in the System.Runtime.InteropServices namespace and its many members. This chapter uses just a handful of its methods in the examples, but the Marshal class and all of its members are described in Appendix A, “System.Runtime.InteropServices Reference.”

As mentioned in Chapter 4, “An In-Depth Look at Imported Assemblies,” whenever the type library importer encounters a parameter or return type that it can’t marshal, it emits the following warning:

At least one of the arguments for 'MemberName' can not be marshaled by the
runtime marshaler. Such arguments will therefore be passed as a pointer and
may require unsafe code to manipulate.

For example, when importing the DirectX 7 for Visual Basic Type Library (DX7VB.DLL), the importer emits 18 such warnings. Seventeen of these are simply due to a void* argument in a method signature, which is really marshaled correctly by the Interop Marshaler given the limited type information.

Passing an argument “as a pointer” simply means using the System.IntPtr type. The term “unsafe code” refers to the C# feature to use pointer syntax inside members or code blocks marked with the unsafe keyword. The text is a bit misleading because the signatures produced by the importer never have pointer types so they aren’t instantly usable by C# unsafe code without at least a cast from the IntPtr type to something like void*. Furthermore, C# unsafe code is never required to manipulate such types; the same manipulations can be performed in any language using the CLS-compliant APIs in System.Runtime.InteropServices, as described in the next section.

C# Unsafe Code Versus System.Runtime.InteropServices APIs

C# unsafe code is a great feature for performing direct memory manipulation in a way that’s natural to C and C++ programmers (with some limitations). In the heated language wars that are often verbally fought between C# and VB .NET zealots, I commonly hear that “C# has unsafe code, but VB .NET doesn’t.” It’s important to understand, however, that you can do just about anything that’s possible with C# unsafe code in any .NET language thanks to two classes in the System.Runtime.InteropServices namespace—Marshal and GCHandle. The syntax isn’t always as readable and almost never as concise as using pointers, but the memory manipulations are the same.

Tip

Whenever using C# unsafe code, it not only must be used inside a class, member, or block marked unsafe, but you must use the /unsafe option when compiling the code using the command-line compiler. Inside Visual Studio .NET, this can be done by selecting Configuration Properties, Build on your Visual C# .NET project’s property page, then setting Allow unsafe code blocks to True.

The following is a list of ten features enabled by C# unsafe code and instructions for accomplishing the same functionality in any language (shown in Visual Basic .NET) using the interoperability APIs:

• Declaring and using pointer types (like byte*, int*, and so on). This can effectively be done in any language by declaring the type as IntPtr.

C# Unsafe Code:

byte* ptrToByte;

Language-Neutral Approach:

Dim ptrToByte As IntPtr

• Pointer indirection (such as *ptr), also known as dereferencing a pointer. This can also be accomplished with several of Marshal’s methods, such as ReadByte, ReadInt16, ReadInt32, ReadInt64, and ReadIntPtr.

C# Unsafe Code:

byte* ptrToByte = ...;
byte b = *ptrToByte;

Language-Neutral Approach:

Dim ptrToByte As IntPtr = ...
Dim b As Byte = Marshal.ReadByte(ptrToByte)

Obtaining a pointer to a variable (the ellipses in the previous code) is discussed in the “Obtaining the Address of Value Types” section after this list.

• Pointer member access (using ptr->member as a shortcut for (*ptr).member). This can also be accomplished with several of Marshal’s methods, such as ReadByte, ReadInt16, ReadInt32, ReadInt64, ReadIntPtr. Examples of using these methods are shown in “Examples of Manipulating IntPtr Types” section.

• C-like array pointer syntax (so ptr[i] means *(ptr + i)). With Marshal.Copy, you could copy a pointer to a C-style array to a .NET array, but to avoid copying you must use Marshal.Read... and Marshal.Write... rather than array syntax in languages other than C#.

C# Unsafe Code:

public unsafe void FillArray(byte* ptr, int count, byte value)
{
  for (int i = 0; i < count; i++) ptr[i] = value;
}

Language-Neutral Approach:

Public Sub FillArray(ptr As IntPtr, count As Integer, value As Byte)
  Dim i As Integer
  For i = 0 To count-1
    Marshal.WriteByte(ptr, i, value)
  Next i
End Sub

Obtaining the address of value types (like &someInteger). Without C# unsafe code, this can’t be done cleanly, but there are workarounds, discussed after this list in the “Obtaining the Address of Value Types” section.

• Pointer arithmetic on all pointer types except for void* (the +, -, ++, and -- operators). This is only directly possible with C# unsafe code. IntPtr types must be converted to other integers before arithmetic can be done.

C# Unsafe Code:

byte* ptrToByte = ...;
byte* newPtr = ptrToByte + 10;

Language-Neutral Approach:

Dim ptrToByte As IntPtr = ...
' This only works on 32-bit platforms due to calling IntPtr.ToInt32
Dim newPtr = New IntPtr(ptrToByte.ToInt32() + 10)

• Pointer comparison (the ==, !=, >, <, >=, and <= operators). In contrast, IntPtr supports the equality and inequality operators, but any other comparisons must be done by converting IntPtr types to other integers first. In Visual Basic .NET, IntPtr types cannot be compared with = or <> because the language doesn’t support overloaded operators. Instead, you can call the IntPtr.op_Equality or IntPtr.op_Inequality methods.

C# Unsafe Code:

if (ptrToByte != newPtr) ...

Visual Basic .NET:

If (IntPtr.op_Inequality(ptrToByte, newPtr)) ...

• Obtaining the size, in bytes, of value types using the sizeof keyword. This is similar to Marshal.SizeOf but it reports the managed size of an instance, not the unmanaged size. Plus, it can only be used on value types, not on reference types with structure layout. This is explained in more depth after this list.

• Pinning a reference type using the fixed statement so a pointer to it can be used directly without its physical location on the GC heap changing. This can be done in any language using the GCHandle class in the System.Runtime.InteropServices namespace. The following silly example demonstrates how to treat a .NET string as a character array without having to allocate a separate array (as String.ToCharArray does).

C# Unsafe Code:

string s = "abcdefg";
// Pin the string to a fixed location
fixed (char* ptr = s)
{

  // Print each character on a separate line, up to the first null
  for (int i = 0; ptr[i] != ''; i++)
  {
    Console.WriteLine(ptr[i]);
  }
}

Language-Neutral Approach:

Dim s As String = "abcdefg"
Dim handle As GCHandle
Try
  ' Pin the string to a fixed location
  handle = GCHandle.Alloc(s, GCHandleType.Pinned)
  Dim ptr As IntPtr = handle.AddrOfPinnedObject()

  ' Print each character on a separate line, up to the first null
  Dim i As Integer = 0
  While (Marshal.ReadInt16(ptr, i) <> 0)
    Console.WriteLine(Convert.ToChar(Marshal.ReadInt16(ptr, i)))
    i = i + 2
  End While
Finally
  If (handle.IsAllocated) Then handle.Free()
End Try

Tip

Notice that when treating a .NET string as a character array, the character index is incremented by one each iteration in C# unsafe code. On the other hand, in Visual Basic .NET, the index must be incremented by two each time. When calculating offsets using IntPtr types, you must always specify the number in bytes (in this case, 2 bytes for a Unicode character). In contrast, when doing pointer arithmetic in C# unsafe code, the offset is automatically multiplied by the size of the pointed-to data.

• Allocating memory on something other than a GC heap using the stackalloc keyword. The Marshal class doesn’t provide a way to allocate memory on the stack, but does provide methods such as AllocCoTaskMem and AllocHGlobal for allocating memory on heaps not managed by the .NET garbage collector.

Tip

Pinning an object in a garbage-collected heap is necessary when doing something with its memory address, because the .NET garbage collector is free to move the physical location of an object at any time. For instance, it compacts objects when “holes” appear in memory. Although regular object references don’t need any special treatment to account for the moveable nature of .NET objects, pointers are a different story. If an object moves while you are using a static memory address, you may end up manipulating memory that doesn’t belong to the object you thought it did.

Because pinning objects interferes with the garbage collector’s operation, too much pinning for long periods of time can significantly reduce the performance of your application. Therefore, make fixed statements as small as possible and free any allocated GCHandle instances as early as possible. In addition, allocating a GCHandle should be done inside a try block and freeing it should be done inside a finally block, so an exception won’t prevent proper clean-up. The fixed statement implicitly has the same sort of try...finally semantics so you don’t need to worry about ensuring unpinning when using it.

Now let’s examine two of the list items a little closer:

• Obtaining the address of value types

• Obtaining the size of types

Obtaining the Address of Value Types

Using C# unsafe code, obtaining the address of a value type is simple:

int x = 5;
int* address = &x;

or:

int x = 5;
IntPtr address = new IntPtr(&x);

The & operator works on blittable types, or other primitive types such as bool or char, plus structs containing fields of these primitive types.

Without C# unsafe code, you can accomplish the same thing using the GCHandle class to pin the instance. Pinning can be done to an object on a GC heap, so this technique actually operates on the boxed form of a value type. This isn’t very natural, unlike the case for reference types that already exist on the GC heap, but it can be done as follows in Visual Basic .NET:

Dim x As Integer = 5
Dim box As Object = x
Dim handle As GCHandle
Try
  handle = GCHandle.Alloc(box, GCHandleType.Pinned)
  Dim address As IntPtr = handle.AddrOfPinnedObject()
  ...
Finally
  If (handle.IsAllocated) Then handle.Free()
End Try
x = CInt(box)

Notice how an Object variable named box is explicitly declared and assigned back to the integer x after the call. Passing a value type like x directly to GCHandle.Alloc would not give the desired results because it would be operating on a temporary boxed object that has no relation to the original value type.

This combination of boxing a value type and pinning carries a lot of overhead for the simple action of getting an address of a value type. Often, a better solution is to allocate memory using Marshal.AllocHGlobal or Marshal.AllocCoTaskMem because these methods return the desired IntPtr value containing the address of the memory. For example:

Dim address As IntPtr = IntPtr.Zero
Try
  ' Allocate four bytes of memory
  address = Marshal.AllocHGlobal(Marshal.SizeOf(GetType(Int32)))
  ' Initialize the value
  Marshal.WriteInt32(address, 5)
  ...
Finally
  If IntPtr.op_Inequality(address, IntPtr.Zero) Then
    Marshal.FreeHGlobal(address)
  End If
End Try

Obtaining the Size of Types

You might guess that C#’s sizeof keyword (used with the name of a type) and the Marshal class’s SizeOf method (which has a System.Type parameter) do the same thing. You’d be wrong. (It’s okay; I was fooled too.) This incorrect assumption is easy to make, especially considering the error message given by the C# compiler when attempting to use sizeof outside of an unsafe block:

error CS0233: sizeof can only be used in an unsafe
  context (consider using System.Runtime.InteropServices.Marshal.SizeOf)

In short, C#’s sizeof reports the managed size of a type (in bytes), whereas Marshal.SizeOf reports the unmanaged size of a type (also in bytes). The unmanaged size of a type is the size of whatever unmanaged type a managed type would be marshaled to by the Interop Marshaler. When non-blittable types are involved, the managed and unmanaged sizes can be different.

As a demonstration, consider the following value type defined in C#:

public struct HowBigAmI
{
  public char un;
  public char deux;
  public char trois;
}

The following C# code prints the size of the value type using both techniques. The results are shown in the comments:

Console.WriteLine(sizeof(HowBigAmI));                  // Prints 8
Console.WriteLine(Marshal.SizeOf(typeof(HowBigAmI)));  // Prints 3

Why such a big difference? In .NET, the char type is a Unicode character, which occupies two bytes. So, you might expect sizeof to return 6, but the size of the structure ends up being eight bytes. That’s because the official contract for sizeof states that it returns the total size that would be occupied by each element in an array of the type in question, including any padding. In this case, two bytes of padding are added to the end of the value type.

Each character field, however, is marshaled to unmanaged code as a one-byte ANSI character. This is due to the fact that all value types defined in C# are implicitly marked with the following pseudo-custom attribute:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]

No padding is done for the unmanaged struct, so the unmanaged size of HowBigAmI is 3 bytes (one byte per character).

Caution

Never use Marshal.SizeOf to determine the “real size” of a managed type, because the results can be misleading. Just think of Marshal.SizeOf as “Marshal.UnmanagedSizeOf”. An appropriate use of Marshal.SizeOf is to fill a struct’s “size field” when passing it to unmanaged code. An example of this is shown in Chapter 19, “Deeper Into PInvoke and Useful Examples.”

Furthermore, you should never use Marshal.SizeOf with the System.Char type to determine the current platform’s character size—use Marshal.SystemDefaultCharSize instead. This is because Marshal.SizeOf always reports 1 for the size of the System.Char type regardless of platform due to an extremely subtle reason. It reports the size of the System.Char value type in the mscorlib assembly, which has a single character field. Because the definition of System.Char is marked with CharSet.Ansi, the structure would have a size of 1 byte if marshaled to unmanaged code as a plain struct (just as HowBigAmI would have a size of 3 bytes). But because the System.Char type is treated specially as a character primitive type, it is never passed as a plain struct to unmanaged code. By default, character parameters and return types are marshaled as 2-byte Unicode characters, and character fields in a structure are marshaled depending on the structure’s character set. Note that doing sizeof(char) in C# unsafe code always returns 2 regardless of platform because .NET characters are always Unicode.

Another difference between the two size-taking techniques is the set of types for which you’re allowed to discover the size. You can use sizeof on any types that you could take the address of (using &): blittable types plus primitive types like char and bool, and any value types composed only of these blittable or primitive types. Marshal.SizeOf is a little more flexible because it doesn’t return the true managed size; it can be used on any type that would be successfully marshaled to unmanaged code as a structure. This includes reference types with struct layout. So, for the following redefined HowBigAmI type:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public class HowBigAmI
{
  public char un;
  public char deux;
  public char trois;
}

sizeof(HowBigAmI) fails to compile with the message:

Cannot take the address or size of a variable of a managed type ('HowBigAmI')

because it looks like any other reference type from .NET’s perspective. However, Marshal.SizeOf still returns 3 because it looks like the same HowBigAmI struct to unmanaged code.

Examples of Manipulating IntPtr Types

Now let’s look at five examples in which manipulating IntPtr types commonly needs to be done. These are:

struct**

• C-style array fields

• By-reference value types that could be null

VARIANTs containing structures

SAFEARRAYs containing VARIANTs containing structures

struct**

A COM parameter that is a reference to a reference to a structure is a classic example of a case for which the type library importer produces an IntPtr type, losing information from the original signature. The problem for the importer is that there’s no such thing as a reference to a by-reference parameter in the .NET world. It could produce a signature literally with struct** because the .NET type system does have a notion of pointers (as in C# unsafe code or C++) but this signature would not be CLS-compliant. As an important example, it would not be useable from Visual Basic .NET.

The following method from the IDirectoryObject interface in the ActiveDS Type Library (ACTIVEDS.TLB) has a struct** parameter, because _ads_attr_info is defined as a structure:

HRESULT _stdcall GetObjectAttributes(
  [in] LPWSTR* pAttributeNames,
  [in] unsigned long dwNumberAttributes,
  [out] _ads_attr_info** ppAttributeEntries,
  [out] unsigned long* pdwNumAttributesReturned);

Therefore, the importer produces the following signature (in C# syntax):

public virtual void GetObjectAttributes(
  [In, MarshalAs(UnmanagedType.LPWStr)] ref string pAttributeNames,
  uint dwNumberAttributes,
  IntPtr ppAttributeEntries,
  out uint pdwNumAttributesReturned);

This method allocates memory for the ppAttributeEntries structure returned via the third parameter, and because its original definition is marked as out-only we know that it’s safe to pass a null pointer to the method. (In System.IntPtr terms, a “null pointer” means IntPtr.Zero.) The method actually returns an array of _ads_attr_info structures, but for this example we’ll assume it’s returning a 1-element array for simplicity. The example in the “C-Style Array Fields” section shows what to do when you’ve got a pointer to an array of structures.

Tip

A null pointer appears as an IntPtr type with the value of 0. The easiest way to initialize an IntPtr to null is to set it to the static IntPtr.Zero field, which is simply an IntPtr instance with the value of 0. Similarly, comparing an IntPtr to IntPtr.Zero is the easiest way to check if it’s null. You could use the expression new IntPtr(0) but its intent isn’t as clear.

The default importer behavior of transforming struct** to an IntPtr type is unfortunate, because it only works when the callee doesn’t wish to change the address of the pointer being pointed to. For instance, you might believe you could call the GetObjectAttributes method using the following C# code:

Image

But this won’t work because the passed-in ppStruct value cannot change because it’s just a by-value integer. It would still have the value IntPtr.Zero despite the fact that the callee tried to pass back the address of the memory it allocated. To fix this, we must use the techniques described in Chapter 7 to convert the outermost pointer to a reference. In other words, the by-value IntPtr representing a pointer-to-pointer-to-struct becomes a by-reference IntPtr representing a simple pointer-to-struct. Here, we’ll just focus on how to do everything except the signature editing, leaving it for the next chapter, so this section’s example assumes that the GetObjectAttributes method has the following .NET signature (shown in C# syntax):

public virtual void GetObjectAttributes(
  [In, MarshalAs(UnmanagedType.LPWStr)] ref string pAttributeNames,
  uint dwNumberAttributes,
  out IntPtr pAttributeEntries,
  out uint pdwNumAttributesReturned);

The tricky part of do-it-yourself marshaling is that you have to be cognizant of who allocates memory, who is supposed to free it, and how memory is allocated and freed. Usually the Interop Marshaler handles this for you, although it only works correctly for memory allocated and freed by the COM task memory allocator. However, when you effectively bypass the marshaler (by making it think it’s passing a simple integer for which it does no extra work), the responsibility is on your shoulders. In this case, the documentation for GetObjectAttributes states that the caller must free the memory for the returned array by calling the FreeADsMem API, a static entry point exposed by ACTIVEDS.DLL.

The following C# code demonstrates how to call the updated GetObjectAttributes method:

IntPtr ptr = IntPtr.Zero;
try
{
  obj.GetObjectAttributes(ref names, 1, out ptr, out attributes);
  // Copy the data from the pointer-to-struct
  _ads_attr_info theStruct = (_ads_attr_info)Marshal.PtrToStructure(
    ptr, typeof(_ads_attr_info));
  // Now use theStruct just like any .NET value type
  ...
}
finally
{
  // Free the memory allocated by GetObjectAttributes
  if (ptr != IntPtr.Zero) FreeADsMem(ptr);
}

First, the method is called passing a null pointer. After the call, Marshal.PtrToStructure can be called to fill a new instance of the _ads_attr_info type with the data from the instance pointed to by ptr. In the finally block, we free the memory using FreeADsMem, as the documentation instructs. To be able to call FreeADsMem from managed code, we need to define a PInvoke signature for it, for example:

[DllImport("activeds.dll")]
static extern bool FreeADsMem(IntPtr pMem);

PInvoke is discussed in Part VI, “Platform Invocation Services (PInvoke).”

If you wanted to implement IDirectoryObject in managed code, you could implement the updated GetObjectAttributes method as follows in C#:

public virtual void GetObjectAttributes(
  [In, MarshalAs(UnmanagedType.LPWStr)] ref string pAttributeNames,
  uint dwNumberAttributes,
  out IntPtr pAttributeEntries,
  out uint pdwNumAttributesReturned)
{
  // For simplicity, assume we want to return a 1-element array
  pAttributeEntries = AllocADsMem(Marshal.SizeOf(typeof(_ads_attr_info)));

  // Create a new value type
  _ads_attr_info newStruct = new _ads_attr_info();
  // Fill the struct's fields with data
  newStruct.pszAttrName = "AttrName";
  ...
  // Copy the data to the struct allocated in unmanaged memory
  Marshal.StructureToPtr(newStruct, pAttributeEntries, false);
}

To comply with the documented behavior of GetObjectAttributes, memory is allocated using AllocADsMem rather than using Marshal.AllocCoTaskMem or Marshal.AllocHGlobal. Again, this requires a PInvoke signature:

[DllImport("activeds.dll")]
static extern IntPtr AllocADsMem(int cb);

After the fields of the new struct are set with the usual syntax, the data is copied to the unmanaged memory using Marshal.StructureToPtr. The last parameter, for which false is passed, determines whether any memory for reference type fields in the struct living in unmanaged memory should be freed before overwriting it with the contents of the value type. For example, if the pszAttrName string field were pointing to a valid string in the unmanaged copy of the structure, we’d want to free it by passing true for the third parameter. In this case, because the field is set to null in the freshly-allocated unmanaged memory, we pass false.

C-Style Array Fields

Another example in which the type library importer creates an IntPtr type is for structs containing a C-style array field. The ADS_FAXNUMBER structure, also in the Active DS Type Library and shown here in IDL syntax, is such a structure:

typedef [public] __MIDL___MIDL_itf_ads_0000_0013 ADS_FAXNUMBER;

typedef struct tag__MIDL___MIDL_itf_ads_0000_0013 {
  LPWSTR TelephoneNumber;
  unsigned long NumberOfBits;
  unsigned char* Parameters;
} __MIDL___MIDL_itf_ads_0000_0013;

This is imported as follows (in C# syntax):

[ComConversionLoss]
public struct ADS_FAXNUMBER
{
  [MarshalAs(UnmanagedType.LPWStr)] string TelephoneNumber;
  uint NumberOfBits;
  IntPtr Parameters;
}

To make use of the elements in this array, Marshal.Copy can be used to extract the elements and create a new .NET array with the same data. In Visual Basic .NET, this looks like the following:

Dim n As ADS_FAXNUMBER
Dim i As Integer
n = obj.ReturnFaxNumber()

' Create a new .NET array of the appropriate length
Dim byteArray(arrayLength-1) As Byte
' Copy the data pointed to by the IntPtr value
Marshal.Copy(n.Parameters, byteArray, 0, arrayLength)

For i = 0 To arrayLength
  Console.WriteLine(byteArray(i))
Next i

As is always the case with C-style arrays, the length of the array must be known through some external means, such as a separate parameter. In this example, an imaginary arrayLength variable is assumed to contain this information.

To avoid the potentially slow operation of copying a large chunk of data pointed to by an IntPtr to a .NET instance, the Marshal class provides several reading and writing methods that enable you to work with the data directly. So, the previous example could be changed as follows:

Dim n As ADS_FAXNUMBER
Dim i As Integer
n = obj.ReturnFaxNumber()

' Directly use the returned "pointer"
For i = 0 To arrayLength
  Console.WriteLine(Marshal.ReadByte(n.Parameters, i))
Next i

This can be useful if you’re interested in only a small number of elements of a large array. For this example, however, calling Marshal.ReadByte for every element of the array is slower than calling Marshal.Copy once, because every call requires a transition into unmanaged code. As described in Chapter 16, “COM Design Guidelines for Components Used by .NET Clients,” minimizing the number of transitions between managed and unmanaged code can significantly improve performance. Replacing several calls to unmanaged code within a loop with a single larger call outside the loop is a great way to do this.

If you need to create a type to be passed via an IntPtr, you often need to allocate unmanaged memory (memory not on a GC heap) using a method of the Marshal class as follows:

Dim n As ADS_FAXNUMBER
Dim ptr As IntPtr = IntPtr.Zero
Dim byteArray(arrayLength-1) As Byte
n = New ADS_FAXNUMBER()

...
Try
  ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(GetType(Byte)) * arrayLength)
  ' Copy the .NET array to the allocated unmanaged memory
  Marshal.Copy(byteArray, 0, ptr, arrayLength)
  n.Parameters = ptr
  ...
Finally
  If IntPtr.op_Inequality(ptr, IntPtr.Zero) Then Marshal.FreeCoTaskMem(ptr)
End Try

Again, methods exist to avoid copying data in the managed to unmanaged direction. In the previous example, Marshal.WriteByte could have been used to avoid creating a .NET array in the first place. Notice the use of Marshal.SizeOf to determine how many bytes of unmanaged memory need to be allocated.

By-Reference Value Types That Could Be Null

Sometimes a COM object has a parameter that’s a pointer to a value type for which null (Nothing in VB .NET) is a valid thing to pass. A common example of this can be seen with COM’s IStream interface and its Read method, shown here in IDL syntax:

HRESULT Read([out, size_is(cb), length_is(*pcbRead)] void *pv,
  [in] ULONG cb, [out] ULONG *pcbRead);

In unmanaged code, if you pass a valid pointer to a ULONG type, the method returns the number of bytes read. If you pass a null pointer, it doesn’t bother giving you the information. Such a method gets imported as follows:

public virtual void Read(IntPtr pv, uint cb, out uint pcbRead);

But in .NET, value types cannot be null, nor can you pass null when a reference to any type (value type or reference type) is required. When calling such a method in managed code, you’d never be able to pass null, which is usually not a huge loss. For IStream.Read, you could call it as follows in C# and simply ignore the returned integer if you don’t care about the value:

uint pcbRead;
stream.Read(pv, cb, out pcbRead);

The situation is worse if you need to implement such a COM method in managed code, however. If a COM object calls your Read implementation and passes a null pointer for the third parameter, an exception is thrown at run time. This is not correct behavior because the method’s documentation states that it’s okay to pass null. To fix this, you can use the techniques in Chapter 7 to change the by-reference integer to an IntPtr parameter to get the following .NET signature:

public virtual void Read(IntPtr pv, uint cb, IntPtr pcbRead);

This method can now be called in C# by doing the following to pass null:

// Pass a "null pointer"
stream.Read(pv, cb, IntPtr.Zero);

or the following to pass a valid pointer:

IntPtr ptr = IntPtr.Zero;
try
{
  // Allocate an integer so we can pass its pointer
  ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Int32)));
  stream.Read(pv, cb, ptr);
  // Extract the returned value
  int value = Marshal.ReadInt32(ptr);
  ...
}
finally
{
  if (ptr != IntPtr.Zero) Marshal.FreeHGlobal(ptr);
}

Furthermore, the Read method can now be implemented as follows, which works even if a COM object passes a null pointer:

public virtual void Read(IntPtr pv, uint cb, IntPtr pcbRead)
{
  ...
  if (pcbRead != IntPtr.Zero)
    Marshal.WriteInt32(pcbRead, someNumber);
}

Tip

There’s another way to solve the problem of by-reference value types that could be null so that you don’t have to resort to using the Marshal class to manipulate IntPtr types. In the Stream.Read example, rather than defining the by-reference integer as an IntPtr you can define it as an array of integers:

public virtual void Read(IntPtr pv, uint cb,   [Out, MarshalAs(UnmanagedType.LPArray)] int [] pcbRead);

(In this case, OutAttribute isn’t strictly necessary because integers are blittable.) You can pass null for such a parameter because arrays are reference types. To pass a valid integer by-reference, simply pass a one-element array. When implementing such a method, checking for null is straightforward, and extracting the by-reference value is done simply as pcbRead[0]! The only downside to this approach is the potential confusion caused by users of the method that don’t understand why the parameter is an array.

VARIANTs Containing Structures

The next example works with a signature containing a pointer to a VARIANT, which gets imported as a by-reference System.Object parameter. Usually this is the desired behavior, but not when the VARIANT must contain a structure (UDT), also known as a VARIANT with the VT_RECORD type (because record is another term for structure). This is problematic for two reasons:

• There is no way to pass a VT_RECORD VARIANT to COM via a System.Object type. The Interop Marshaler exposes boxed value types and reference types with struct layout as VT_DISPATCH VARIANTs (or VT_UNKNOWN, when appropriate) so a COM object can’t treat it like a structure or even access its fields directly; it could only call its methods such as GetType or ToString. (A .NET-aware COM client could use reflection, however, to extract the field values!) Attempting to pass a boxed value type to a COM component expecting a structure usually results in an exception, such as a “Type Mismatch” COMException from VB6 components.

• There is no way for a COM object to return a VT_RECORD VARIANT to managed code via a System.Object type. This applies to return values as well as by-reference Object parameters. When a COM object attempts to do so, the Interop Marshaler throws an ArgumentException with the following message:

The method returned a VT_RECORD Variant, which is not supported by Interop.

This message should really say that it’s not naturally supported by the Interop Marshaler, because you can get this to work, as this section demonstrates. Using the techniques in Chapter 7, we can change such a by-reference Object parameter to an IntPtr type in order to take VARIANT marshaling into our own hands. Again, we’ll just focus on how to do everything except the signature editing, leaving it for the next chapter.

Suppose we have a Visual Basic 6 COM component (an ActiveX DLL project) with the following code inside a class module named Class1:

Type RECT
  Left As Long
  Top As Long
  Right As Long
  Bottom As Long
End Type

Public Sub FillRect(ByRef v As Variant)
  Dim r As RECT
  r.Left = 0
  r.Top = 0
  r.Right = Screen.Width
  r.Bottom = Screen.Height
  v = r
End Sub

When running the type library importer on the compiled DLL and its embedded type library, we get the following signature (in C# syntax):

public virtual void FillRect(ref object v);

We’ve already established that such a signature is unusable from managed code. It runs into the previously mentioned second scenario because it doesn’t do anything with the incoming VARIANT, but it attempts to pass one to a .NET client. Assume that, using the techniques discussed in Chapter 7, the signature is changed to look like the following:

public virtual void FillRect(IntPtr v);

Listing 6.1 demonstrates how to use the updated signature in C#. See Chapter 7 for instructions on how to modify the Interop Assembly containing the FillRect definition, which is required in order to compile this listing.

Listing 6.1. Performing Manual VARIANT Marshaling in C#

Image

Image

Image

Image

Lines 1–4 list the System namespace for the IntPtr type; System.Reflection for the generic VARIANT-to-Object transformation done at the end of the listing; System.Runtime.InteropServices for a handful of custom attributes, the Marshal and GCHandle classes, and more; and ComObject is the namespace containing the Visual Basic 6 class—Class1—with the FillRect method.

Lines 6–14 contain a definition of the raw COM VARIANT structure, simplified for our specific use of the type. The real definition has two unions: one that shares all 16 bytes with a DECIMAL field named decVal, and one that shares the 8 bytes occupied by pvRecord and pRecInfo with 39 other fields so you can view the data as an integer or a date or a boolean, and so on. This definition is entirely usable regardless of the type of VARIANT because the IntPtr types can be manipulated however you’d like; the field names are just biased toward using the structure with the VT_RECORD type. The structure implicitly has sequential layout, required by all structs marshaled to unmanaged code, because this is the default behavior in C# (as well as in VB .NET and C++).

Caution

If you attempt to define a COM structure such as Variant in any other language than C#, VB .NET, or C++, check to see if your compiler automatically emits value types with sequential layout. (This can be checked using ILDASM.EXE.) When in doubt, always mark a value type to be used from unmanaged code with the StructLayoutAttribute custom attribute and its LayoutKind.Sequential value.

Also, Variant is a keyword in Visual Basic .NET, so you’d want to define the structure with a different name (such as RawVariant) if using it in VB .NET.

Lines 16–45 define the IRecordInfo COM interface manually, necessary for the kind of raw interaction with VT_RECORD VARIANTs that the rest of the listing does. Instructions for manually defining COM interfaces are given in Chapter 21, “Manually Defining COM Types in Source Code,” so the definition won’t be explained here.

Lines 49–51 define a PInvoke signature for the GetRecordInfoFromTypeInfo method exposed by OLEAUT32.DLL. Lines 57–74 prepare the Variant object to be passed to the FillRect method. Although the COM method doesn’t actually do anything with the incoming instance, it must appear to be a valid VT_RECORD VARIANT. First, Lines 60 and 61 use the GetRecordInfoFromTypeInfo to get an OLE Automation-supplied implementation of IRecordInfo. This method requires an ITypeInfo pointer for a structure, which is obtained using Marshal.GetITypeInfoFromType. To convert the returned IntPtr to an IRecordInfo interface pointer, Line 62 calls Marshal.GetObjectForIUnknown. This method treats the passed-in IntPtr type as an IUnknown pointer, and constructs an RCW for the object. When the returned Object type is cast to IRecordInfo, a QueryInterface call is performed by the RCW, as usual.

Now that we’ve obtained an IRecordInfo instance, Lines 65–67 initialize the Variant type appropriately. Its type, stored in the vt field, is set to VT_RECORD using the VarEnum enumeration defined in System.Runtime.InteropServices. Its pRecInfo field is set to the IntPtr representing a pointer to the IRecordInfo implementation, and its pvRecord field is set to a new instance of RECT obtained by calling the IRecordInfo implementation’s RecordCreate method.

Tip

If you want to pass a structure to COM initialized with certain data, this could be done between Lines 67 and 69 by calling the IRecordInfo instance’s PutField and/or PutFieldNoCopy methods as appropriate. In Listing 6.1, no fields are set because the example COM object doesn’t care about the incoming VARIANT data.

Line 74 pins the boxed Variant only to get the address of the value type to pass as the IntPtr parameter to FillRect. As discussed in the previous section, this could have been accomplished by allocating the memory for the Variant instance on a non-GC heap using Marshal.AllocCoTaskMem or Marshal.AllocHGlobal, or by taking advantage of C# unsafe code and doing the following inside an unsafe block:

IntPtr addrOfVariant = new IntPtr(&v);

Line 77 calls the COM object’s method with the address of the Variant, so Line 81 unpins the object by calling the GCHandle’s Free method. Notice that the unpinning is done as soon as possible so the impact to the garbage collector is minimized. Because the FillRect method fills the RECT instance returned via the by-reference parameter with data, Lines 89–90 print its contents. But first the IntPtr parameter needs to be converted to a RECT type; the opposite of the conversion done before the call. This is done by the VT_RECORDToStructure method defined in Lines 93–112, a general-purpose method that works for any type of struct inside a VT_RECORD Variant. This can be done using the power of reflection plus the reflection-like methods in IRecordInfo.

Line 95 checks to make sure that the incoming Variant does indeed have the VT_RECORD type, then Lines 98–99 extract the IRecordInfo instance from the Variant. Lines 101–105 do one last sanity check, making sure that the name of the struct represented by the IRecordInfo and the name of the .NET type match. Finally, Lines 108–111 enumerate through the fields of the boxed value type and use FieldInfo.SetValue to set each field to the value obtained by IRecordInfo.GetField.

SAFEARRAYs Containing VARIANTs Containing Structures

You can run into the same marshaler limitations when a COM object uses SAFEARRAYs of VARIANTs for which the VARIANT type must be VT_RECORD. If treated as a .NET array of System.Object types, attempting to pass an array of boxed value types suffers from the same ArgumentException described in the previous section. One could imagine defining and using a raw SAFEARRAY structure in managed code just as we used a raw VARIANT structure, such as defining the following in C#:

public struct SafeArray
{
  ushort cDims;
  ushort fFeatures;
  uint cbElements;
  uint cLocks;
  IntPtr pvData;
  IntPtr rgsabound;
}

But this often isn’t necessary if the COM object passes or returns a pointer to a SAFEARRAY, because the IntPtr value representing a pointer to a SAFEARRAY can be passed directly to several SAFEARRAY APIs provided by OLE Automation and obtainable via PInvoke. Listing 6.2 shows an example of using these APIs to print the contents of a SAFEARRAY containing VARIANTs with type VT_RECORD that each contain a POINT structure. In this example, the COM object has a method called ReturnPtrToSafeArray that returns a pointer to the SAFEARRAY. The source code for this method is available from the book’s Web site. The Variant type and VT_RECORDToStructure method from Listing 6.1 are referenced here.

Listing 6.2. Performing Manual SAFEARRAY Marshaling in C#

Image

Image

Tip

Listings 6.1 and 6.2 use COM methods that pass or return a pointer to a VARIANT or a pointer to a SAFEARRAY. Often, these types are passed directly rather than with a level of indirection. In these cases, the parameters would have to be changed to be the corresponding structure types rather than System.IntPtr. Chapter 7 describes how to change a parameter such as a by-value Object inside an Interop Assembly to a Variant value type like the one defined in Listing 6.1.

As mentioned in Chapter 5, “Responding to COM Events,” late binding to methods with structure parameters is problematic because the nature of late binding means that all parameters are automatically passed in VARIANTs. Structure parameters must be passed as VT_RECORD VARIANTs when late binding, causing the same failures from the Interop Marshaler. If the parameters in questions are pointers to structures, you can change them to be IntPtr types and use the same kind of techniques demonstrated in this chapter.

For dispinterfaces, which by definition only support late binding, the situation is even worse. In managed code, you cannot invoke members that use by-value UDT parameters directly on dispinterfaces. If such parameters are not pointers but instead by-value structs, there’s no way to change the .NET definition of a dispinterface signature to make it work with the built-in late binding support. That’s because a structure of some sort must be passed to the member (except for the unlikely case in which the struct is small enough that a primitive type like int or long could be passed in its place). One alternative would be to manually write a .NET IDispatch definition that uses raw VARIANT types as in Listing 6.1, cast the object implementing the dispinterface to IDispatch, then call IDispatch.Invoke yourself with raw VARIANT structures that you fill appropriately. In this case, you must perform all the same work that you would have to do in unmanaged C++ code.

Threading and Apartments

COM’s threading models and various apartment types have always been a source of great confusion. Fortunately, .NET no longer has a notion of apartments or different kinds of threads. (.NET has its own new and often-confusing concepts such as application domains, but that’s another story.) Threading is yet another area in which COM Interoperability does a lot of work to bridge two disparate models. In this section, we’ll look at the following topics in threading:

• Threading in COM versus threading in .NET

• Choosing your apartment state in a .NET application

• Callbacks from a COM object to a .NET object

Threading in COM Versus Threading in .NET

In .NET, objects can be accessed by any number of threads at any time. This means that class authors must take on the responsibility of protecting internal state from getting corrupted due to concurrent access and also ensure that all methods are reentrant. This is known as being threadsafe. Or, class authors should document when their classes are not threadsafe so clients don’t attempt to use the same instance from multiple threads. Examples of this can be seen throughout the .NET Framework. For instance, Windows Forms classes are not threadsafe, so they provide a mechanism (Control.Invoke) to facilitate using the classes from multiple threads. As another example, System.Collection.Hashtable supports multiple concurrent readers but only one writer at a time for performance reasons. But if you want to support multiple concurrent writers, you can call its Synchronized method to obtain a threadsafe wrapper to the original Hashtable.

The .NET Framework has several mechanisms to manage concurrency, such as the Monitor and Mutex classes in the System.Threading namespace. (C#’s lock statement implicitly uses Monitor.Enter and Monitor.Exit.) The System.Runtime.Remoting.Contexts class has a SynchronizationAttribute custom attribute for Remoting purposes. In addition, any method can be marked as requiring synchronization using the little-known System.Runtime.CompilerServices.MethodImplAttribute custom attribute with the MethodImplOptions.Synchronized value, as shown in the previous chapter.

Writing threadsafe code is not an easy task, so COM has the notion of apartments to protect programs that rely on thread affinity and to isolate programmers who don’t want to worry about concurrency issues. An apartment is a logical process that contains one or more running threads, used to group together objects that have the same threading requirements. Every COM object lives inside exactly one apartment.

There are three kinds of apartments:

• Single-Threaded Apartments (STAs) only contain one thread and zero or more COM objects. Any process can contain zero or more STAs.

• Multi-Threaded Apartments (MTAs) contain one or more threads and zero or more COM objects. A process can only contain zero or one MTA.

• Neutral Apartments (NAs), introduced with COM+, contain no threads; just COM objects. A process can only contain zero or one NA.

Every thread must “enter” an apartment before instantiating or using COM objects. This occurs when a program initializes the COM library by calling CoInitialize or CoInitializeEx. Calling CoInitialize(NULL) or CoInitializeEx(NULL, COINIT_APARTMENT_THREADED) means that the current thread enters an STA (making it an STA thread), whereas calling CoInitializeEx(NULL, COINIT_MULTITHREADED) means that the current thread enters an MTA (making it an MTA thread). There’s no such thing as an “NA thread” because NAs never contain threads.

COM objects that are in-process servers advertise their threading model requirements using the Windows Registry. The ThreadingModel registry value under the HKEY_CLASSES_ROOTCLSID{CLSID}InProcServer32 key contains a string that can be set to:

Apartment—Indicates that the COM object must be created in an STA.

Free—Indicates that the COM object must be created in an MTA.

Both—Indicates that the COM object can be created in any type of apartment: an STA, MTA, or NA. (The value is called Both because it existed before NAs were invented. As if COM threading models weren’t confusing enough already!)

Neutral—Indicates that the COM object must be created in an NA.

Single—Indicates that the COM object must be created in the “main STA,” the first STA created in the process. This exists only for legacy reasons, from the time before COM had threading models and each process could only have one thread. It’s also the default behavior if no ThreadingModel value exists.

With this information, CoCreateInstance instantiates the COM objects in the appropriate apartment.

So what’s the value in having all these rules with threading models and apartment types? As mentioned earlier, the main motivation is to have the notion of a single-threaded atmosphere (an STA) to enable rapid application development free from the complexities of multithreading. Many COM components live in STAs, including any authored in Visual Basic 6 (with the exception of ActiveX EXE projects) or with Microsoft Foundation Classes (MFC). By default, COM objects created using the Visual C++ ATL wizard live in STAs as well. Such components are called apartment-threaded components, and are the easiest kind to write because they live in a sheltered world.

A COM object living in an STA (which has the Single or Apartment threading model) can only be called by the thread that created the object. To make such an object usable from COM objects living in an MTA or other STAs, any calls from different threads are synchronized with a queue used by the original thread. On Windows, COM uses the Windows message queue. Therefore, any STA thread must have a Windows message loop.

A COM object that lives in an MTA, on the other hand, can be called by multiple threads concurrently just like .NET objects. Such components are called free-threaded components and must handle synchronization themselves. Although more difficult to write correctly, free-threaded components can achieve high performance because they can be called from multiple threads without incurring the cost of COM marshaling. Any calls crossing from one apartment to another are more expensive than intra-apartment calls due to proxy/stub marshaling and a thread switch, so COM clients should attempt to live in the same apartment as the COM objects they use whenever possible. Objects registered as Both make this task easier, as the in-process server always gets created in the same apartment as the creator.

The neutral apartment was introduced to help avoid performance problems when cross-apartment calls are required. Because an NA has no threads, COM objects living an NA (also known as neutral components) are always called on the same thread doing the calling, whether an MTA thread or an STA thread. The result is a lightweight proxy that does not require a thread switch. Neutral components must still worry about synchronization just like free-threaded components.

Choosing Your Apartment State in a .NET Application

When using COM components in a .NET client application, the CLR must initialize the COM library at some point before the first COM object is instantiated. By default, the CLR calls CoInitializeEx as follows:

CoInitializeEx(0, COINIT_MULTITHREADED)

This means that .NET threads are MTA threads by default. This has the following implications for using apartment-threaded COM components from managed code:

• Some COM objects requiring an STA may simply be unusable if you attempt to call them from an MTA thread. For example, Microsoft Collaboration Data Objects (CDO) has a Session class that returns an error in such a situation. Deadlocks are also possible in this scenario, depending on the two components.

• Because the COM object would be created in a separate apartment, COM attempts to set up a proxy to marshal the calls from one thread to another. Even if a proxy can be created (which can sometimes require extra registration of a proxy/stub DLL), performance suffers due to the extra work involved.

Therefore, it’s desirable to have control over the apartment that a .NET component resides in. This can be done by putting one of two custom attributes on your .NET application’s entry point (the Main method):

System.STAThreadAttribute—Instructs the CLR to initialize COM with CoInitializeEx(NULL, COINIT_APARTMENT_THREADED), so the current thread is an STA thread.

System.MTAThreadAttribute—Instructs the CLR to initialize COM with CoInitializeEx(NULL, COINIT_MULTITHREADED), so the current thread is an MTA thread.

Caution

Be mindful of the threading requirements of any COM object you use in managed code so you can avoid cross-apartment COM marshaling. Cross-apartment marshaling may not work if the COM interfaces you’re calling on don’t have a proxy/stub marshaler registered, or you may experience poor performance due to the extra marshaling.

This behavior is not specific to .NET but is true for plain COM clients and servers as well. The difference is that you may be used to developing in an STA world (using Visual Basic 6, MFC, the default ATL behavior, and so on) but switching to a language like C# places you in an MTA world by default.

Failure due to unmarshalable interfaces manifests as an InvalidCastException, but other kinds of failures can result from incompatible threading models, such as a COMException with the message, “An internal error occurred in a remote procedure call (RPC).” When using ActiveX controls wrapped as Windows Forms controls, a System.Threading.ThreadStateException is thrown during object construction if the current thread is not an STA thread.

Using STAThreadAttribute looks like the following in C#:

[STAThread]
public static void Main(string [] args)
{
  ...
}

The CLR always waits to initialize COM until it’s needed, so in theory this custom attribute has no effect if COM Interoperability is never used. It turns out, however, that the CLR internally uses COM when starting up so COM is always initialized by the time the first line of code inside Main is run.

Tip

The Visual Basic .NET compiler automatically emits the STAThreadAttribute on an application’s entry point (Sub Main) to more closely match the apartment-threaded nature of Visual Basic 6. To override this behavior, you can use MTAThreadAttribute. In any other language, MTA behavior is the default, overridable using STAThreadAttribute. Visual Studio .NET marks the Main method with STAThreadAttribute by default in Visual C# Windows Application and Console Application projects.

In an ASP.NET page, you can accomplish the same thing as using STAThreadAttribute in a standalone application by using the following directive at the top of the .aspx page:

<%@ Page aspcompat=true %>

This is known as turning on ASP Compatibility Mode. As with any .NET applications, the threads used by ASP.NET have an MTA apartment state by default. Using ASP Compatibility Mode forces the page to run on an STA thread instead.

ASP.NET’s built-in instantiation mechanisms—Server.CreateObject, Server.CreateObjectFromClsid, and the <object> tag (with runat=server)—check to see if you’re instantiating an apartment-threaded COM object on an ASP.NET page that isn’t running in ASP Compatibility Mode, but only if you’re creating the object via ProgID or CLSID. If the object’s threading requirements aren’t compatible with the thread’s apartment state, instantiation fails. ASP.NET makes this check for three reasons:

• Almost all COM objects used in ASP pages are apartment-threaded.

• Using an apartment-threaded COM component can be dangerous or poor-performing without aspcompat=true.

• A COM component’s threading requirements aren’t obvious unless you check the registry, and ASP.NET developers are unlikely to do so.

The non-ASP.NET-specific instantiation mechanisms do not enforce such a requirement.

Caution

The aspcompat=true directive is supported only in ASP.NET Web pages (.aspx files), not in XML Web services (.asmx files).

What if you’re writing a .NET component that is used by a .NET application whose entry point you don’t control? You can still control the initialization of COM, but only on a thread that hasn’t yet been initialized. This can be done by obtaining the current thread instance (assuming the current thread has not been initialized) and setting its ApartmentState property. The Thread.ApartmentState property can be set to one of the following values in the System.Threading.ApartmentState enumeration:

STA—An STA thread, running inside a single-threaded apartment.

MTA—An MTA thread, running inside a multithreaded apartment.

The enumeration also has an Unknown value that represents an apartment state has not yet been chosen, which means that COM has not yet been initialized. Because COM gets initialized during program startup, this state is never seen on the main thread (nor can it be set programmatically).

Once a thread’s ApartmentState has been chosen, you cannot change it. Attempting to set a thread’s ApartmentState property never throws an exception, but attempting to change it from a state other than Unknown silently fails.

In order to set a thread’s ApartmentState property successfully, you must do it on a new thread because the main thread’s apartment state was chosen when COM gets initialized during process startup. This is demonstrated by the odd-behaving C# code:

using System;
using System.Threading;

public class ThreadingModels
{
  public static void Main()
  {
    Thread.CurrentThread.ApartmentState = ApartmentState.STA;
    // The next line prints "MTA"!
    Console.WriteLine(Thread.CurrentThread.ApartmentState);
  }
}

Image

Because the default MTA behavior was chosen during startup, setting ApartmentState to an STA thread has no effect. The code should have marked Main with STAThreadAttribute instead. The implicit STAThreadAttribute emitted by the Visual Basic .NET compiler causes the reverse odd behavior:

Imports System
Imports System.Threading

Public Module ThreadingModels
  Public Sub Main()
    Thread.CurrentThread.ApartmentState = ApartmentState.MTA
    ' The next line prints "STA"!
    Console.WriteLine(Thread.CurrentThread.ApartmentState.ToString())
  End Sub
End Module

Image

The following code demonstrates the correct way to set the ApartmentState property, by using a brand new thread that has not been initialized:

using System.Threading;

public class ThreadingModels
{
  public void CalledOnMTAThread()
  {
    Thread t = new Thread(new ThreadStart(CalledOnSTAThread));
    t.ApartmentState = ApartmentState.STA;
    t.Start();
    ...
  }

  private void CalledOnSTAThread()
  {
    ...
  }
}

Callbacks from a COM Object to a .NET Object

Callbacks from a COM object in .NET applications are a source of much confusion because the behavior does not match the rules of COM. In the previous section, you saw that when managed code calls members of a COM object, the same rules of threads and apartments apply. If the COM object lives in an STA, any calls from MTA threads are marshaled appropriately so the COM object remains in its world of thread affinity.

But, in the other direction, no such thread or context switch occurs. Even if you’ve marked your .NET object as created on an STA thread, COM components (or .NET components, for that matter) are able to call you on multiple threads. A managed thread’s apartment state only affects the instantiation of COM objects, not of .NET objects. If you’re expecting a managed callback method to be called on a certain thread, you’re responsible for getting to the right thread yourself before calling the code with the thread affinity.

Unlike most COM objects, there is no way to force .NET objects to live in an STA. .NET objects are always exposed to COM in a context-agile fashion, effectively as if they aggregate the free-threaded marshaler (FTM). This is because a design goal of .NET is to avoid thread affinity wherever possible. The result is that no .NET components can assume that they will only be called on one thread.

Tip

There’s an exception to the rule that all .NET components may be called by multiple threads. The System.EnterpriseServices.ServicedComponent class and any subclasses strictly act as COM objects. This means that if a ServicedComponent or derived type is created on an STA thread, all calls from COM objects and even .NET objects are always marshaled to the original thread.

Listing 6.3 shows Visual Basic .NET code using DirectX that receives a callback on a separate MTA thread despite the fact that the main thread is implicitly an STA thread. In this case, attempting to call members of a COM object created on the main thread fails because the DirectMusicPerformance interface being called upon cannot be marshaled across apartment boundaries (due to no proxy/stub marshaler being registered for the interface). The equivalent code works without errors in Visual Basic 6 because all code is executed on the same STA thread.

Listing 6.3. Visual Basic .NET Code with COM Callbacks on a Separate Thread

Image

Image

To compile this listing, you must reference System.Windows.Forms.dll, System.dll, and DxVBLib.dll, an Interop Assembly for the DirectX 7 for Visual Basic Type Library (DX7VB.DLL) generated by the type library importer. This program requires two files from the DirectX SDK, which can be downloaded from MSDN Online, to be copied to the same directory. These are heartland.sty and heartland2.sgt, two files that enable music to be played. (Any pair of .sty and .sgt files will do, as long as you change the filenames in the listing.) They can be located with the samples installed by the SDK, in the folder containing media such as pictures and sounds.

This code represents a blank Windows Form that uses DirectX (specifically, DirectMusic) to play a song. Every time the song’s measure or beat changes, the form receives a callback. The callback interface is called DirectXEvent, and its single method is called DXCallback. Lines 12–23 implement the DXCallback method, but first we’ll look at the form’s constructor in Lines 26–48.

Line 29 instantiates the DirectX7 object, which serves like a class factory for the many objects available from DirectX. Lines 32–38 initialize the music using “segment” and “style” files available from the DirectX SDK. Line 32 is important because it marks the creation of the performance object, an object that implements the DirectMusicPerformance interface. The object returned is apartment-threaded, and because the main thread is an STA thread, it enters the current apartment.

Lines 41–44 setup the callback by, among other things, passing a reference to itself to DirectX7.CreateEvent, which expects an object implementing DirectXEvent. The DirectMusicPerformance object supports several kinds of callbacks, and you can select which ones you’re interested in by calling AddNotificationType with appropriate enumeration values. Line 42 specifies that we’re only interested in callbacks when the music’s measure or beat changes. Line 47 begins playing the music asynchronously. Control returns immediately to the main thread, at which point the Windows message loop idly runs inside Application.Run on Line 51.

When the callbacks occur on a new thread, Line 19 attempts to call a member of the performance COM object, which causes the following exception:

System.InvalidCastException: QueryInterface for interface DxVBLib.DirectMusicPerformance failed.
   at DxVBLib.DirectMusicPerformance.GetNotificationPMSG (DMUS_NOTIFICATION_PMSG& message)
   at Form1.DXCallback(Int32 eventid)

You could manually register an appropriate proxy/stub marshaler for the DirectMusicPerformance interface to make Listing 6.3 run correctly, but Listing 6.4 will demonstrate a Visual Basic .NET source solution instead.

Notice that the implementation of DXCallback catches the exception and displays a message box with its contents. Had it not done that, the call to GetNotificationPMSG would have appeared to silently fail. That’s because the InvalidCastException thrown would propagate to DXCallback’s caller, and that caller is some COM object inside DirectX. This COM object happens to ignore any failure HRESULTs returned by DXCallback implementations, so the exception gets silently “swallowed.” This is reasonable behavior for the DirectX COM object because it has no great way to report such an error.

Caution

Be careful about exceptions thrown from .NET methods called back from COM. If the method appears to silently fail, chances are the exception is being caught by the COM Interop layer and the resultant failure HRESULT is being ignored by the COM caller. This behavior is especially non-intuitive for Visual Basic 6 developers, because the VB6 runtime directly raises the error rather than letting it propagate up the call stack.

Fortunately, you can configure the Visual Studio .NET debugger to behave like the Visual Basic 6 IDE and trap the exception before letting it propagate to the caller. Follow these steps to make the debugger trap most .NET exceptions:

Select Debug, Exceptions... from the menu.

Highlight Common Language Runtime Exceptions and then select Break into the debugger inside the box labeled When the exception is thrown:.

Click OK.

The subset of .NET exceptions to which these changes apply depends on the settings of the subnodes of Common Language Runtime Exceptions. The debugger behavior for each exception type can be configured individually or configured to inherit the parent node settings.

Listing 6.4 updates the VB .NET code from Listing 6.3 with some threading constructs to handle the multi-apartment issues with the unmarshalable interface. The idea here is to use the System.Threading.ManualResetEvent class so the callback thread can signal the thread that created the performance object when callbacks occur. The original thread can then execute the original callback implementation, then wait for the next signal. An easier hack would have been to derive the Form1 class from System.EnterpriseServices.ServicedComponent to inherit its COM-like threading behavior, but then the class wouldn’t be able to derive from System.Windows.Form, because .NET doesn’t support multiple implementation inheritance.

Listing 6.4. Visual Basic .NET Code to Make COM Callbacks Run on the Right Thread

Image

Image

Image

Image

This listing begins by adding the System.Threading namespace to the list of imported namespaces. This time, in Form1’s constructor (Lines 25–30), a second thread is started to create all the COM objects and the main thread waits inside Application.Run on Line 74. This is done because the thread that creates the COM object is going to have to wait for callback notifications in a loop. If the main UI thread were the one waiting, the Windows Form would be non-responsive to user input. So, the second thread is started in Line 29, which executes the Run method. The second thread’s apartment state needs to be set to STA in Line 28 for the sake of instantiating the apartment-threaded DirectMusicPerformance object in Line 38, otherwise the CLR’s internal QueryInterface call would fail. Unlike the main thread in Visual Basic .NET programs, freshly created threads always have an uninitialized apartment state that becomes ApartmentState.MTA unless you explicitly set it otherwise.

The implementation of Run is the same as the previous listing’s constructor until Line 53, where the ManualResetEvent instance is initialized to an unsignaled state. After kicking off the playing of music in Line 56, this second thread enters an infinite loop. Inside this loop, it waits for the ManualResetEvent instance to be signaled, calls the SameThreadCallback method, then resets the ManualResetEvent instance so it waits again at the beginning of the next loop iteration. SameThreadCallback is defined in Lines 66–71, and does the work that was originally intended for the DXCallback method. To prevent this thread from keeping the process running infinitely, Line 75 aborts it once the Windows Form has been closed (causing control to leave the Application.Run method).

The DXCallback method in Lines 15–22 now must only call ManualResetEvent.Set to signal to the correct thread that a callback has occurred.

Caution

You can’t escape multithreading in the .NET Framework. For example, a class’s finalizer is always invoked by the CLR on a separate finalizer thread. If you’re called on a separate thread, you better handle it appropriately. This can be a huge issue when attempting to migrate Visual Basic 6 code to Visual Basic .NET when using COM interfaces that don’t support cross-context marshaling. As Listings 6.3 and 6.4 demonstrate, the code required can be substantially different.

Troubleshooting an InvalidCastException

On the surface, there are two main causes for an InvalidCastException thrown when using COM objects:

QueryInterface failure

• Casting to an RCW class

Both of these causes, and their underlying causes, are discussed in the following sections. These discussions exclude the obvious causes for an InvalidCastException, such as casting an object to an interface that it simply does not implement.

QueryInterface Failure

An InvalidCastException caused by a QueryInterface failure can happen when you’re casting an RCW to a COM interface, or when you’re simply trying to call a method on an RCW with no casting involved. It often seems weird when the simple act of calling a COM object causes an InvalidCastException, but it happens because the CLR calls QueryInterface the first time you attempt to call a method on an interface that doesn’t match the type of the object you’re calling the method on. For example, calling a method directly on a strongly typed RCW class (one with a Class suffix) provokes a QueryInterface call:

// Calls QueryInterface for only IUnknown:
WebBrowserClass wb = new WebBrowserClass();
// Calls QueryInterface for IWebBrowser2 before calling the method:
wb.GoHome();

Declaring types as interfaces causes QueryInterface calls to be done earlier:

// Calls QueryInterface for IWebBrowser2:
IWebBrowser2 wb = new WebBrowserClass();
// Simply calls the method:
wb.GoHome();

The CLR sometimes calls QueryInterface in other non-casting situations as well, such as if a COM object is used from a different STA thread or context.

Any InvalidCastException caused by a failed QueryInterface call has the following message:

QueryInterface for interface InterfaceName failed.

It can often be difficult to determine the cause of a QueryInterface failure, but there are four common sources that people run into:

Incompatible apartments and lack of marshaling support

• Non-reflexive IUnknown implementations

• Type library errors

• Recompiling VB6 projects without binary compatibility

Incompatible Apartments and Lack of Marshaling Support

If your managed code is running inside in a different apartment than the failing COM object’s apartment, the interface target type must be able to be marshaled from one apartment to the other. As discussed in the previous section, this is COM marshaling—not Interop marshaling—and is no different from when no managed code is involved. If the target interface is unmarshalable, then a cross-apartment QueryInterface call always fails. This problem shows up often in .NET applications because several COM objects require running in an STA, whereas .NET threads are MTA threads by default. We also saw that this problem can occur due to callbacks on new threads, regardless of the apartment state.

The simplest solution is to change the threading behavior of your managed code to run in an STA thread by default, as discussed in the previous section. When both components are in the same apartment, no COM marshaling is required to make the QueryInterface call succeed.

Another solution is to make the interface marshalable from one apartment to another. This can be done by registering a proxy/stub DLL using the Windows Registry value HKEY_CLASSES_ROOTInterface{IID}ProxyStubClsid32 set to the proxy/stub marshaler’s CLSID. If all of an interface’s members use OLE Automation-compatible parameters, and if the interface is marked with the IDL oleautomation attribute, then its type library can usually be registered to cause the standard OLE Automation marshaler to be registered for the interface. This is called type library marshaling, because the OLE Automation marshaler uses the information in the interface’s type library definition to determine how to marshal the interface. If the interface is not marked with the oleautomation attribute but its members use only OLE Automation-compatible parameters, you could manually register OLE Automation’s type library marshaler (with CLSID 00000320-0000-0000-C000-000000000046) and hope for the best.

For Visual Basic 6 and ATL components, running REGSVR32.EXE on the DLL usually registers the embedded type library in addition to the standard COM registration. Opening a type library with OLEVIEW.EXE also registers the type library.

Caution

When using ATL COM components, you may sometimes be surprised to learn that interfaces that should have a registered proxy/stub marshaler (generated by MIDL) do not. When using the Visual C++ 6 ATL COM Wizard and selecting Allow merging of proxy/stub code, the default behavior does not properly register the proxy/stub DLL required for cross-context COM marshaling. The generated dlldatax.c file does not get built with the project’s output. To fix this, do the following:

Right-click on the dlldatax.c file in the FileView window and select Settings....

On the General tab, uncheck Exclude file from build.

On the C/C++ tab, choose the Precompiled Headers category and select Not using precompiled headers.

Click OK to accept these changes.

Regardless of whether you make the COM interface marshalable across contexts, marking the .NET component with the same threading model as the COM component is a good idea, because it provides the best performance by avoiding this extra marshaling. If you can’t change the threading model of your .NET component (either because you don’t control the entry point or perhaps because you use Windows Forms controls) and if registering a proxy/stub interface marshaler is too difficult, consider the technique of starting a new thread to call and use a COM component, as shown in the previous section.

Non-Reflexive IUnknown Implementations

The rules of COM dictate that any IUnknown.QueryInterface implementation must be transitive, symmetric, and reflexive. However, errors in COM objects can occasionally occur that breaks some of these rules. The reflexive behavior is critical for COM Interoperability because the CLR calls QueryInterface on any incoming COM object before wrapping it in an RCW to ensure that it supports the interface that the signature claims the instance supports. For example, suppose a COM method signature is defined as:

IMoniker GiveMeAMoniker();

The object returned by GiveMeAMoniker must respond successfully to a QueryInterface call for IMoniker; otherwise, an InvalidCastException occurs from the failed QueryInterface call. This check is done by the CLR to avoid subtle incorrect run-time behavior caused by incorrect signatures or COM objects that don’t follow the rules of COM. This strictness is especially handy for catching errors when manually defining your own type information for COM components, described in Chapter 21.

Caution

Visual Basic 6 does not check that an object implements the interface given in its parameter/return type declaration, which is why so many of these types of problems surface for the first time with .NET. The CLR is a stricter COM client than Visual Basic 6, and isn’t as forgiving when a COM object breaks the rules of COM. The most vulnerable COM objects are ones authored in C++ (because you have more power to make mistakes) and primarily used in Visual Basic 6 (because it’s so forgiving).

This problem used to plague the NameList type in SQL Distributed Management Objects (SQLDMO), a part of Microsoft SQL Server 2000, before its first service pack was released in July 2001. Without the service pack, the following C# code fails with an InvalidCastException, because querying the returned object for the NameList interface (the signature’s return type) fails:

SQLDMO.Application app = new SQLDMO.Application();
SQLDMO.NameList list = app.ListAvailableSQLServers();

As with any QueryInterface failure, you should be able to reproduce it in unmanaged C++:

SQLDMO::NameList* list1;
SQLDMO::NameList* list2;
// The call works fine...
hr = app->ListAvailableSQLServers(&namelist);
...
// ...but the QueryInterface call fails:
hr = list1->QueryInterface(__uuidof(SQLDMO::NameList), (void**)list2);

Tip

Attempting to reproduce a QueryInterface failure in unmanaged C++ code is a great way to isolate the problem and convince yourself that COM Interoperability is not (directly) responsible for the failure. Just be sure that you’re accurately imitating managed code interaction, such as initializing COM with the appropriate threading model.

Besides waiting for a fix to the COM object, there’s not much you could do to fix this type of problem unless you can find another interface on which to invoke members. If you changed the metadata signature’s return type to represent IUnknown rather than a specific interface (which would be System.Object marked with MarshalAs(UnmanagedType.IUnknown)), then the call would succeed, assuming that the object responds successfully to a QueryInterface call to IUnknown. But to call the members of the returned object, you’d need to late bind using Type.InvokeMember, and this would only work if the returned object happened to implement IDispatch. Changing a signature in this way is discussed in Chapter 7.

Type Library Errors

The same sort of failed QueryInterface problems can happen when a COM component’s type library simply contains errors. The DirectX for Visual Basic 8.0 type library had a few such problems in version 8.0a (fixed in later releases), which caused unexpected InvalidCastExceptions to be thrown in managed code. For example, the Direct3DDevice8 interface was given the following IID in its type library definition:

7385E4DF-8FE8-41D5-86B6-D7B48547B6CF

However, the “same” interface in the C++ header file (IDirect3DDevice8 in d3d8.h) has an IID that differs by one digit:

7385E5DF-8FE8-41D5-86B6-D7B48547B6CF

The object can be queried successfully for the interface represented by the second GUID, but not the first one. Therefore, when the CLR asks the COM object whether it implements 7385E4DF-8FE8-41D5-86B6-D7B48547B6CF, it returns E_NOINTERFACE because it doesn’t recognize the IID. If the QueryInterface call instead passed the correct 7385E5DF-8FE8-41D5-86B6-D7B48547B6CF value, the COM object would return S_OK and everything would proceed as expected.

Of course, the incorrect IID in the type library does not affect Visual Basic 6 programs (the intended audience of the type library). The next chapter shows how to change the generated Interop Assembly’s IID for this interface to work around such a problem.

Recompiling VB6 Projects Without Binary Compatibility

Because there’s no way to control the GUIDs assigned to classes and interfaces generated by the Visual Basic 6 compiler, the VB6 IDE provides the option to compile a project with binary compatibility. This works by telling VB6 the file containing a previously compiled type library with which you want your recompiled component to be compatible. The classes and interfaces generated on recompilation are given the same CLSIDs and IIDs as they had in the previous version. This is critical if you don’t want to break COM clients of your component that don’t have the luxury of being recompiled with your new type library.

If you recompile a Visual Basic 6 ActiveX DLL project without using binary compatibility, existing COM clients and .NET clients alike often experience failed QueryInterface calls. If the clients attempt to instantiate one of your VB6 classes, the CLSID change might not be noticeable if the old CLSID is still registered. But attempting to make the same QueryInterface call for the VB6 class interface will fail due to the changed IID.

If you can’t revert your VB6 component back to its old CLSIDs and IIDs, you’ll need to re-import an Interop Assembly (assuming that .NET clients use it) and recompile any .NET clients with the new Interop Assembly.

Casting to an RCW Class

Casting a COM object (an RCW) to a class type is really no different from casting a .NET object to a class type, in that the class’s metadata is the sole determining factor of whether or not the cast succeeds. If the object’s class type—the type you’d get by calling its GetType method—either is the destination type or derives from the destination type, the cast succeeds. Otherwise, the cast fails with an InvalidCastException and the generic message “Specified cast is not valid.”

The reason that casting COM objects to class types can fail when you don’t expect it to is that it’s not always obvious what the type of the source instance is. When instantiating the object yourself using your language’s new keyword, the type of the instance is the strongly-typed RCW class. But when an instance of a COM object is returned to you, via a method’s return type or a by-reference parameter, its type may not be what you expect. The reason for this is that COM objects are always passed/returned as interface pointers. In “pure COM,” you don’t know exactly what the type of the object you’re dealing with is; you can only discover what interfaces it supports via QueryInterface. This excludes COM objects that implement IProvideClassInfo or some other custom interface that enables a user to retrieve a CLSID and therefore discover the class type.

If the CLR cannot determine the type of a COM object, then it has no choice but to use an RCW of the generic System.__ComObject type (an internal type inside the mscorlib assembly) when presenting a COM object to managed code. This __ComObject type is the source of casting confusion, because the programmer expected the type to be a strongly-typed class that the instance really is. The problem is that just because you know the real type of a COM object doesn’t mean that the CLR has the same semantic information (or metadata for the desired type) to wrap it as such. Attempting to cast a System.__ComObject type to a class type will always fail unless the target type happens to be one of its public base classes—System.Object or System.MarshalByRefObject. This is because the metadata for __ComObject is static and has no relationship with any other classes.

The CLR uses several tricks, however, to avoid wrapping a COM object with the System.__ComObject type whenever possible. For one thing, if the type library importer replaced a parameter, field, or return type with a coclass interface type, then it has essentially made the assertion that the COM instance is a certain coclass. In this case, the CLR wraps the returned instance in a strongly-typed RCW using the .NET class type specified in the coclass interface’s CoClassAttribute custom attribute unless the same COM instance (with the same IUnknown pointer address) has previously been wrapped in a different type, likely System.__ComObject.

If the static type of a returned COM instance (the type in the metadata signature) is not a coclass interface, then the CLR attempts to discover the class type of the COM object by asking if it implements the IProvideClassInfo COM interface. This classic interface is implemented by many COM objects (including any authored in Visual Basic 6) to provide a means of discovering what class a COM object really is. This interface has a single method—GetClassInfo—that return’s the COM object’s CLSID. This isn’t quite enough information for the CLR, however, because it ultimately needs a .NET class type with which to wrap the COM object.

To get a .NET class type that corresponds to a CLSID, the CLR checks for the presence of two Windows Registry values under the HKEY_CLASSES_ROOTCLSID{clsid}InprocServer32 key:

Assembly="AssemblyName, Version=..., Culture=..., PublicKeyToken=..."
Class="NamespaceQualifiedClassName"

With this information, the CLR can wrap the COM object with the class specified in the registry, as long as it can load the specified assembly. This means that the assembly mentioned in the registry must be in the Global Assembly Cache, the application directory, or anywhere such that calling Assembly.Load with the Assembly value’s string would succeed (because that’s essentially what the runtime is doing). Or, there could be a third value alongside the other two, pointing to the location of the assembly; for example:

CodeBase=file:///C:/MyApplication/MyAssembly.dll

If this value is present, then the assembly will be loaded from the specified location (essentially like Assembly.LoadFrom), but only if it can’t be found through the normal loading procedure.

Rest assured that you don’t have to manually add these registry keys to enable this behavior. The Assembly Registration utility in the .NET Framework SDK (REGASM.EXE) adds the Assembly and Class entries for you when it’s run on an Interop Assembly. It also has a /codebase option for adding the CodeBase value for every class. This utility is usually used when exposing non-Interop assemblies to COM, covered in Chapter 8, “The Essentials for Using .NET Components from COM,” but it does the job here as well.

Therefore, COM objects returned to you whose static type is something other than a coclass interface (which would be either some other interface or System.Object), are wrapped with the strongly-typed RCW class if the object implements IProvideClassInfo and if the Interop Assembly containing the strongly-typed RCW class definition is registered appropriately. All Primary Interop Assemblies are usually registered using REGASM.EXE, so it’s likely that you’ll see this nicer wrapping behavior with types defined in PIAs. If, for some reason, the CLR is unable to load the assembly or find the class specified in the registry, it silently falls back to using the __ComObject wrapper type.

Caution

It was mentioned in Chapter 3, but it’s worth mentioning again: Never cast a COM object to a class type because you don’t know for sure when it will succeed and when it will fail. The Class suffix on imported classes is meant to discourage casting to such a type. You can access all of the objects functionality by casting to any implemented interface or any event interface instead.

Changing the Type of the RCW

If you’re not happy with the type that a COM object was wrapped with—you either got System.__ComObject when you desire a strongly-typed RCW, or got the wrong strongly-typed RCW due to an overzealous coclass interface transformation—you can change it using the Marshal.CreateWrapperOfType method in the System.Runtime.InteropServices namespace.

One major benefit of having a strongly-typed RCW instance rather than System.__ComObject (besides being able to invoke members on all of its interfaces without casting) is that you can use the entire reflection API to get the information you’d expect. With a System.__ComObject type, the only useful thing you can do is call Type.InvokeMember.

CreateWrapperOfType takes an existing RCW and the type you want it to be wrapped in and returns the new wrapper object. For example, if you have an instance of a COM object called oldObj with type OldWrapper, you can change its wrapper to the NewWrapper type as follows:

C#:

NewWrapper newObj = (NewWrapper)Marshal.CreateWrapperOfType(oldObj,
  typeof(NewWrapper))

Visual Basic .NET:

Dim newObj As NewWrapper = CType(Marshal.CreateWrapperOfType(oldObj, _
  GetType(NewWrapper)), NewWrapper)

C++:

NewWrapper* newObj = (NewWrapper*)Marshal::CreateWrapperOfType(oldObj,
  __typeof(NewWrapper))

The instance passed as the first parameter must be an RCW (which means it either is a System.__ComObject instance or derived from one), and the type passed as the second parameter must be an RCW type (which means it’s marked with the ComImportAttribute pseudo-custom attribute).

This method won’t let you convert one wrapper to another arbitrarily. Instead, it succeeds only if the source instance responds successfully when the CLR calls its QueryInterface implementation for every interface listed as implemented by the target type, with the exception of importer-generated event interfaces. (What makes these event interfaces exempt is that they’re marked as “COM-invisible,” a concept that’s introduced in Chapter 8.) Therefore, the CreateWrapperOfType algorithm applies the logic of “If it looks like a duck, walks like a duck, and smells like a duck, it must be a duck!” If the source object doesn’t pass this test, CreateWrapperOfType fails with an InvalidCastException and the message “Source object can not be converted to the destination type because it does not support all the required interfaces.”

The important drawback to using CreateWrapperOfType is that it loses the identity of the COM object. In other words, a second .NET instance—a second RCW—wraps the same COM object. Therefore, the following C# code:

NewWrapper newObj = (NewWrapper)Marshal.CreateWrapperOfType(oldObj,
  typeof(NewWrapper))

if (newObj == oldObj)
  Console.WriteLine("Objects are the same.");
else
  Console.WriteLine("Objects are not the same.");

would print “Objects are not the same.” This counters the normal behavior (described in Chapter 2) that every COM object has exactly one RCW.

Garbage Collection

Chapter 3 introduced the issue that the garbage-collected world of .NET exhibits different behavior than the reference counted world of COM. These differences can cause problems when using COM objects that must be released at specific points in a program. The need for this can be driven by performance concerns, or by semantics of an object model that require certain objects to be released at certain times. Marshal.ReleaseComObject was introduced as a way to deterministically release COM objects when called.

To understand how Marshal.ReleaseComObject works, you should understand that COM objects wrapped in RCWs are not reference-counted based on the number of .NET clients. Instead, any COM interface obtained through an RCW has its reference count incremented once. When the RCW is garbage collected, its finalizer calls IUnknown.Release on every cached interface pointer. Marshal.ReleaseComObject simply performs this release step when called rather than during the RCW’s finalization. Although the RCW might still be alive on a GC heap, attempting to use it throws a NullReferenceException. If multiple .NET clients use the same RCW and one of the clients calls ReleaseComObject, the RCW is no longer usable to any .NET clients.

There are two important things to remember when using Marshal.ReleaseComObject:

• Be aware of any intermediate RCWs created when using an RCW. You may have to call ReleaseComObject more than once to clean up everything without waiting for garbage collection.

• When calling ReleaseComObject on multiple objects that are dependent on each other, be sure to release them in the correct order. For example, ADO requires that you release a Recordset instance before releasing its corresponding Connection instance.

Usually calling ReleaseComObject once does the trick for releasing the wrapped COM object because an RCW’s reference count is typically set to 1. However, an RCW’s reference count gets incremented every time the same COM interface pointer is passed from unmanaged to managed code. It’s rare that this occurs more than once, but to be absolutely sure that calling ReleaseComObject releases the underlying COM object, you’d need to call ReleaseComObject in a loop until the returned reference count reaches zero.

Unlike typical reference count values returned by IUnknown.AddRef and IUnknown.Release, you can count on the value returned by ReleaseComObject to be reliable. Once the count reaches zero, the CLR is guaranteed to release all of the COM object’s interface pointers that the RCW holds onto.

Sometimes, due to custom marshaling, you may think you’re interacting with an RCW but you’re actually interacting with a special .NET object instead. This can happen when COM enumerator objects implementing IEnumVARIANT are exposed as .NET enumerator objects implementing IEnumerator. Calling ReleaseComObject on such an instance would not work, since it’s not an RCW. Chapter 20 demonstrates this and shows a solution.

Another popular option to release COM objects immediately is to do it the .NET way: calling System.GC.Collect followed by System.GC.WaitForPendingFinalizers. If you’ve finished using RCWs and they are eligible for collection, these two calls will not only release the wrapped COM objects but also the RCWs themselves. This is sometimes the only option if you’ve created RCWs that you don’t store in variables (as can happen if you’re calling an object’s property’s property). The problem with using GC.Collect is that it’s not always obvious when .NET objects are considered to be eligible for collection, as demonstrated by the following code snippets.

Let’s look at some simple demonstrations with Microsoft PowerPoint 2002, which gets installed with Microsoft Office XP, to better understand the effects of garbage collection when using COM objects. On a computer with Office XP (or just PowerPoint 2002), the upcoming four code snippets can be run by placing them inside the Main method in a C# file that looks like the following:

using System;
using PowerPoint;
using Microsoft.Office.Core;

public class Client
{
  public static void Main()
  {
    ...
  }
}

This can be compiled as follows using the C# command-line compiler:

csc client.cs /r:PowerPoint.dll /r:Office.dll

Or, to compile debuggable code, you can instead type:

csc client.cs /r:PowerPoint.dll /r:Office.dll /debug

The referenced DLLs (PowerPoint.dll and Office.dll) can be created by running TLBIMP.EXE on the MSPPT.OLB file found in the Program FilesMicrosoft OfficeOffice10 directory then placing them in the same directory as your C# client.

All of the following code snippets create a new PowerPoint Application object, set its Visible property so we can see the application after it’s created, then call Application.Quit to close the application. The PowerPoint application does not shut down if any of its objects are still being referenced by the client application, so this is an easy way to see when COM objects get released in a variety of scenarios.

The first code snippet causes PowerPoint to exit properly, so you only see a quick flash for the moment that the application is visible:

Application app = new Application();
app.Visible = MsoTriState.msoTrue;
app.Quit();
Console.WriteLine("Press Enter to exit...");
Console.ReadLine();

The call to Console.ReadLine is present simply to keep the managed client alive; it simulates other work done by the client application. This is necessary because the PowerPoint application always exits once the managed client process terminates because all COM references get released (unless the client is forcibly shut down with Ctrl+C).

Next, we add a single line to the previous code that stores the object returned by Application’s Presentations property in a local variable:

Application app = new Application();
Presentations pres = app.Presentations;
app.Visible = MsoTriState.msoTrue;
app.Quit();

Console.WriteLine("Press Enter to exit...");
Console.ReadLine();

This single line of code causes the PowerPoint application to remain open after Quit has been called! The COM object wrapped by the pres RCW has not been released because nothing provoked garbage collection. One way to get the expected behavior would be to force release by calling Marshal.ReleaseComObject:

Application app = new Application();
Presentations pres = app.Presentations;
app.Visible = MsoTriState.msoTrue;
app.Quit();

// This causes the PowerPoint application to exit:
System.Runtime.InteropServices.Marshal.ReleaseComObject(pres);

Console.WriteLine("Press Enter to exit...");
Console.ReadLine();

However, one might attempt to use GC.Collect instead to indirectly release the COM object as a side-effect of garbage collection. (This is desirable if other COM objects have been created that were not stored in variables.) This would look like the following:

Application app = new Application();
Presentations pres = app.Presentations;
app.Visible = MsoTriState.msoTrue;
app.Quit();

// Attempt to collect the pres instance
GC.Collect();
GC.WaitForPendingFinalizers();

Console.WriteLine("Press Enter to exit...");
Console.ReadLine();

This code exhibits unintuitive behavior. If compiled with a debug configuration (the /debug option for the command-line C# compiler), the PowerPoint application does not exit immediately, but if compiled without debugging information (the default for the command-line C# compiler), the PowerPoint application does exit immediately. When compiled with /debug, the pres instance is still considered “alive” due to the way that the JIT compiler tracks local variables, which differs between debuggable code and non-debuggable code.

Therefore, the only surefire way to ensure that an object gets collected by a call to GC.Collect is to first exit the method that declares the variable (assuming that nobody has a reference to the object to keep it alive). But exiting a method might not be as easy as it sounds; simple methods are sometimes inlined by the JIT compiler. In such cases, you may think you’re exiting a method when looking at source code, but at run time you really aren’t. If you’re concerned about inlining, you can mark a method with System.Runtime.CompilerServices.MethodImplAttribute and its MethodImplOptions.NoInlining value to prevent it from happening.

Listing 6.5 demonstrates this final technique using the objects from Microsoft PowerPoint’s imported type library. When running the code in this listing, the PowerPoint application always exits promptly regardless of compiler options.

Listing 6.5. Ensuring That All PowerPoint RCWs Are Collected When Finished Using Them So the Application Exits Promptly

Image

Securing Unmanaged Calls

Code-access security is an important part of any .NET application. Unmanaged code is outside the reach of the .NET security system, so if unmanaged code executes it can do whatever the operating system allows it to (based on the identity of the user who runs the code). This could include formatting your hard drive, sending e-mail messages on your behalf, or other nasty things. Fortunately, by default, all calls to COM objects are secure because they require everyone in the call stack to have unmanaged code permission. This permission is one of a handful of .NET security permissions that are generally regarded as “full trust” because so much can be done by code with these permissions. (Other such permissions include reflection permission and serialization permission.) In the default .NET security policy, applications that run from the local computer have unmanaged code permission granted to them, but applications running from the intranet or Internet zones do not.

If you believe that your interactions with unmanaged code can’t be abused in an unintended and harmful way, you may wish to assert unmanaged code permission to enable your code to be run in an environment without this permission. As long as the client of your component trusts your component, you can assert permissions to prevent the CLR from checking farther down the call stack. (If non-trusted components could assert permissions, there would be no point to having permissions at all.) Chapter 3, for example, used Microsoft Word for spell-checking functionality. It’s pretty reasonable to assume that the act of checking spelling is a “safe” operation, so a user of Word’s Application object could assert unmanaged code permission as follows:

[
  SecurityPermission(SecurityAction.Assert,
  Flags=SecurityPermissionFlag.UnmanagedCode)
]
public bool IsMisspelled(string word)
{
  // This should be a safe operation
  return msWord.CheckSpelling(word, ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing, ref missing);
}

If the assembly containing the IsMisspelled method is marked with AllowPartiallyTrustedCallersAttribute from the System.Security namespace, and if the computer’s policy is set such that this assembly is trusted, then the addition of SecurityPermissionAttribute makes it possible for code without any permissions to call IsMisspelled.

Caution

Be very careful when asserting any permissions, and when using AllowPartiallyTrustedCallersAttribute. Every use of these mechanisms adds a risk of exposing a security hole, violating the trust your component has been given. Remember that calling unmanaged code is secure by default. Programs get into trouble only when trying to be more accommodating or trying to gain the best possible performance by doing things such as asserting unmanaged code permission.

Rather than using the all-or-nothing approach to permissions, you may wish to provide a more granular permission requirement so your component can run in a semi-trusted environment. For example, despite the fact that file I/O classes use unmanaged code (Win32 APIs) to do the bulk of their work, they can require just file I/O permission to perform file I/O rather than unmanaged code permission. This can be achieved with the coding pattern of demanding one permission while asserting another. For example, the following code assumes that a new permission type called DictionaryPermission has been created, and uses it to restrict access to IsMisspelled to those who are allowed to look in a dictionary:

[
  SecurityPermission(SecurityAction.Assert,
  Flags=SecurityPermissionFlag.UnmanagedCode)
]
public bool IsMisspelled(string word)
{
  // Demand permission to access a dictionary
  new DictionaryPermission().Demand();

  return msWord.CheckSpelling(word, ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing, ref missing);
}

Another security concept that’s important and easy to misunderstand is known as a link demand. This type of demand checks only a member’s immediate caller for a required permission. With a link demand, the caller’s permissions are checked once during link time (another name for when the called method is just-in-time compiled), or during “JIT time.” A link demand can be placed on a member, or on a type to affect all of the type’s members except a static class constructor. (When an assembly isn’t marked with AllowPartiallyTrustedCallersAttribute, every public member of a strong-named assembly is already implicitly marked with a link demand for full trust.) The following Visual Basic .NET code demonstrates making a link demand for unmanaged code permission:

<SecurityPermissionAttribute(SecurityAction.LinkDemand, _
Flags:=SecurityPermissionFlag.UnmanagedCode)> _
Public Sub UnsafeOperation()
  ...
End Sub

Link demands are performed independently of the run-time stack walking performed for regular demands. By making a link demand for a certain permission rather than a regular demand, you can realize substantial performance improvements. However, link demands must be used carefully because it makes code susceptible to luring attacks. A luring attack refers to untrusted malicious code using trusted code to act on its behalf and do something damaging.

The reason link demands are important to understand for interoperability scenarios is that every member of the Marshal class and every member of the GCHandle class (except the IsAllocated property) in the System.Runtime.InteropServices namespace is marked with a link demand for unmanaged code permission to boost performance at the expense of security. The result is that care must be used with these APIs to prevent exposing a security hole in your own assembly, if it allows partially-trusted callers. The following C# code exposes a method that wraps Marshal.PtrToStringBSTR to provide the same behavior of creating a new .NET string from the contents of a BSTR:

// If this is a trusted method that allows partially-trusted callers,
// a client could trick it into doing something malicious
public string StringFromBSTR(IntPtr ptr)
{
  return Marshal.PtrToStringBSTR(ptr);
}

Image

If this is a public member of a trusted component that has unmanaged code permission and allows partially-trusted callers, it opens up a security hole. Because Marshal.PtrToStringBSTR only performs a link demand for unmanaged code permission, any caller of StringFromBSTR doesn’t require any sort of permission to execute its code. A malicious caller could then trick StringFromBSTR to read and return data from any random spot in memory by passing an IntPtr type with a random value, and discover information that it should not have access to. To prevent this from happening, the method should either demand unmanaged code permission, or perform a faster link demand and “pass the buck” to its caller:

// Guard against malicious callers without unmanaged code permission
[SecurityPermissionAttribute(SecurityAction.LinkDemand,
Flags=SecurityPermissionFlag.UnmanagedCode)]
public string StringFromBSTR(IntPtr ptr)
{
  return Marshal.PtrToStringBSTR(ptr);
}

The link demand could alternatively be placed on the class containing the StringFromBSTR method.

Caution

If you use AllowPartiallyTrustedCallersAttribute, be very careful whenever you call a member that performs a link demand, so you don’t expose dangerous functionality to untrusted callers of your strong-named component. The most widely used examples of members that perform a link demand are those of the System.Runtime.InteropServices.Marshal class.

Members that perform a link demand should say so in their documentation, but as in many cases where documentation isn’t adequate, the IL Disassembler (ILDASM.EXE) can come to the rescue. Any methods using a link demand have a .permissionset linkcheck statement in their disassembly information displaying what kind of permission is being demanded. Figure 6.2 shows what this looks like for one of the Marshal.Copy overloads. You can see the text representation of the link demand on the right-hand side of the window.

Figure 6.2. A link demand displayed in the IL Disassembler.

Image

One last topic regarding unmanaged code and security is a custom attribute called System.Security.SuppressUnmanagedCodeSecurityAttribute. This custom attribute can be marked on a class, interface, or method, to turn off the security stack walk that checks for unmanaged code permission at run time. A link demand for unmanaged code permission occurs in its place.

Although it’s tempting to use this custom attribute to increase performance, you should avoid it. Besides making code vulnerable to luring attacks, it has an interesting side effect. When an interface is marked with SuppressUnmanagedCodeSecurityAttribute, it requires that the caller have unmanaged code permission even if it’s implemented by a .NET class (meaning no unmanaged code is involved). Without this custom attribute, unmanaged code permission would only be required if the interface is implemented by a COM object.

The type library importer can optionally produce Interop Assemblies with the SuppressUnmanagedCodeSecurityAttribute custom attribute on all the types within. TLBIMP.EXE exposes this as its /unsafe option. When using this option, any user of such an Interop Assembly must take great care to encapsulate the use of the Interop Assembly so that it doesn’t permit its callers to do too much without the appropriate permissions. Because an Interop Assembly contains all public types and members for other assemblies to make use of, an author of an “unsafe” Interop Assembly can’t limit who can use and misuse the assembly. This is why TLBIMP.EXE’s /unsafe option is not recommended, and should never be used when creating a PIA.

Using COM+ and DCOM Objects

Both COM+ objects and DCOM objects can be used from managed code just like COM objects. All the standard COM Interoperability features apply, and there isn’t any additional built-in support for using such objects besides two methods for invoking DCOM objects on remote computers—Type.GetTypeFromProgID and Type.GetTypeFromCLSID.

Both Type.GetTypeFromProgID and Type.GetTypeFromCLSID have four overloads, two of which contain a string parameter that specifies a server name. This specifies the server on which the remote object should be created when you call Activator.CreateInstance using the Type object returned from either of these methods. This looks like the following in C#:

Type t = Type.GetTypeFromProgID("MyProgID", "MyComputerName");
Object o = Activator.CreateInstance(t);

Visual Basic .NET’s CreateObject method can also be used for the same purpose:

Dim o As Object = CreateObject("MyProgID", "MyComputerName")

Internally, creating an object in this fashion calls CoGetClassObject with the CLSCTX_REMOTE_SERVER flag and a COSERVERINFO structure whose pwszName field is set to a server name. This is standard DCOM, so all the standard rules apply. If you register the COM object appropriately on both computers (including a registered type library or registered proxy/stub DLL on the client machine necessary for COM marshaling), then everything should work just as it does from a COM client.

One easy way to set up a remote COM object is to install it as a COM+ object in the Component Services explorer. This can be done as follows on the server computer:

1. Select Component Services from the Administrative Tools item on the Windows Control Panel.

2. Choose the Component Services, Computers, My Computer, COM+ Applications node under the Console Root.

3. Right-click on COM+ Applications and select New, Application.

4. Follow the wizard to create an empty application.

5. Under the node for your empty application, right click on the Components folder and select New, Component.

6. Follow the wizard to add your COM component.

Now that the server computer is set up, you can register the COM component and its type library on the client machine (assuming that the COM component’s interfaces use type library marshaling).

An alternative way to create a remote object using DCOM is to simply use the new keyword, for example (in C#):

Object o = new MyClass();

Because the CLR calls CoCreateInstance with the CLSCTX_SERVER flag (which is a combination of CLSCTX_INPROC_SERVER, CLSCTX_LOCAL_SERVER, and CLSCTX_REMOTE_SERVER) in this situation, you can force new to create the COM object remotely as long as it’s registered appropriately on the client machine to point to the remote machine.

If you require the security and MultiQI features of CoCreateInstanceEx when creating remote COM objects, you’ll need to use PInvoke to call this method directly. An example of this is shown in Chapter 19. No built-in methods in the .NET Framework expose the functionality of CoCreateInstanceEx.

Caution

On some versions of Windows (before Windows XP or before Windows 2000 with Service Pack 3), calling CoGetClassObject with CLSCTX_REMOTE_SERVER attempts to create the COM object locally before attempting to create it remotely. This incorrect behavior can therefore been seen when using Type.GetTypeFromProgID, Type.GetTypeFromCLSID, or VB .NET’s CreateObject on such operating systems when a COM class is registered on the client machine such that it could successfully be created locally. A workaround on these operating systems is to call CoCreateInstanceEx directly via PInvoke in your .NET client.

DCOM can be used for communication between .NET and COM components (in either direction), or even between two .NET components when they both expose themselves as COM objects. However, .NET Remoting is the preferred means of cross-process and cross-machine communication. Just as it’s easy to use DCOM from .NET applications, it’s easy to communicate with a remote COM object using .NET Remoting. Figure 6.3 displays this scenario. It demonstrates two .NET applications communicating with the same remote COM object. One uses DCOM, and the other uses SOAP via .NET Remoting.

Figure 6.3. Communicating with a remote COM object using DCOM or using SOAP.

Image

Once you create an Interop Assembly for a COM component, you can publish it just as you would any other assembly to be used with .NET Remoting. This diagram is a simplification, because the top client likely references a local version of the Interop Assembly in order to cast the object returned by Activator.CreateInstance to interfaces that the COM object implements. Similarly, the bottom client requires a local copy of the Interop Assembly in order to use .NET Remoting. For more information about .NET Remoting, consult the .NET Framework SDK documentation.

Creating new .NET objects that act like COM+ objects themselves can be done by creating a serviced component—a class that derives from System.EnterpriseServices.ServicedComponent. The behavior of such a class is controlled with a variety of custom attributes listed in Chapter 12, “Customizing COM’s View of .NET Components.”

Inheriting from COM Classes

At first glance of this section’s title, you might think, “Why on earth would I want to inherit from a COM class? I barely want to use inheritance with .NET classes.” Still, this feature exists, and has some interesting properties.

As mentioned in Chapter 2, “Bridging the Two Worlds—Managed and Unmanaged Code,” COM doesn’t have a notion of inheritance. Instead, implementation reuse is achieved through containment or aggregation. In an interesting mix of the COM and .NET programming models, it is possible for a .NET class to derive from an RCW in a limited fashion. Internally, deriving from a COM class uses aggregation to provide the illusion of inheritance.

If you were not familiar with the renaming done by the type library importer with classes and coclass interfaces, your first attempt might be to derive from the coclass interface, such as ADO’s Recordset:

public class CustomRecordset : Recordset
{
  ...
}

This is not inheritance, however; it’s simply implementing an interface. It’s more apparent in a language like Visual Basic .NET which distinguishes between implementing an interface and deriving from a class (with Implements and Inherits keywords), but in C# or C++ the previous code can be misleading. In Visual Basic .NET, it would look as follows:

Public Class CustomRecordset
  Implements Recordset
  ...
End Class

Still, implementing a coclass interface usually fulfills the same need as inheritance would, because COM objects should always be passed via interface parameters anyway. The downside to implementing a coclass interface means that you must implement all of its members, including any event members from the default event interface that the coclass interface might be deriving from.

Deriving from a COM class instead looks like the following:

C#:

public class CustomRecordset : RecordsetClass
{
  ...
}

Visual Basic .NET:

Public Class CustomRecordset
  Inherits RecordsetClass
  ...
End Class

Using inheritance, however, doesn’t turn out to be a much better option. When deriving from an RCW, the CLR has a little-known rule that if you override any members that correspond to a COM interface, you must override all of that interface’s members. Unfortunately, no .NET compilers enforce this rule. The result for single-interface COM classes is that you end up with the same requirement of implementing all of the class’s members (except for event members from the event interface), but without the compiler support to guide you.

Caution

When defining a .NET class that derives from a COM class (the strongly typed RCW), make sure that you override all members of an interface if you override any of them. Failure to do so causes a TypeLoadException to be thrown at run time with the message, “Types extending from COM objects should override all methods of an interface implemented by the base COM class.”

There are a handful of easy ways to check for a TypeLoadException. One way would be to run the .NET Framework PE Verifier utility (PEVERIFY.EXE) that comes with the SDK. When running this tool on an assembly with such a class, you’d get the following error:

[IL]: Error:  [token ...] Type load failed.

The message isn’t very descriptive, but if you run the Assembly Registration Utility (REGASM.EXE) or simply write code that instantiates your incorrectly derived class, you’ll get the exception with the descriptive message.

This limitation may seem to make using inheritance with COM classes useless, but it’s reasonable considering that COM classes were not designed to be inherited from. For example, COM classes can’t expose protected members that expose internal state that may be needed by derived classes, so what are the chances that overriding Recordset.Open alone could perform differently than the base Open method and still work seamlessly with the interface’s other members? Implementing inheritance correctly is hard enough for classes designed to be inherited from; imagine how hard it would be to implement inheritance with a base class whose designers never imagined it being derived from! By overriding all the methods in an interface, you’re effectively re-implementing it. If you really only wanted to affect a handful of methods (such as logging when Open and Save are called before calling the base Open and Save methods), overriding all the other interface members to simply call their corresponding base members isn’t too much of a hassle.

Another limitation of deriving from COM classes is that some .NET languages may not support defining some signatures that appear in COM interfaces, yet you’d need to define them in order to override them. For example, Visual Basic .NET does not support defining properties with by-reference parameters (although you can call such properties), so a Visual Basic .NET class cannot derive from a COM class and override a property with by-reference parameters. These language limitations are discussed in Chapter 21.

An interesting option is available to derived .NET classes, and that is the ability to control how the base class is instantiated. This can be done with a delegate defined with the same signature as ObjectCreationDelegate in the System.Runtime.InteropServices namespace. The delegate signature has a System.IntPtr parameter called aggregator and returns a System.IntPtr type. The aggregator parameter represents the pUnkOuter parameter that would be passed to CoCreateInstance, and the returned IntPtr should be a pointer to an IUnknown interface, just like what CoCreateInstance would return.

To “register” the delegate so it’s called during class construction, it must be passed to the static ExtensibleClassFactory.RegisterObjectCreationCallback method, also in the System.Runtime.InteropServices namespace. There are two important guidelines for calling this method:

1. RegisterObjectCreationCallback must be called inside the class’s class constructor, also known as a static initializer. This is a constructor marked static in C# and C++ or Shared in VB .NET that gets executed once before the first instance of the class is created.

2. RegisterObjectCreationCallback may only be called once per class type.

Also, in version 1.0 of the CLR, the delegate passed to RegisterObjectCreationCallback must correspond to an instance method rather than a static method.

By registering an appropriate delegate, the Runtime-Callable Wrapper will invoke this custom method instead of calling CoCreateInstance to instantiate the base COM class. The base object can be instantiated however you like—as a singleton object, with a COM moniker, on a remote machine, from the running object table (ROT), and so on. So even if you never override any of the base class’s members, using this callback mechanism can still provide useful behavior that overrides the behavior of new.

Tip

Deriving from an RCW simply to control instantiation rather than overriding members is a slick way to hide a potentially complex activation scheme within a client’s use of the new keyword. The base COM object, however, must behave correctly when aggregated for this to work.

The following C# code demonstrates the use of ExtensibleClassFactory.RegisterObjectCreationCallback to plug in a CustomCreateInstance method that replaces CoCreateInstance and uses a moniker to create the base COM object.

public class MyDerivedClass : MyCoClass
{
  // Class constructor
  public static MyDerivedClass()
  {
    MyDerivedClass c = new MyDerivedClass();
    ExtensibleClassFactory.RegisterObjectCreationCallback(
      new ObjectCreationDelegate(c.CustomCreateInstance));
  }

  public IntPtr CustomCreateInstance(IntPtr aggregator)
  {
    object o = Marshal.BindToMoniker("...");
    return Marshal.GetIUnknownForObject(o);
  }
}

Debugging into COM Components

Visual Studio .NET has a feature called mixed-mode debugging, with which you can debug COM Interoperability calls from managed code into unmanaged code—or vice-versa—all from within the same IDE. This functionality is not available in the .NET Framework SDK debugger (CORDBG.EXE).

To be able to step into the source code of a COM component (or .NET component, for that matter) you need two items:

1. The source code for the component

2. A program database file (with the extension .pdb) that provides the debugger with extra information needed to debug the program and associate machine code statements with lines of source code

Any Visual C++ programs compiled using the Debug configuration automatically have a .pdb file available. For Visual Basic 6 programs, you need to perform an extra step because .pdb files are not generated by default. To create one, open the Visual Basic 6 project you wish to make debuggable in the Visual Basic 6 IDE and perform the following steps:

1. Select ProjectName Properties... from the Project menu inside the Visual Basic 6 IDE.

2. Click on the Compile tab.

3. Check the Create Symbolic Debug Info option, then click the OK button. (See Figure 6.4.)

Figure 6.4. Creating a debuggable Visual Basic 6 COM component.

Image

6. Recompile the project (Make FileName... on the File menu).

Once you have a COM component that can be debugged, simply leave the .pdb file alongside the unmanaged DLL to which it belongs. Visual Studio .NET will be able to find it there.

The trick to debugging programs that use COM Interoperability is that the Visual Studio .NET debugger does not enable stepping into unmanaged code by default. Instead, you need to explicitly enable mixed-mode debugging. This functionality is disabled by default because it makes the debugger run more slowly than it would otherwise. Interestingly enough, the procedure for turning on the functionality is different for Visual C#, Visual Basic, and Visual C++ projects!

To enable this feature, do the following inside Visual Studio .NET:

1. Right-click on the project name in the Solution Explorer, and select Properties from the menu.

2. Select Debugging under the Configuration Properties folder.

3. Choose your next setting based on the language you use:

• For Visual C# projects, set Enable Unmanaged Debugging to True.

• For Visual Basic projects, check Unmanaged code debugging.

• For Visual C++ projects, set the Debugger Type to Mixed.

These three actions are displayed in Figures 6.5, 6.6, and 6.7.

Figure 6.5. Enabling mixed-mode debugging in a Visual C# .NET project.

Image

Figure 6.6. Enabling mixed-mode debugging in a Visual Basic .NET project.

Image

Figure 6.7. Enabling mixed-mode debugging in a Visual C++ .NET project.

Image

Tip

Be sure to enable mixed-mode debugging when you want to step from managed code into unmanaged code! Otherwise, the debugger won’t stop at unmanaged breakpoints and silently refuses to honor your request to step into an unmanaged member, stepping over it instead.

That’s all there is to it! Now you can use the Visual Studio .NET debugger as you normally would, and seamlessly step into COM components. Figure 6.8 shows someone stepping from a C# application into a Visual Basic 6 COM component. Notice the call stack, with the VB6 stack frame marked with the language BASIC.

Figure 6.8. Stepping into a Visual Basic 6 COM component inside Visual Studio .NET.

Image

Caution

Visual Basic 6 doesn’t always generate complete debug information, especially for local variables. As a result, it’s common to see incorrect or incomplete information in the Visual Studio .NET debugger’s Locals window when debugging VB6 components, such as variables named unnamed_var1 instead of the real variable names.

Caution

There are a few limitations to the mixed-mode debugging support. The result is that sometimes you’ll need to set breakpoints to stop the debugger in places where the “stepping” operation doesn’t work as expected. The limitations are:

• Stepping from one thread to another is not supported. This means that attempting to step into a call on an STA COM component from an MTA thread or an MTA COM component from an STA thread acts like a “step over” operation because one thread must be marshaled to the other. To work around this, you could simply set a breakpoint inside the COM call and the debugger will stop on it.

• The complete call stack may not always be shown when it consists of both managed and unmanaged stack frames. In particular, unmanaged frames can sometimes be missing. In the rare cases when this happens, stepping across a missing unmanaged frame from managed code can produce the wrong results. To work around this, you can set a breakpoint and jump to it rather than stepping one line at a time.

• Property evaluation in debugger windows can be extra slow. To avoid this, you can tell the debugger to not evaluate properties. This is a good idea, anyway, for all applications, because a debugger evaluating a property with side effects can produce incorrect behavior in the application you’re debugging. For any project type, you can turn this off as follows:

Choose Options from the Tools menu.

Select General under the Debugging folder.

Uncheck Allow property evaluation in variables windows.

RPC debugging is not enabled by default, even for unmanaged-only projects. To enable it, do the following:

1. Choose Options from the Tools menu.

2. Select Native under the Debugging folder.

3. Check RPC debugging.

Monitoring Performance

To create software with top-notch performance, it’s important to understand how processing time is being spent when the application runs. For programmers new to the .NET Framework and COM Interoperability, understanding your application’s run-time performance profile is critical, because it’s easy to inadvertently use a poor-performing approach. To help obtain such information, NT-based versions of Windows have a System Monitor in which applications that expose performance objects can be monitored while they are running. This utility can display information using line graphs, bar charts, and other displays, so it can be a great tool for understanding performance problems such as bottlenecks in your application.

The .NET Framework exposes several performance objects that can be used with the System Monitor, including one specifically for interoperability. Every performance object has one or more performance counters, individual data values being tracked over time, such as “number of bytes in the GC heaps,” or “number of classes loaded.”

The interoperability performance object contains the following performance counters, listed here with their displayed names:

# of CCWs—The number of COM-Callable Wrappers (CCWs) currently active. With this counter, you can see how many .NET objects are currently being used from COM.

# of marshalling [sic]—The number of times marshaling occurs across the managed/unmanaged boundary in either direction. This counter doesn’t show the times when the marshaling overhead is small enough for the marshaling stubs to become inlined. (Stubs are pieces of code that perform the marshaling.) This counter applies to both COM Interoperability and PInvoke.

# of Stubs—The number of stubs that have been created by the CLR to perform marshaling, either in a COM Interoperability or PInvoke call.

# of TLB exports / sec—This counter is never used.

# of TLB imports / sec—This counter is never used.

To view these performance counters, available on a system-wide or per-process basis, use the following steps on Windows 2000, Windows XP, or subsequent operating systems:

1. Run PERFMON.EXE at a command prompt.

2. Right-click on the list at the bottom of the right-hand pane and select Add Counters..., or click on the button at the top with the plus icon.

3. Select the desired counters from the .NET CLR Interop performance object. This dialog is pictured in Figure 6.9.

Figure 6.9. Selecting interoperability performance counters.

Image

The .NET Framework also provides several performance counter APIs in the System.Diagnostics namespace, enabling you to create your own performance counters and use any performance counters programmatically. Consult the .NET Framework documentation for more information about performance counters.

Conclusion

This chapter rounds out this book’s coverage of using COM components in .NET applications. The only chapter remaining in Part II, mentioned several times in this chapter, focuses on the task of modifying Interop Assemblies using tools provided by the .NET Framework SDK. Although this chapter only contained one Frequently Asked Question, all of the material covered answers several FAQs that appear time and time again when developers get serious about using COM components in managed code. Hopefully, this information will come in handy as you take on the adventure for yourself.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.183.1