Chapter 20. Custom Marshaling

In This Chapter

Transforming Types Without Custom Marshaling

Custom Marshaling Architecture

Marshalers, Marshalers, Marshalers!

Limitations

Custom marshaling is the extensibility mechanism for the Interop Marshaler. The term custom marshaling can mean different things in different situations, but this chapter refers to customizing Interop marshaling across managed/unmanaged boundaries. COM-style custom marshaling across contexts (performed by implementing IMarshal) is completely independent of Interop custom marshaling, and both can coexist peacefully.

The work done by the Interop Marshaler without custom marshaling is summarized in Figure 20.1. These are the same diagrams that were presented in Chapter 2, “Bridging the Two Worlds—Managed and Unmanaged Code.”

Figure 20.1. COM and .NET interaction when no custom marshaling is involved.

Image

The CLR’s custom marshaling infrastructure enables you to replace the system-supplied COM-Callable Wrappers (CCWs) and Runtime-Callable Wrappers (RCWs) on a call-by-call basis. This capability is illustrated in Figure 20.2.

Figure 20.2. COM and .NET interaction with custom marshaling.

Image

Notice how an object implementing IMyInterface is exposed via a wrapper implementing IAnyInterface instead. When you control the wrappers, you can transform objects however you’d like (within some limitations discussed in the “Limitations” section at the end of the chapter).

Transforming one data type to a completely different type may sound like a crazy thing to do, but it’s actually the Number One application of custom marshaling. This occurs because the arrival of .NET has introduced several brand-new data types that, at some logical level, serve the same purpose as existing COM types. For example, although the .NET System.IO.Stream is unrelated to COM’s IStream interface (from the perspective of any runtimes or compilers), they are extremely similar abstractions that enable reading, writing, and seeking over a buffer.

Likewise, the .NET System.Drawing.Font class and the COM IFont interface are just different ways of exposing the same operating system font objects. The same goes for System.Drawing.Color and OLE_COLOR, System.Collections.IEnumerator and IEnumVARIANT, and so forth. Such transformations are really no different than the data type transformations done by the standard Interop Marshaler, which bridges the “unrelated” System.String and BSTR types, .NET arrays and SAFEARRAYs, System.Guid and GUID types, and so on.

Custom marshaling provides the glue between objects that use different but semantically similar data types. It can also be used to customize standard marshaling in more subtle ways (shown in the last example in the “Marshalers, Marshalers, Marshalers!” section) but this isn’t as common.

In this chapter, we begin by examining how one could convert one data type to another without any custom marshaling support from the CLR. Then we examine the custom marshaling infrastructure that standardizes such conversions. Finally, we end the chapter with lots of examples and a discussion of custom marshaling limitations.

Transforming Types Without Custom Marshaling

Imagine that you’re using a COM object in managed code that returns a stream to you in the form of an object implementing COM’s IStream interface. You want to use some .NET APIs that operate on System.IO.Stream objects, such as constructing a System.IO.StreamReader with this IStream instance. The following C# code would not compile because you must pass a Stream instance to the StreamReader constructor:

Image

IStream s = comObject.GetXml();
StreamReader sr = new StreamReader(s); // This line doesn't compile because
                                       // IStream isn't a Stream
Console.SetIn(sr);
...
sr.Close();

To make this work, you could define an adapter class that wraps an object implementing IStream inside a class that derives from Stream. An instance of such a class is called an adapter object, which could be used as follows:

IStream origStream = comObject.GetXml();
Stream s = new ComStream(origStream); // Convert IStream to Stream
StreamReader sr = new StreamReader(s);
Console.SetIn(sr);
...
sr.Close();

Just like System.IO.FileStream or System.IO.MemoryStream, the ComStream class can be used anywhere Stream is required thanks to the polymorphism provided by inheritance. The ComStream class is responsible for wrapping IStream appropriately so Stream.Read calls IStream.Read, Stream.Write calls IStream.Write, and so on. All of IStream’s methods are displayed in Listing 20.1, which contains the definition for System.Runtime.InteropServices.UCOMIStream—the official .NET definition of the COM IStream interface.

Listing 20.1. The .NET Definition of UCOMIStream Shown in C# Syntax

Image

Representing streams in .NET, the System.IO.Stream abstract class has abstract Read, Write, Seek, SetLength, and Flush methods, a handful of abstract properties, and many more public members. Although the mapping of core functionality (read/write/seek) might be straightforward, other areas might require some thought. Listing 20.2 defines a ComStream adapter class in C# that exposes UCOMIStream functionality as a .NET Stream.

Listing 20.2. ComStream.cs. An Adapter Class That Exposes an IStream Implementation as a .NET Stream

Image

Image

Image

Image

Image

Image

Lines 1–3 use the System namespace for IntPtr and a handful of exception types, the System.IO namespace for Stream, and the System.Runtime.InteropServices namespace for UCOMIStream and Marshal. The constructor in Lines 11–21 sets the private UCOMIStream field to the passed-in instance if it’s not null. The class’s finalizer in Lines 24–27 calls the Close method, which is Stream’s equivalent to IDisposable.Dispose. (The base Stream class explicitly implements IDisposable, and its Dispose implementation simply calls Close.) Lines 30–33 define an UnderlyingStream property that publicly exposes the raw COM stream object. This property can be useful in cases where the COM object implements other interfaces for a client to make use of.

Line 37 begins the implementation of Stream’s members, which forward calls to the UCOMIStream interface. The first two lines of the Read method (39 and 40) perform a common task for a class that operates on a disposable instance—checking to see if the object has been disposed already and throwing an ObjectDisposedException if it has. Notice that this implementation of Read only accepts an offset value of zero. This is done for simplicity and performance, because the only way to pass UCOMIStream.Read an appropriate array when a non-zero offset is given would be to instantiate a new array and copy the source array’s elements beginning at the offset.

The tricky part of the Read implementation is that it needs to obtain the address of an integer variable, represented as an IntPtr type, to pass as the third parameter to UCOMIStream.Read. Line 48 uses C# unsafe code to cleanly accomplish this. Because of this, the method must be marked with the unsafe keyword and the class must be compiled with the /unsafe option from the command line. (Or, Allow unsafe code blocks must be set to True within Visual Studio .NET.) See Chapter 6, “Advanced Topics for Using COM Components,” for another way to get a value type’s address that works for any .NET language.

ComStream.Write in Lines 57–67 works just like ComStream.Read, but because it doesn’t return how many bytes were written, Line 66 can simply pass a “null pointer” (IntPtr.Zero) as the third parameter to UCOMIStream.Write. ComStream.Seek (Lines 70–85) also has a similar implementation, and uses unsafe code just like ComStream.Read to pass an address of a 64-bit variable. Fortunately, the STREAM_SEEK enumeration used by UCOMIStream and the SeekOrigin enumeration used by Stream have the exact same values with the exact same meanings, so the input origin variable can simply be cast to the integer type (which represents a STREAM_SEEK value) expected by UCOMIStream.Seek in Line 82.

The remaining methods and properties in Lines 88–157 do what they can to bridge the functionality from UCOMIStream to the functionality of Stream, but the mapping isn’t always obvious. For example, ComStream.Length uses UCOMIStream.Stat to obtain the stream’s length, and ComStream.Flush calls UCOMIStream.Commit with the value 0 to represent STGC_DEFAULT (an enumeration value used with COM’s IStream interface).

This ComStream class doesn’t bother overriding members of Stream that already have a suitable implementation, such as ReadByte, WriteByte, BeginRead, BeginWrite, EndRead, and EndWrite. The class also lets any exceptions thrown be exposed directly. To be more user-friendly, it could have caught some exceptions and rethrown them with different exception types that a user of Stream would expect from its methods. This can be especially helpful when the inner object throws lots of COMExceptions.

The problem with the adapter class approach used in Listing 20.1 is that it uses System.Runtime.InteropServices.UCOMIStream for the .NET definition of IStream, but no Interop Assemblies generated by the type library importer use this type unless they were manually altered as in Chapter 7, “Modifying Interop Assemblies.” Most likely, any APIs that use IStream exposed by an Interop Assembly end up using a .NET IStream interface definition contained in the same assembly (due to the #import effect described in Chapter 15, “Creating and Deploying Useful Primary Interop Assemblies”). This isn’t a huge setback when the object implementing IStream is a COM object. You can explicitly cast a COM object to any .NET definition of IStream you’d like and it will always succeed as long as its QueryInterface implementation responds positively to the IID of IStream. Therefore, the ComStream adapter class defined in Listing 20.2 could be used as follows if an object returns a non-standard IStream interface:

IStream origStream = comObject.GetXml();
Stream s = new ComStream((UCOMIStream)origStream);
StreamReader sr = new StreamReader(s);
Console.SetIn(sr);
...
sr.Close();

An adapter class may be desired in the other direction as well. Suppose you’re using a .NET class that returns a Stream instance, and you need to pass the stream to a COM object. Again, doing this directly isn’t possible:

Stream s = System.Console.OpenStandardOutput();
comObject.WriteContentsToStream(s); // This line doesn't compile because
                                    // Stream doesn't implement IStream
s.Close();

Instead, you could define an adapter class that wraps an object derived from Stream inside a class that implements IStream. This could then be used as follows:

Stream s = System.Console.OpenStandardOutput();
UCOMIStream istream = new ManagedIStream(s); // Convert Stream to IStream
comObject.WriteContentsToStream(istream);
s.Close();

Listing 20.3 contains a definition of the ManagedIStream adapter class that internally acts upon a Stream class.

Listing 20.3. ManagedIStream.cs. An Adapter Class That Exposes a Stream Object Through an IStream Implementation

Image

Image

Image

Image

Image

Image

Lines 1–3 use the System namespace for IntPtr and a handful of exception types, the System.IO namespace for Stream, and the System.Runtime.InteropServices namespace for UCOMIStream and Marshal. The class is marked with ClassInterfaceType.None because any COM access should be done through the UCOMIStream interface. This also removes the need to mark the class’s members with the necessary attributes that make them work correctly when called from unmanaged code. These attributes can be seen in Listing 20.1 on the UCOMIStream interface definition, such as [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1), Out] on the first parameter of UCOMIStream.Read.

The constructor, finalizer, and UnderlyingStream property in Lines 12–40 work just like they do in Listing 20.2. The ManagedIStream.Read, ManagedIStream.Write, and ManagedIStream.Seek methods (Lines 44–101) all have an IntPtr third parameter that can either be “null” (IntPtr.Zero) or have a value if the caller wants to know how many bytes were read, written, or moved past. Therefore, Lines 49, 67, and 91 check the value of the third parameter to determine whether to do the extra work. Writing to the “dereferenced pointer” is done with Marshal.WriteInt32 in Lines 56 and 76–77 and Marshal.WriteInt64 in Lines 98–99. The cast to an integer in Line 77 could fail if more than 0x7FFFFFFF bytes were written, so a robust adapter class may wish to check for this case.

The Revert and Clone methods (Lines 154–158 and 189–192, respectively) throw a NotImplementedException because there’s no corresponding functionality in the Stream class. For an implementation of ManagedIStream.Clone, you might be tempted to try casting the originalStream instance to ICloneable and using its cloning functionality, if it exists.

However, this does not give the same semantics as UCOMIStream’s Clone method, which is supposed to clone just the seek pointer so that the new seek pointer operates on the same underlying bytes. This functionality is not available from System.IO.Stream.

Rather than throwing a NotImplementedException, LockRegion and UnlockRegion (Lines 161–165 and 168–172, respectively) throw a COMException with the HRESULT STG_E_INVALIDFUNCTION because the documentation for these functions states that this is the HRESULT that should be returned when a stream does not support locking. In the upcoming “Example: Marshaling Between System.IO.Stream and IStream” section, we’ll see how to support locking when the .NET stream is an instance of System.IO.FileStream.

By defining a pair of adapter classes like ComStream in Listing 20.2 and ManagedIStream in Listing 20.3, you can transform one type to another without using any custom marshaling support from the CLR. At this point, you might be asking why we spent so much time looking at this approach because this chapter is supposed to be about custom marshaling. The answer is that this same approach is exactly what is done when you use the built-in custom marshaling support—it’s just encapsulated. The next section describes how custom marshaling works and how adapter classes like the ones in the previous two listings fit in.

Custom Marshaling Architecture

There are usually three components involved in custom marshaling:

• The custom marshaler

• The consumers

• The adapter objects

The custom marshaler is an object that the CLR calls into when data transformations need to occur. The consumers of a custom marshaler are signatures marked with the MarshalAsAttribute pseudo-custom attribute and the value UnmanagedType.CustomMarshaler, specifying that a given parameter, return type, or field should not use the standard marshaling support. When a member marked with a custom marshaler (via MarshalAsAttribute) is invoked, the CLR instructs the custom marshaler to do the marshaling and return an object representing the marshaled result. These objects are typically adapter objects but, as discussed in the “The Adapter Objects” section later in this chapter, they may not be.

Just as CCWs and RCWs are largely transparent to their users, so are adapter objects when they are used with the custom marshaling infrastructure. This transparency is the only reason for bothering with custom marshaling because everything can be accomplished by directly using adapter classes the same way they were used in the previous section.

Tip

Use custom marshaling to save your users the extra step of directly instantiating adapter classes to convert between two different data types.

The Custom Marshaler

There are two requirements for a custom marshaler. It must:

• Implement the System.Runtime.InteropServices.ICustomMarshaler interface

• Implement a static GetInstance method

The reason there are two requirements rather than just one (such as only implementing the ICustomMarshaler interface) is that interfaces cannot have static members. (Well, they can in IL Assembler but not with the semantics needed here.)

The ICustomMarshaler Interface

System.Runtime.InteropServices.ICustomMarshaler is a relatively simple interface, shown as follows in C# syntax:

public interface ICustomMarshaler
{
  object MarshalNativeToManaged(IntPtr pNativeData);
  IntPtr MarshalManagedToNative(object ManagedObj);
  void CleanUpNativeData(IntPtr pNativeData);
  void CleanUpManagedData(object ManagedObj);
  int GetNativeDataSize();
}

In these methods, native is used as another term for unmanaged. Here are descriptions of the five methods:

MarshalNativeToManaged. This method is called by the CLR whenever an object must be marshaled from unmanaged code to managed code. It’s called with a pointer to the unmanaged object, represented as a System.IntPtr type. The implementer must return an appropriate .NET object representing the “managed view” of the unmanaged object. This can be an instance of an adapter class like ComStream in the previous section.

MarshalManagedToNative. This method is called by the CLR whenever an object must be marshaled from managed code to unmanaged code. It’s called with the managed object instance. The implementer must return a pointer to an appropriate unmanaged object (as a System.IntPtr type) representing the “unmanaged view” of the .NET object. This object can be an instance of an adapter class like ManagedIStream in the previous section. If a COM interface pointer is returned, its reference count must be incremented.

CleanUpNativeData. This method is called by the CLR when it’s appropriate to clean up the unmanaged object allocated in the MarshalManagedToNative method. This can include decrementing the reference count for a COM object that was incremented in MarshalManagedToNative, freeing memory that was allocated, unpinning memory that was pinned, and so on. The passed-in System.IntPtr parameter is a pointer to whatever needs to be cleaned up.

CleanUpManagedData. This method is called by the CLR when it’s appropriate to clean up the managed object allocated in the MarshalNativeToManaged method. Of course, because managed objects are garbage-collected, typically nothing needs to be done in this method. Even if the object implements IDisposable, you should not call its Dispose method unless you’re performing custom marshaling by value. This is discussed in the upcoming “An Alternative to Adapter Objects” section. The passed-in object parameter is the instance that could potentially require clean-up.

GetNativeDataSize. This method is supposed to return the size, in bytes, of the unmanaged type being marshaled if it’s a value type, or –1 to indicate that you’re not marshaling a value type. Because custom marshaling of value types is not supported in version 1.0 of the .NET Framework, this method is never called by the CLR. See the “Limitations” section in this chapter for more information. This method’s implementation should always return –1.

The GetInstance Method

Besides implementing ICustomMarshaler, a custom marshaler must implement a static (Shared in VB .NET) method named GetInstance with the following signature (shown in C#):

static ICustomMarshaler GetInstance(string cookie)

Because this method is not part of the ICustomMarshaler interface definition, you can’t rely on the compiler to remind you to implement this method.

Tip

Remember to implement GetInstance when writing a custom marshaler (and be sure to make it static), because your compiler can’t enforce this requirement. Failure to do so causes an exception at run time when the CLR attempts to use your custom marshaler.

GetInstance is called by the CLR in order to get an instance of the custom marshaler. The string parameter is a “cookie” value that can be specified by the custom marshaler consumer and used by the custom marshaler however it wishes. The following is a proper implementation of GetInstance in C# that ignores the cookie value and assumes the presence of a private class member called marshaler:

public static ICustomMarshaler GetInstance(string cookie)
{
  if (marshaler == null)
    marshaler = new MyMarshaler();

  return marshaler;
}

This instantiation pattern makes the custom marshaler a singleton object, which is appropriate because custom marshalers must not maintain any state. This is the case because the CLR calls GetInstance only once per application domain per parameter, field, or return type that uses the custom marshaler, regardless of how many times the marshaler is used.

Similar to the custom registration functions presented in Chapter 12, “Customizing COM’s View of .NET Components,” the GetInstance method doesn’t have to be public because the CLR can invoke it regardless of its visibility, but it must be static.

The Consumers

The consumers of a custom marshaler were described earlier as any member signatures with a parameter, return type, or field marked with MarshalAsAttribute and the value UnmanagedType.CustomMarshaler. This works on any parameters and return values, regardless of whether they belong to a PInvoke signature or a member of a COM object, and works on any fields, regardless of whether they are exposed as fields of a structure or properties of a class. Recall that to use MarshalAsAttribute with a property, you must mark the parameter and return type of its accessor methods.

MarshalAsAttribute has the following named parameters relevant to custom marshaling:

MarshalTypeRef. This can be set to the type of a custom marshaler class, using typeof in C#, GetType in VB .NET, or __typeof in C++. Either this named parameter or MarshalType must be used with UnmanagedType.CustomMarshaler, but MarshalTypeRef is recommended.

MarshalType. This can be set to a string specifying the custom marshaler type. This can be simply the namespace-qualified type name if it resides in the same assembly, but it must be qualified with a full assembly name if it’s in a different assembly. This is useful for a “late bound” reference to a custom marshaler type, which does not require its metadata at compile time.

MarshalCookie. This can be set to a string that will be passed to the custom marshaler’s GetInstance method. If MarshalCookie is not set, an empty string is passed.

Either MarshalTypeRef or MarshalType must be used to specify the custom marshaler type, but MarshalCookie is optional. If both MarshalTypeRef and MarshalType are specified (which you should never do), MarshalTypeRef is ignored. If the custom marshaler type specified by MarshalTypeRef or MarshalType cannot be loaded when a marked member is called, a TypeLoadException is thrown.

The use of MarshalAsAttribute for custom marshaling is demonstrated in the following C# signatures:

Image

Tip

The choice of using MarshalTypeRef with a Type object versus MarshalType with a string is just like the choice encountered with ComSourceInterfacesAttribute, discussed in Chapter 13, “Exposing .NET Events to COM Clients.” You should use MarshalTypeRef whenever possible, because the compiler emits a string with the full assembly name, as can be seen using ILDASM.EXE:

Image

Or, if the custom marshaler type happens to be in the same assembly as the attribute using MarshalTypeRef, only the type name is emitted.

Although using a partial assembly name with MarshalType, such as "StreamMarshaler, StreamMarshaler" works for assemblies not in the Global Assembly Cache, you should not use this if there’s a chance that the custom marshaler will ever be installed in the GAC at a later date.

By-reference or out parameters are automatically supported without any extra work needed by the custom marshaler. For example, both MarshalManagedToNative and MarshalNativeToManaged are called when marshaling a by-reference parameter, but only one of them is called when marshaling a by-value parameter. Which one is called depends on the direction of the call. To get both MarshalManagedToNative and MarshalNativeToManaged to be invoked when marshaling a by-value reference type, a consumer would need to mark the parameter with both InAttribute and OutAttribute, a requirement that matches the behavior of standard marshaling. This is often not necessary, however, because the use of adapter classes means that changes to the reference type’s state (such as the bits in a stream) are seen after the call anyway because the callee is indirectly using the same object instance.

The previously shown C# signatures are exported to a type library as follows:

HRESULT GetStream([out, retval] IUnknown** pRetVal);

HRESULT WriteToStream([in] IUnknown* s);

HRESULT GetStreamViaOutParameter([out] IUnknown** s,
  [out, retval] VARIANT_BOOL* pRetVal);

HRESULT GetStreamViaByRefParameter([in, out] IUnknown** s,
  [out, retval] VARIANT_BOOL* pRetVal);

HRESULT PrintString([in] long s);

HRESULT SetData([in] long data);

Notice that the unmanaged type to which each managed type is being custom-marshaled does not show up in an exported type library. That’s because the MarshalAsAttribute contains no information about what unmanaged type a custom marshaler marshals; it’s hidden as an implementation detail. Users of a custom-marshaled object must call QueryInterface to get a pointer to the desired interface. Visual Basic 6 users don’t have to do anything because the VB6 runtime does the QueryInterface call, although the type will appear as Unknown in the object browser and IntelliSense.

Also, notice that custom-marshaled string and array parameters are exported as long. This really represents a pointer value; in other words, void*. Such a type library works fine on a 32-bit platform, but is not a portable way to express a pointer.

Tip

To work around limitations of what the type library exporter produces for custom-marshaled parameters, you could change the type library (using OLEVIEW.EXE to get an IDL representation then using MIDL.EXE to compile a new type library) or create a new one yourself. It’s valid to change a custom-marshaled IUnknown* to a specific interface like IStream* if and only if the custom marshaler always produces a pointer to IStream or a derived interface. This depends on the implementation of the custom marshaler, because it may validly produce a plain IUnknown pointer instead of a more specific interface. It’s also a good idea to change the long type exported to void* for custom-marshaled strings and arrays.

The Adapter Objects

Adapter objects are typically what a custom marshaler returns from ICustomMarshaler.MarshalNativeToManaged and ICustomMarshaler.MarshalManagedToNative. To demonstrate, Listing 20.4 (when combined with the previous two listings) contains a complete custom marshaler to transform a .NET Stream to a COM IStream and vice-versa. This can be compiled from a command prompt as follows:

csc StreamMarshaler.cs ComStream.cs ManagedIStream.cs /unsafe

Listing 20.4. StreamMarshaler.cs. A C# Custom Marshaler That Converts Stream to IStream and Vice-Versa

Image

Image

MarshalNativeToManaged returns null on Line 11 if the incoming pointer value represents null, or returns a new ComStream instance from the object implementing UCOMIStream on Line 17. This object is the standard RCW obtained from the incoming IntPtr value using Marshal.GetObjectForIUnknown on Line 13.

In the reverse direction, MarshalManagedToNative constructs a new ManagedIStream instance from the incoming object on Line 30 and uses Marshal.GetComInterfaceForObject to create a standard CCW for the object. The pointer to the CCW is returned in Lines 31–32. Marshal.GetComInterfaceForObject increments the reference count for the CCW, so CleanUpNativeData calls Release on the same object in Line 37 to avoid a reference count leak.

Marshal.GetIUnknownForObject could have been called instead of Marshal.GetComInterfaceForObject, but it’s better to return an IStream pointer rather than an IUnknown pointer in case consumers of the custom marshaler are tempted to modify a type library to change an exported IUnknown* corresponding to a custom marshaled Stream parameter to IStream*.

Tip

Using members of the Marshal class in custom marshalers is a great way to leverage bits and pieces of standard marshaling functionality inside your custom transformations. Several of its methods, such as the GetHRForException and ThrowExceptionForHR methods used in the upcoming “Marshalers, Marshalers, Marshalers!” section, were designed to be used inside custom marshalers. Appendix A, “System.Runtime.InteropServices Reference,” covers every method of the Marshal class.

Because C# code cannot interact with COM objects directly, it can’t truly provide a custom CCW and custom RCW as explained at the beginning of the chapter. Instead, the custom marshaler in Listing 20.4 uses the standard CCW for the adapter object to act as a custom CCW for the original .NET Stream object when marshaling from .NET to COM. In the reverse direction, the marshaler wraps a standard RCW with the ComStream wrapper rather than actually replacing the RCW that directly wraps the COM object. These two situations are pictured in Figures 20.3 and 20.4, using the example of Stream on the .NET side and IStream on the COM side.

Figure 20.3. Using the standard CCW for an adapter object when marshaling from .NET to COM.

Image

Figure 20.4. Using the standard RCW with an adapter object when marshaling from COM to .NET.

Image

This approach is valid, and is pretty straightforward to implement because everything primarily stays in the managed realm. Using Visual C++ .NET with Managed Extensions, however, you can make one adapter object the RCW and the other one the CCW. This means using the C++ capabilities of mixing managed and unmanaged code to handle most or all of the interactions normally done by the CLR-supplied wrappers. The custom CCW is a full-fledged COM class (even with an explicit implementation of IUnknown) that happens to interact with managed objects. The custom RCW is still a .NET class, but one that does not need to rely on COM Interoperability to interact with unmanaged objects.

Figures 20.5 and 20.6 illustrate this approach in both directions, using the example of Stream on the .NET side and IStream on the COM side. Although these diagrams show no use of standard RCWs, in practice a standard RCW is often used by the custom RCW as an easy way to ensure context-safe calls on the wrapped COM interface. This will be demonstrated in Listing 20.9 later in the chapter. Listings 20.9 and 20.10 will re-implement the two adapter classes from Listings 20.2 and 20.3 in C++ to demonstrate how to implement this custom marshaling technique.

Figure 20.5. Using an unmanaged adapter object that is a custom CCW when marshaling from .NET to COM.

Image

Figure 20.6. Using a managed adapter object that is a custom RCW when marshaling from COM to .NET.

Image

Although the diagrams for this case are simpler, implementing the adapter objects may be more complicated. Here are the pros and cons of using C++ to implement adapter objects that work directly with managed and unmanaged code:

PROS:

• Better performance. Besides the fact that you can eliminate a wrapper in each direction, the other approach sometimes involves extra data transformations. For example, supporting non-zero offsets in ComStream.Read and ComStream.Write in Listing 20.2 would’ve involved copying one .NET array to another before exposing it to COM. When the adapter class is rewritten in Listing 20.9, you’ll see that a pointer to the correct element in the array can be exposed directly without performing any copying.

• No limitations. Because the COM adapter object uses its .NET object directly and the .NET adapter object uses its COM object directly, you aren’t limited by the support given by the standard RCW and CCW objects. For example, you could support VARIANTs with the VT_RECORD type.

CONS:

• Less built-in support. The price of total freedom is the fact that you have to do more yourself. For example, you’re responsible for all data transformations between COM and .NET—manually converting between .NET strings and BSTRs, .NET arrays and SAFEARRAYs, .NET objects and VARIANTs, and so on. (The System.Runtime.InteropServices.Marshal class helps a great deal, however, by providing small chunks of standard marshaling functionality that can be used by your custom wrappers.) Also, whereas a standard CCW implements many standard interfaces (like IProvideClassInfo, ISupportErrorInfo, IConnectionPointContainer, and so on), a custom CCW COM object doesn’t magically implement any interfaces that you don’t explicitly implement in your source code.

• Difficulty integrating with .NET code in other languages. If you don’t mind having your custom marshaler in a separate assembly or are doing your development in C++ anyway, this doesn’t apply. People often have the urge to contain all their functionality in a single file, but if your primary development language is C# or VB .NET, you can’t merge your custom marshaling code with your other code without ILDASM.EXE and ILASM.EXE trickery. You could also opt to produce a C++ module and a C# module and link them into a multi-file assembly.

Listing 20.5 contains the code for a typical custom marshaler written in C++, assuming the existence of two adapter classes: CustomCCW—an unmanaged COM class, and CustomRCW—a .NET class. This custom marshaler will be used with the C++ adapter classes implemented in the “Marshalers, Marshalers, Marshalers!” section later in this chapter, replacing UnmanagedType and ManagedType with the types being custom-marshaled.

Listing 20.5. A Typical Custom Marshaler Written in C++ That Uses Adapter Classes Serving Directly as Custom CCWs and RCWs

Image

Image

In the C#-authored custom marshaler, Marshal.GetObjectForIUnknown was used to convert an IntPtr to an object inside MarshalNativeToManaged. Here, in Line 11, IntPtr’s ToPointer method is called, which returns void*. This void* can then be cast to the desired .NET type. This type is passed to the .NET CustomRCW adapter object.

Tip

In C++, use IntPtr.ToPointer as an intermediate step when converting an IntPtr value to a class or interface type. This is necessary because you cannot cast an IntPtr value to a reference type directly.

In MarshalManagedToNative, Line 19 calls QueryInterface directly on the CustomCCW object and Line 20 casts the returned interface pointer to return directly to an IntPtr value. This can only be done when CustomCCW is an unmanaged COM class. If CustomCCW is a .NET class instead, it wouldn’t really be a CCW, so Marshal.GetComInterfaceForObject could be used to obtain an IntPtr value representing an interface pointer for the CLR-supplied CCW for the CustomCCW .NET class.

The rest of the listing is essentially the same as the C# version. The “Marshalers, Marshalers, Marshalers!” section later in this chapter shows examples demonstrating how to write CustomCCW and CustomRCW classes that can be used by such a custom marshaler.

Implementing ICustomAdapter

Both adapter classes in Listings 20.2 and 20.3 had an UnderlyingStream property that provided users of the adapter object the ability to retrieve the original object. This is helpful if the original object contains other functionality that is not made available by the adapter object that only knows about the stream functionality. Such a mechanism is important for adapter objects used by a custom marshaler because users of the custom marshaler don’t have access to the original objects from the other side of the managed/unmanaged transition.

For an illustration of why this can be important, consider the Visual Basic 6 error object (ErrObject). The VB6 runtime wraps an incoming object that implements IErrorInfo, presenting the information in the way expected by Visual Basic 6 programs. However, because there’s no way to access the original object implementing IErrorInfo from a Visual Basic 6 error object, you can’t get a hold of _Exception or any other interfaces implemented by error objects created from .NET exceptions. Had ErrObject been defined with a method like GetErrorInfoObject, .NET-aware Visual Basic 6 programs could have taken advantage of this extra functionality.

The System.Runtime.InteropServices namespace defines an interface that formalizes the practice of exposing the wrapped object. This interface is called ICustomAdapter and has a single parameter-less method—GetUnderlyingObject. As you might expect, GetUnderlyingObject returns a System.Object instance that should be the original object. All adapter objects should implement this interface, so those clients that know to look for it can take advantage of it. Unlike COM aggregation, the outer object can’t dynamically support arbitrary interfaces of the inner object directly, so providing access to the inner object is the next best thing.

An Alternative to Adapter Objects

Using adapter objects can be considered custom marshaling by reference. The term marshal by reference is used elsewhere in the .NET Framework when discussing objects marshaled across application domain boundaries. When an object is marshaled by reference across an application domain, a proxy handles communication with the original object. Similarly, when an object is custom marshaled by reference across the managed/unmanaged boundary, the adapter object acts like a proxy.

But adapter objects aren’t the only means of implementing custom marshalers, and sometimes aren’t even an option. Adapter objects can always be used when transforming from one interface to another. Consider a class such as System.Drawing.Font, however, which we want to custom marshal with IFont, the interface representing a font in COM. The issue here is that Font is sealed (NotInheritable in VB .NET), so it’s impossible to create a derived class that acts like a Font and forwards its implementation to an object implementing IFont.

Instead, when marshaling IFont (a “COM font”) to Font (a “.NET font”), a custom marshaler could simply create an instance of the Font class, initialize it with the state of an IFont object, and pass this new “detached” object as the custom-marshaled IFont. In the reverse direction, an adapter object could still be used to provide an IFont implementation that forwards calls to a Font object. Or, the copying approach could be used here as well: instantiate a StdFont class (a standard OLE Automation class that implements IFont), initialize it with the state of a Font object, and pass this new object as the custom-marshaled Font.

This approach that doesn’t use adapter objects can be considered custom marshaling by value. When an object is marshaled by value across an application domain boundary, it is copied from one domain to another. Similarly, when an object is custom marshaled by value across the managed/unmanaged boundary, the original object’s state is copied to an object of a different but related type.

Figures 20.7 and 20.8 illustrate custom marshaling by value in both directions. They assume that a language like C# or Visual Basic .NET is being used. If custom marshaling by value is performed in Visual C++ .NET, the standard RCW can be eliminated because the COM object (in this case, StdFont) can be used directly by the custom marshaler.

Figure 20.7. Custom marshaling by value from .NET to COM.

Image

Figure 20.8. Custom marshaling by value from COM to .NET.

Image

Custom marshaling by value works naturally for fonts, as it’s easy to imagine initializing a COM font object with the state of a .NET font object, and vice-versa. As long as two font objects have the same name, size, and settings for bold, italic, and so on, the new object should represent the old one just fine. In addition, the System.Drawing.Font class already has methods that facilitate such a transformation, so you don’t have to get and set properties one-by-one to perform the copying. This will be demonstrated in Listing 20.6.

Although the sealed nature of Font requires custom marshaling by value (at least in one direction), it’s sometimes a more attractive approach than custom marshaling by reference anyway. Here’s a list of pros and cons to custom marshaling by value:

PROS:

• Ease in custom marshaler implementation. For a pair of objects that provide a public means for getting/setting their state (or for immutable objects that provide a public means of getting their state), custom marshaling by value can be much easier than writing adapter classes that implement all of an object’s public members and forward the calls appropriately. In addition, existing objects that you instantiate often implement several other interfaces that clients might find handy. For example, the StdFont class implements IFontDisp in addition to IFont.

CONS:

• Poorer performance. The overhead of copying an object’s state at every managed/unmanaged transition can substantially hurt performance. For font objects the copying may not be noticeable, but for streams with large buffers the extra copying can be unacceptable. When marshaling needs to occur in both directions for a single call (as with a by-reference parameter), the copying needs to occur twice.

• Subtlety for consumers. When types are custom marshaled by value, any changes made by the callee to the state of an object do not get reflected to the caller after the call by default. This behavior is often non-intuitive for reference types because they exhibit marshal-by-reference behavior in pure managed code. (The marshal-by-value behavior does match the default behavior for non-blittable reference types when called across the managed/unmanaged boundary, however.) To get changes to an object’s state propagated back to the caller, a signature either must use the custom marshaler on a by-reference parameter or mark a by-value parameter with InAttribute and OutAttribute.

• Limiting. Custom marshaling by value means that providing functionality like ICustomAdapter.GetUnderlyingObject isn’t possible. For example, once an object implementing IFont has been returned by a custom marshaler, the original Font object cannot be accessed (just like the IErrorInfo/ErrObject example).

Regardless of whether you choose custom marshaling by reference or custom marshaling by value, you should document the behavior for consumers of your custom marshaler so they know what behavior to expect in terms of seeing changes to a parameter’s state after a method call (and whether to use InAttribute and OutAttribute).

Marshalers, Marshalers, Marshalers!

Now that we’ve covered all the background information about custom marshaling, it’s time for some examples. First we’ll look at three examples of converting between related COM and .NET types:

System.Drawing.Font and IFont

System.IO.Stream and IStream

System.String and SAFEARRAY

The first example uses custom marshaling by value, as pictured in Figures 20.7 and 20.8. The second example is similar to the custom marshaler already covered in Listings 20.2, 20.3, and 20.4, but uses C++ to demonstrate more direct custom marshaling by reference, as pictured in Figures 20.5 and 20.6. Plus, the second example adds a few more features that the C# StreamMarshaler does not have. The third example isn’t practical like the first two, but demonstrates custom marshaling with something other than an interface on the COM side—a SAFEARRAY.

In addition to these three examples of transforming one type to another, we’ll cover a fourth example that modifies a type while preserving its interface identity. In this example, we’ll see how to use a custom RCW to provide deterministic release of resources held by any .NET object exposed to COM.

Example: Marshaling Between .NET and COM Fonts

Here’s the first example of a custom marshaler that marshals its objects by value. As you’ll see in these listings, custom marshaling by value often involves much less code than custom marshaling by reference because no adapter objects are involved. This example is broken up into two parts:

• The custom marshaler

• A sample consumer and its client

The remaining examples only show custom marshalers, because the consumers of any custom marshalers should be self-explanatory once you see an example of one.

The Custom Marshaler

Listing 20.6 contains FontMarshaler, a complete custom marshaler class that marshals System.Drawing.Font to IFont and vice-versa. This class requires .NET type definitions for the IFont interface and FONTDESC and LOGFONT structures. FONTDESC and LOGFONT are defined in the listing, but the definition of IFont is available on this book’s Web site in the IFont.cs file. This definition is written in C# due to a Visual Basic .NET limitation described in the next chapter, “Manually Defining COM Types in Source Code.” Therefore, you must perform the following command-line steps to compile Listing 20.6:

1. csc /t:library IFont.cs

2. vbc /t:library FontMarshaler.vb /r:IFont.dll /r:System.Drawing.dll

Listing 20.6. FontMarshaler.vb—A Custom Marshaler That Marshals Between .NET Fonts and COM Fonts

Image

Image

Image

Image

To perform the custom marshaling, we need a way to convert a .NET font to a COM font, and vice-versa. System.Drawing.Font doesn’t have a method to construct an instance of itself from an IFont interface, but it does have a method to construct one from a Windows font handle (an HFONT)—Font.FromHFont. Fortunately, IFont has a property called hFont that retrieves a Windows handle to the font object. Therefore, the MarshalNativeToManaged implementation creates a .NET font from the incoming pointer to a COM font by calling Font.FromHFont on Line 23. An interesting point about Font.FromHFont is that it throws an exception if the input font isn’t a TrueType font, because GDI+ doesn’t support fonts that aren’t. A more accommodating custom marshaler could use a heuristic to find a best match TrueType font for any incoming non-TrueType font. In this simple example, we let the exception get exposed to the user.

In the reverse direction, MarshalManagedToNative calls the Windows API OleCreateFontIndirect in Line 50 to create a StdFont object that implements IFont. Creating such an object requires a FONTDESC structure filled with information explaining what kind of font is desired. Lines 39–45 fill in this information with data obtained from the incoming .NET Font instance. Font.ToLongFont is called in Line 34 so we can get information about the font’s weight and character set because this information isn’t available directly from the Font class itself. A font’s size is stored differently in COM fonts than in .NET fonts, so the .NET font size is multiplied by 10,000 in Line 40 to account for the different representation. OleCreateFontIndirect returns an HRESULT, so Line 51 checks for a failure value then, if necessary, throws the appropriate exception using Marshal.ThrowExceptionForHR. By defining the OleCreateFontIndirect PInvoke signature with DllImportAttribute and setting PreserveSig to false, the HRESULT to exception transformation could’ve been handled by the CLR instead. But for the simplicity of this example, Declare is used to define the PInvoke method in Lines 83–86.

The call to OleCreateFontIndirect increments the StdFont object’s reference count, and the call to Marshal.GetComInterfaceForObject increments the object’s reference count again, so CleanUpNativeData calls Marshal.Release twice in Lines 60–61 to properly restore its reference count. Because Font implements IDisposable, and because we’re marshaling it by value, CleanUpManagedData calls IDisposable.Dispose in Line 67.

Lines 89–117 contain the .NET definitions of the FONTDESC and LOGFONT structures. Although FONTDESC is defined as a value type, LOGFONT is defined as a formatted reference type for an extremely subtle reason. The System.Drawing.Font.ToLogFont method called in Line 34 has a System.Object parameter that must be a LONGFONT instance to be filled in with information. Had LOGFONT been defined as a value type, it would be boxed for the call to ToLogFont and any changes to the boxed object’s fields would be lost after the call. If you want the state of a by-value parameter to be changed after the call, it must be a reference type. The use of StructLayoutAttribute in Line 100 not only formats the LOGFONT reference type with sequential layout, but it also sets the character set of the lfFaceName field appropriately.

A Sample Consumer and Its Client

Listing 20.7 contains a .NET Windows Form that uses the FontMarshaler custom marshaler from Listing 20.6 on a property that gets/sets the font of its TextBox control. This form is the consumer of the custom marshaler, and can be built from a command prompt as follows:

vbc /t:library ManagedForm.vb /r:System.Windows.Forms.dll /r:System.Drawing.dll /r:System.dll /r:FontMarshaler.dll

Listing 20.7. ManagedForm.vb—A Windows Forms Control That Uses the FontMarshaler Custom Marshaler

Image

Image

Image

This is a standard Windows Form with a TextBox, a Button for changing the font, and a FontDialog. The important part is emphasized in Lines 31–42—a TextBoxFont property of type Font, marked with the FontMarshaler custom marshaler on both accessors. This property gets and sets the font of the form’s TextBox control.

If .NET clients were to use the ManagedForm class and get/set its TextBoxFont property, custom marshaling would not be involved because there would be no managed/unmanaged transition. The MarshalAsAttribute marking would be ignored and Font instances could be passed back and forth as usual. If COM clients use the ManagedForm class, however, they view the property as enabling them to get/set the font using an IFont instance.

Listing 20.8 contains such a Visual Basic 6 client that instantiates the Windows Forms component and enables a user to invoke the .NET property’s get and set accessors. The combination of these two forms is a testing application that enables you to see the successful custom marshaling of fonts right before your eyes. The application is pictured Figure 20.9.

Figure 20.9. Using FontMarshaler to achieve font interoperability.

Image

Listing 20.8. VB6Form.frm—A Visual Basic 6 Form That Uses the Visual Basic .NET Form

Image

Image

This form is a standard Visual Basic 6 form with a TextBox, three Buttons, and a CommonDialog control used for selecting a font. Line 5 initializes the TextBox to a TrueType font because those are the only fonts supported by the .NET Font type. Line 8 instantiates the Windows Form, and Line 9 makes it visible by calling Show through its class interface.

Lines 12–27 contain the two methods that invoke the .NET property’s accessors. Line 15 calls the custom-marshaled TextBoxFont property get accessor when the user clicks Get .NET Font, and Line 26 calls the custom-marshaled TextBoxFont property set accessor when the user clicks Set .NET Font. Notice how the fact that the property is of type IUnknown doesn’t affect the Visual Basic 6 client code one bit.

The ChangeFontButton_Click method in Lines 29–57 handles the font dialog displayed to the user. Although the dialog presents the user with a choice of font color, color information is not part of the information stored with IFont.

To build this application, you should register the ManagedForm assembly and generate a type library. For example:

regasm ManagedForm.dll /tlb

Then create a new Visual Basic 6 Standard EXE project, reference the exported type library, and paste the code from Listing 20.8. Add the controls pictured in Figure 20.9 to the form and rename them appropriately. Don’t forget that if you plan to run the application within the Visual Basic 6 IDE, you should either add the ManagedForm assembly (and its dependent FontMarshaler and IFont assemblies) to the GAC, or simply use REGASM.EXE’s /codebase option when performing the registration.

Example: Marshaling Between System.IO.Stream and IStream

This example contains two listings—one with the contents of CustomRCW and one with the contents of CustomCCW. The CustomRCW class serves the same purpose as ComStream in Listing 20.2 except that it uses the power of mixed-mode C++ programming to wrap a COM object directly rather than wrapping its RCW. The CustomCCW class serves the same purpose as ManagedIStream in Listing 20.3 but is actually a COM class wrapping a .NET Stream object.

These two classes can be used with a custom marshaler just like TypicalCustomMarshaler in Listing 20.5, replacing UnmanagedType with IStream and ManagedType with Stream. So the two important ICustomMarshaler methods would look like the following:

Object* MarshalNativeToManaged(IntPtr pNativeData)
{
  if (pNativeData == IntPtr::Zero) return NULL;
    return new CustomRCW(
      static_cast<IStream*>(pNativeData.ToPointer()));
};

IntPtr MarshalManagedToNative(Object* ManagedObj)
{

  if (ManagedObj == NULL) return IntPtr::Zero;
  CustomCCW *ccw = new CustomCCW(static_cast<Stream*>(ManagedObj));
  IStream* intf = NULL;
  ccw->QueryInterface(IID_IStream, (void**)&intf);
  return (IntPtr)intf;
}

The resultant custom marshaler can be found on this book’s Web site in the file StreamMarshaler.cpp.

Listing 20.9 contains the definition of CustomRCW, which wraps an IStream implementation in a Stream-derived object.

Listing 20.9. The CustomRCW Adapter Class That Exposes IStream as Stream

Image

Image

Image

Image

Image

Image

Image

Image

Line 2 includes the objidl.h header file, part of the Windows Platform SDK, because it contains an unmanaged definition of the IStream interface. The class is defined as a managed class with the __gc in Line 9. Besides deriving from Stream, it implements ICustomAdapter, as all adapter classes should.

The CustomRCW constructor in Lines 13–23 is passed an unmanaged COM IStream interface pointer. Notice, however, that rather than storing the IStream pointer as a class member to be used directly by CustomRCW’s other methods, it calls GetObjectForIUnknown in Line 17 to construct a standard RCW for the COM object. This RCW is used by the other methods, which call GetComInterfaceForObject to extract the original IStream interface pointer wrapped by this RCW. Why does CustomRCW do this extra wrapping and unwrapping instead of storing and using the IStream pointer directly? The answer is that storing and using a COM interface pointer directly is not context-safe. For example, if managed code uses CustomRCW from a different apartment than the one it was created in, calling a cached COM interface pointer directly would violate COM rules. The easiest way to ensure context safety is to let the CLR handle cross-context COM marshaling automatically. When you use a standard RCW, calls to the COM object always occur in the correct context. Of course, you could alternatively use your own mechanism to ensure context-safety that doesn’t involve standard RCWs, such as COM’s global interface table (GIT).

Tip

Rather than storing the unmanaged COM interface pointer you’re wrapping as a member of a custom RCW, you should store its standard RCW to ensure that it is always used in a context-safe manner. Although this contradicts Figure 20.6, which illustrates custom RCWs written in C++ as not wrapping standard RCWs, doing so can still perform better than writing a custom marshaler in C# or Visual Basic .NET in some cases because the raw COM object can be extracted from the standard RCW before calling its members.

The Read method in Lines 39–76 is noticeably different from the C# ComStream.Read method in Listing 20.2. In Line 56, the incoming .NET array is pinned so the address of the object can be passed directly to IStream.Read in Line 64. The call to GCHandle.Alloc is placed inside a try...finally block so the allocated GC handle always gets freed. Also inside the try...finally block, Lines 59–60 extract the IStream COM interface pointer from the standard RCW created in the constructor using GetComInterfaceForObject (which increments the reference count), and Line 72 releases the pointer. This same pattern is used in every method that needs to call a member of IStream.

Because a pointer to the pinned array is directly accessible, we can support non-zero offsets without having to perform any copying, unlike in Listing 20.2. (Listing 20.2 could have achieved the same effect as this listing by using a custom .NET definition of IStream that uses the ArrayWithOffset value type. See Appendix A, “System.Runtime.InteropServices Reference,” for more information about this type.) Line 64 simply adds the offset to the pointer value. Lines 66–67 check the HRESULT returned from the call to IStream.Read and use the handy Marshal.ThrowExceptionForHR method to throw an exception just like the standard Interop marshaler would if it were throwing an exception for a failure HRESULT. If the IStream.Read implementation sets rich error info via IErrorInfo, the thrown exception gets automatically populated with this information.

Tip

Using Marshal.ThrowExceptionForHR is needed throughout a custom RCW implementation to properly transform failure HRESULTs and additional IErrorInfo information into exceptions, unless you plan to provide a custom mapping from HRESULT values to exception types. Although Listing 20.9 checks to see if a returned HRESULT has a failure value before calling Marshal.ThrowExceptionForHR, this actually isn’t necessary because this method only throws an exception if the HRESULT has a failure value. Still, leaving the check makes the code easier to follow (and faster in non-failure cases).

The Write method in Lines 80–116 is structured just like the Read method. The get_Length property accessor in Lines 151–176 has an interesting detail that’s worth pointing out. The unmanaged STATSTG struct is used by its other name of tagSTATSTG because using STATSTG would be ambiguous between the unmanaged struct defined in objidl.h and the .NET definition of the same struct in System.Runtime.InteropServices. The only other way around this problem would be to omit the using namespace System::Runtime::InteropServices directive from Line 6, but doing so would make the code practically unreadable due to the abundance of System::Runtime::InteropServices qualifications that would litter the code. Doing mixed-mode programming in Visual C++ .NET is filled with little nuances such as this to appease both managed and unmanaged worlds simultaneously.

Listing 20.10 contains the definition of CustomCCW, which wraps a Stream implementation in a COM object that implements IStream. It has a few special cases to provide more functionality if the object being wrapped is an instance of FileStream.

Listing 20.10. The CustomCCW Adapter Class That Exposes Stream as IStream, with Extra Support for FileStream

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Line 10 begins the definition of the CustomCCW class that implements IStream. It can’t implement IDisposable or ICustomAdapter like the CustomRCW class does in Listing 20.9 because COM classes can’t directly implement .NET interfaces using mixed-mode C++ programming. Still, it implements a custom GetUnderlyingObject method (Lines 74–77) for the benefit of COM clients. (Alternatively, it could implement the exported ICustomAdapter interface defined in MSCORLIB.TLB, which would involve returning the IStream interface pointer inside a VARIANT.)

An important aspect of this listing is the use of the gcroot template on Line 414. Visual C++ .NET does not allow an unmanaged class to contain a managed member (a __gc pointer type), so defining the originalStream field of type Stream would cause a compilation error. Fortunately, the vcclr.h header file referenced in Line 3, which contains helper functions specifically for C++ managed extensions, defines a gcroot type-safe wrapper template to use specifically for this purpose. This template provides the illusion of having a managed member of an unmanaged class, but in reality an integer representing a GCHandle is being stored inside the unmanaged object. The template has a destructor that frees the GCHandle.

Tip

In Visual C++ .NET, use the gcroot template defined in vcclr.h whenever you require a managed member of an unmanaged class. Through clever wrapping of a GCHandle, you can use a gcroot<Type*> variable just as if it were defined as Type*. This is sometimes called a virtual __gc pointer.

The constructor in Lines 14–31 differs from the ManagedIStream constructor in Listing 20.3 in two ways. First, because CustomCCW is a COM class, it must maintain a reference count, so Line 19 initializes it to zero. Second, Line 22 checks to see if the Stream object being wrapped is also a FileStream. The boolean isFileStream variable is used later in the listing to provide more functionality specific to file streams. Lines 34–71 contain a standard implementation of IUnknown’s methods. The InterlockedIncrement and InterlockedDecrement Windows APIs are used to provide thread-safe reference counting.

The IStream methods follow the opposite pattern as in the previous listing—each method implementation is contained inside a try...catch block and Marshal.GetHRForException is used to return the failure HRESULT corresponding to any .NET exception that may be thrown. The CopyTo implementation in Lines 205–255 differs from the CopyTo implementation from Listing 20.3 because it calls CustomCCW.Read and CustomCCW.Write rather than calling the underlying stream’s Read and Write methods directly. This works more naturally in this case because calling Stream.Read and Stream.Write would require more lines of code. Marshal.AllocHGlobal is used in Line 222 to allocate memory for the temporary buffer and Marshal.FreeHGlobal frees it in Line 249.

The LockRegion, UnlockRegion, and Stat methods are the ones that do extra work if the wrapped Stream object is a FileStream instance. Although generic Stream objects don’t support any locking or unlocking functionality, FileStream objects do. FileStream has a Lock method called by LockRegion in Lines 285–310, and an Unlock method called by UnlockRegion in Lines 313–338. In Stat (Lines 343–386), more information in the structure can be filled in that applies to file streams only—the stream’s creation time, last access time, last write time, and possibly its name. The Stat method uses _FILETIME in Lines 359–361, rather than FILETIME, for the same reason the previous listing used tagSTATSTG—to avoid a name collision with System.Runtime.InteropServices.FILETIME.

Example: Marshaling With Arrays

So far all the examples have custom marshaled .NET types to COM interfaces. This example demonstrates custom marshaling a .NET string to a SAFEARRAY instead. This isn’t a practical custom marshaler like the others, but it’s an easy-to-follow example of custom marshaling to something other than an interface pointer on the COM side. The marshaling is accomplished by value, so Listing 20.11 contains the complete custom marshaler in the StringMarshaler class, written in C++. This listing can be compiled from a command prompt as follows:

cl /CLR /LD StringMarshaler.cpp /link oleaut32.lib

Listing 20.11. The StringMarshaler Custom Marshaler Marshals .NET Strings to COM SAFEARRAYs and Vice-Versa

Image

Image

Image

Image

Line 3 includes oleauto.h, a header file containing the definitions of several SAFEARRAY-related functions. The System.Text namespace is used in Line 6 for the UnicodeEncoding class. The StringMarshaler constructor initializes its encoding member on Line 19, used by MarshalManagedToNative to convert a .NET string to an array of bytes. The first parameter passed to the UnicodeEncoding constructor chooses big-endian or little-endian ordering based on the current operating system. Passing false for the second parameter turns off the byte order mark (BOM), which we don’t want to include as part of the string data.

Lines 24–62 contain the implementation of MarshalNativeToManaged. After obtaining a pointer to the SAFEARRAY by calling ToPointer on the input IntPtr value, several SAFEARRAY APIs defined in oleauto.h are called using the IntPtr value. Lines 35–40 ensure that the incoming array has only a single dimension and contains byte elements. The SafeArrayGetVartype API only works reliably on SAFEARRAYs created using SafeArrayCreateEx or SafeArrayCreateVectorEx, so checking for an element size of 1 byte instead is a sure-fire way to check the type of elements.

Lines 43–49 get the array’s upper and lower bounds, so the contents of the array can be copied regardless of the bounds. Lines 52–53 create a new .NET array with the correct number of elements, then the loop in Lines 56–59 fill the new array with the SAFEARRAY’s data. This is done simply so it can be passed to the static BitConverter.ToString method in Line 61.

MarshalManagedToNative is defined in Lines 64–100. Line 81 obtains a .NET array for the string by calling the UnicodeEncoding.GetBytes method. Then a new SAFEARRAY is created using SafeArrayCreateVectorEx in Lines 86–87, and the data from the .NET array is copied to the SAFEARRAY in Lines 90–93. Line 99 returns an IntPtr value representing the pointer to the SAFEARRAY. The implementation of CleanUpNativeData in Lines 102–106 frees the SAFEARRAY allocated in Lines 86–87 with a call to SafeArrayDestroy.

Example: Providing Deterministic Release of Resources

The standard CCW does not provide a means for deterministic release of resources other than having COM clients call IDisposable.Dispose explicitly (if the .NET object implements IDisposable). This can be problematic when .NET objects implementing COM interfaces are exposed to pre-.NET COM clients that can’t be modified. Such clients may rely on the fact that resources must be released in a timely manner, and calling an additional method is not an option. When defining a custom CCW, however, you can insert a call to IDisposable.Dispose that executes when the CCW’s reference count reaches zero.

Caution

Disposing a .NET object when its CCW’s reference count reaches zero should not be done for objects that can potentially be used simultaneously by COM clients and .NET clients. Any .NET clients using the object are invisible to the object’s CCW, so the object could get disposed prematurely while others are still using it.

Another problematic situation occurs if the same .NET instance is passed to COM using custom marshaling with some signatures and standard marshaling with other signatures (due to a consumer not using the appropriate MarshalAsAttribute everywhere the type is exposed). In this case, COM clients using an interface pointer for the standard CCW can become victims of premature disposal because the custom CCW cannot track this usage.

Therefore, the approach demonstrated by this custom marshaler should only be used in controlled situations.

Writing a custom marshaler to add dispose functionality must be done on an interface-by-interface basis, because custom marshalers must statically implement interfaces exposed to COM users. In this example, the custom marshaler is written for the IPersistFile COM interface.

The custom marshaler class looks just like the TypicalCustomMarshaler class in Listing 20.5, but with the following implementation of MarshalNativeToManaged and MarshalManagedToNative:

Object* MarshalNativeToManaged(IntPtr pNativeData)
{
  if (pNativeData == IntPtr::Zero) return NULL;

  // Just use the standard RCW
  return Marshal::GetObjectForIUnknown(pNativeData);
};

IntPtr MarshalManagedToNative(Object* ManagedObj)
{
  if (ManagedObj == NULL) return IntPtr::Zero;
  CustomCCW *ccw = new CustomCCW(static_cast<UCOMIPersistFile*>(ManagedObj));
  IPersistFile* intf = NULL;
  ccw->QueryInterface(IID_IPersistFile, (void**)&intf);
  return (IntPtr)intf;
};

The complete source code is available on this book’s Web site. No special marshaling is required in the native-to-managed direction because all we care about is customizing the exposure of a .NET object to COM. Therefore, calling Marshal.GetObjectForIUnknown inside MarshalNativeToManaged acquires the standard RCW for the COM object.

The C++ CustomCCW adapter class used by the custom marshaler in MarshalManagedToNative is implemented in Listing 20.12. This is unique from the other custom marshalers because the source and target types appear to be the same to their users—System.Runtime.InteropServices.UCOMIPersistFile on the .NET side and IPersistFile on the COM side.

Listing 20.12. Disposing an Object on Its Last Release Inside a Custom CCW

Image

Image

Image

Image

Image

This listing is straightforward because the IPersistFile methods simply call their corresponding UCOMIPersistFile methods in Lines 71–146. The implementation mimics the work that would normally be done by the standard Interop Marshaler. The unique part of the listing occurs inside the implementation of IUnknown.Release (Lines 53–60). If the wrapped object implementing UCOMIPersistFile also implements IDisposable, the object will be disposed when the CCW reference count equals zero. Otherwise, object release is performed the standard way.

The adapter class doesn’t bother exposing a GetUnderlyingObject method because this custom CCW is meant for COM clients that aren’t .NET-aware. If they could be modified to call GetUnderlyingObject, then they could be modified to call IDisposable.Dispose and avoid the need for the custom marshaler in the first place.

Limitations

Even when implementing a custom marshaler (and possibly adapter objects) using Visual C++ .NET, the custom marshaling infrastructure has some limitations. Some of these limitations were mentioned earlier in the chapter, but here’s a summary:

• Value types can’t be custom marshaled. Only reference types are supported in version 1.0 of the .NET Framework. This applies to both the .NET and COM sides of marshaling so, as an example, custom marshaling a System.Decimal type to anything unmanaged or custom marshaling anything managed to a by-value VARIANT type cannot be done. Note that you can custom marshal a boxed value type defined as an Object type, or a formatted reference type. The formal type of the unmanaged parameter does not matter as long as it’s pointer-sized, so you could custom marshal a managed type to a pointer to an unmanaged structure (similar to the SAFEARRAY example). Attempting to use a custom marshaler on a parameter, return type, or field defined as a value type causes a MarshalDirectiveException to be thrown at run time.

• The type library exporter can’t faithfully represent the unmanaged view of a custom marshaled type in an exported type library. This isn’t a big limitation for classes or interfaces being exported as IUnknown*, but the behavior of exporting strings or arrays as long can be problematic.

• The type library importer can’t be taught to use additional custom marshalers. The importer makes use of a handful of built-in custom marshalers shipped with the .NET Framework, but there’s no way to plug in additional custom marshalers. Adding custom marshaling to an Interop Assembly would have to be done using the techniques in Chapter 7 instead.

• Only uses of a type can be marked as custom marshaled rather than a type itself. Because there’s no way to force a data type to always be custom marshaled when used, MarshalAsAttribute must be applied to every parameter, return type, and field that desires custom marshaling. A side effect of this is that if you export a class called MyStream derived from Stream, there’s no way to get the exporter to transform it into a MyStream coclass that implements IStream. Instead, any uses of MyStream as a parameter, return type, or field would need to be marked with the custom marshaler. The same applies to import as well: a coclass implementing IEnumVARIANT is imported as a class implementing IEnumVARIANT, even though any IEnumVARIANT parameters, return types, or fields in a type library are changed to a custom-marshaled IEnumerator.

You can usually work around these limitations by expanding the scope of the custom marshaler. For example, because you can’t directly custom marshal a .NET parameter type to a VARIANT with type VT_RECORD, you can instead custom marshal the entire interface containing the method with the parameter you need to custom marshal.

Conclusion

Custom marshaling is useful for working around limitations of the built-in COM Interoperability support. The .NET Framework ships with a CustomMarshalers assembly containing a handful of custom marshalers used by the CLR. These custom marshalers marshal between ITypeInfo and System.Type, IEnumVARIANT and IEnumerator, IDispatchEx and System.Runtime.InteropServices.Expando.IExpando or System.Reflection.IReflect (based on a cookie value), and finally IDispatch and IEnumerable. The ITypeInfo/System.Type custom marshaler marshals by value, but the others all marshal by reference, using the adapter object approach covered throughout this chapter.

Tip

All the adapter objects used by the .NET Framework’s custom marshalers implement ICustomAdapter. For an example of how this can be useful, consider a COM enumerator object (implementing IEnumVARIANT) that holds onto limited resources until it’s destroyed. Suppose you want to use this object in managed code (which, thanks to a custom marshaler, now appears to implement IEnumerator), but you also want to release it as soon as you’re finished. For example, in C#:

Image

Calling Marshal.ReleaseComObject on the enumerator object returned by GetEnumerator doesn’t work because it’s not the original COM object. It’s not even a COM object at all; it’s a .NET adapter object returned by the built-in IEnumVARIANT/IEnumerator custom marshaler! To release the original COM object, you must cast the object implementing IEnumerator to ICustomAdapter. Then you can call Marshal.ReleaseComObject on the object returned by GetUnderlyingObject:

Image

Using C#’s foreach statement or VB .NET’s For Each statement should not be used with such COM enumerators, because the object implementing IEnumerator is never directly exposed to you.

The IStream/Stream custom marshaler covered in this chapter fulfills a common need when using COM Interoperability, but there are numerous other custom marshalers you could create for common scenarios. For example, you could write a “visibility marshaler” that transforms COM-invisible .NET classes into visible COM classes. Of course, such an approach would have to be done on a type-by-type basis, because a generic custom marshaler can’t be written unless you expose an IDispatch implementation that internally uses reflection on the wrapped .NET type. As another example, you could support marshaling a .NET array to a varying conformant array, using the cookie string to enable users to specify first_is and last_is information. Or, you could create a custom RCW that stores the most recently returned HRESULT in thread local storage (TLS) and expose it to managed code as a LastHResult property (similar to Marshal.GetLastWin32Error). With such a property, success HRESULTs could be obtained without resorting to PreserveSig to change the look of the .NET signature.

Another useful custom marshaler would be one that transforms an ADO.NET DataSet into an ADO Recordset and vice-versa. In one direction, you can use System.Data.OleDb.OleDbDataAdapter.Fill to convert a Recordset to a DataSet. In the other direction, you could use DataSet.GetXml to obtain an XML representation of the DataSet, use an XSLT transform to convert it into the XML format usable by ADO, then load it into an ADO Recordset. The possibilities are endless!

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

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