Marshaling is employed by several technologies within the Microsoft® .NET Framework, including Remoting and COM Interop. Within Remoting and COM Interop, the .NET Framework allows programmers to tailor or customize client object presentations through the use of custom marshaling. For example, programmers typically think of implementing interfaces statically at compile time; however, occasionally you need a class that can dynamically implement the public interface of another referenced class instance. Such situations typically arise when performing method interception—intercepting method calls to a target object in order to perform some behavior completely orthogonal to the target object itself. Many scenarios probably come to mind. Method tracing, authentication, authorization, and transaction semantics are just some examples. You can probably think of some other implementations, such as COM+ and the Universal Delegator. With the .NET Remoting technology, you can implement dynamic public interfaces through the use of specialized proxies.
Similarly, it may be useful in some .NET-based client applications that use COM Interop to change or extend the public interface of legacy or third-party COM components. This scenario can occur when a legacy COM component doesn't or can't implement a newer interface used by a .NET-based client. Extended interface support for these client apps can be accomplished by using the COM custom marshaling capabilities in the .NET Framework.
Finally, there are advanced cases in which a combination of extended .NET Remoting proxies and COM custom marshaling is in order. These situations exemplify the power and flexibility of the .NET Framework application environment. This article examines some of the advanced aspects of the customized marshaling capabilities available in .NET Remoting and COM Interop.
Of all technologies that the .NET Framework encompasses, Remoting ranks as one of the most interesting, challenging, and flexible. Remoting is itself comprised of many individual technologies such as contexts, messages, channels, proxies, sinks, and configuration. Of all of these concepts, the flexibility that Remoting proxies provide might get the least attention, even though coverage of the topic of Remoting proxies is quite extensive.
As its name implies, a Remoting proxy is a stand-in for a remote object. To a caller, it looks and acts like the actual remote object. A Remoting proxy hides all of the details of transporting and executing a method call issued from a caller to the remote object.
For example, in an inter-AppDomain Remoting scenario, one that perhaps crosses machine boundaries, a Remoting proxy is the first step towards getting a method call forwarded to the remote object through the Remoting channel. The Remoting proxy obscures all details of such behavior from the caller. For instance, in Figure 1 the caller views method calls being executed directly on the target object. These method calls are actually executed against a proxy object, not the original. The proxy hides the underlying details of the method-call transport mechanism across message sinks and the channel to the remote object itself.
In the Microsoft .NET Framework there are two classes that work together to form the Remoting proxy: System.Runtime.Remoting.Proxies.TransparentProxy and System.Runtime.Remoting.Proxies.RealProxy. The client of a remote object instance always communicates with that remote object through an instance of a TransparentProxy, which gives the caller the appearance of the target object.
The TransparentProxy instance presents the interface of the remote object through close cooperation with a peer object—an instance of the RealProxy class. The RealProxy class is the source of information about and communication with the actual remote object, but it's an abstract class that must be extended. This override allows a derived class to properly configure the RealProxy. TransparentProxy, on the other hand, cannot be extended and derives its operation strictly from information obtained from and services provided by the associated RealProxy. A caller obtains an instance of the TransparentProxy class from the RealProxy in which the TransparentProxy is contained.
The following code defines the three basic elements that are involved in extending the RealProxy class: the constructor, the virtual CreateObjRef method override, and the abstract Invoke method override:
public abstract class RealProxy
{
protected RealProxy( Type classToProxy );
public virtual ObjRef CreateObjRef( Type requestedType );
public abstract IMessage Invoke( IMessage message );
•••
}
The key to extending a RealProxy is specifying the type of object that the resulting TransparentProxy will represent. A RealProxy-derived class accomplishes this type specification through the protected constructor of the RealProxy class. This constructor takes a single parameter, a System.Type instance called classToProxy. This parameter represents the type or class of object that is to be proxied. That is, the TransparentProxy will customize its behavior to act as an instance of the type specified in the classToProxy parameter. While the constructor of the RealProxy takes a generic System.Type object as a parameter, class ToProxy must be one of two permissible types or the RealProxy constructor will throw a System.ArgumentException.
The first permissible System.Type argument to the RealProxy constructor is an instance of a System.MarshalByRefObject type. MarshalByRefObject is the base class for any and all objects that can be marshaled by reference from one AppDomain to another. It goes without saying that remotely referenced objects require proxy support. This is especially true given all the implementation details necessary to get a method call from one AppDomain to another, as illustrated in Figure 1.
In general, the TransparentProxy for a System.MarshalByRefObject will appear to a caller as an actual instance of the remote type. In the case of remotely referenced objects, this makes perfect sense. Passing an instance of the System.MarshalByRefObject type into the RealProxy constructor introduces some subtle concerns, including propagation of the proxy to the new AppDomain and the possible performance implications of this action.
The second permissible type of RealProxy constructor argument is an interface. In this case, the resulting TransparentProxy will appear to a caller as an object implementing the interface type specified in the RealProxy constructor. Such a caller view presents some interesting implementation possibilities in extending the RealProxy class. The first obvious possibility is that this new public view can be applied to objects whose type is not derived from System.MarshalByRefObject. The other notable implementation possibility is that a TransparentProxy can expose a completely different public view than that exposed by the actual target class. This article will expand further on these implementation possibilities in subsequent sections.
The next task in extending a RealProxy is overriding the CreateObjRef method. The .NET Framework calls CreateObjRef whenever it attempts to remote an object reference. Considering only the case where the RealProxy constructor argument is an interface type, CreateObjRef should be overridden as follows:
public override ObjRef CreateObjRef( Type requestedType )
{
throw new NotSupportedException();
}
As emphasized here, a class implementing the interface type exposed by the TransparentProxy does not necessarily inherit from System.MarshalByRefObject. Remoting of such a class is not possible. As a result, the creation of an ObjRef is not supported.
The final task in extending a RealProxy, which must be done for all derived classes, is to override the abstract Invoke method. Invoke initiates the transmission of a method call to a remote object. The type of this transmission may vary depending on the particular remoting situation. For example, when remoting a method call between AppDomains, Invoke transmits the method call through a Remoting channel. In other cases, a method call can reach the target object through other means, such as direct reflection.
The TransparentProxy forwards the actual method being called to the overridden Invoke method on a derived RealProxy implementation. The form or representation of an actual method call is that of a Remoting message. In the case of Invoke, a Remoting message contains all the information necessary to communicate a method call to a remote object, including the actual method being called. The goal of the Invoke implementation is to transmit the actual method call message to the remote object, receive the results, and form a return message that encompasses the return value and modified parameter values of the method call.
The code segment that is shown in Figure 2 combines all of the elements necessary to form a RealProxy-derived class called MyProxy. When using the techniques described here, this code segment provides a general template for the extension of the RealProxy class. You should note that the code segment contains a public static method called Marshal from which a caller can obtain a TransparentProxy instance that wraps a specified object instance. The Marshal method removes the burden of callers having to manually obtain the TransparentProxy instance by dealing directly with the derived RealProxy.
The name of the static method, Marshal, is also significant. Calling the Marshal method represents a context boundary in which a caller's view of the original object may be different from that view presented by the resulting object. Herein lies one aspect of custom marshaling within .NET Remoting.
Because it contains the public static Marshal method, the constructor of MyProxy is private. Additionally, the constructor takes one parameter, an object instance against which the derived proxy will operate in the overridden Invoke method. The constructor of MyProxy initializes the base RealProxy with the interface type, IMySpecial. This action results in the TransparentProxy instance supporting the interface IMySpecial.
I've left out the details of the Invoke method for the time being, but I'll cover them in subsequent samples. It should be apparent to you that method call interception on the client or caller side begins in a RealProxy-derived class. The remainder of this article builds on this method call interception capability that is inherent in RealProxy-derived classes.
As previously described, constructing a RealProxy with an interface type results in a TransparentProxy presenting that interface as the public view of the underlying object. What's more, this public view may be different than that of the underlying object. Imagine for a moment the possibilities. A RealProxy-derived class can intercept one method and potentially forward that call to the target object as another, entirely different method. A further possibility is that the RealProxy-derived class can intercept a method call and implement it locally without forwarding it to the target object at all. It is also entirely possible to swap out the target object held by the RealProxy-derived object while allowing current clients to hold onto the existing proxy.
A RealProxy-derived class can take its stealthiness one step further by implementing the System.Runtime.Remoting.IRemotingTypeInfo interface. When implemented by a RealProxy-derived class, IRemotingTypeInfo provides additional type information to the TransparentProxy. More specifically, the TransparentProxy calls IRemotingTypeInfo.CanCastTo to determine whether or not a caller can cast to a particular interface type.
The decision of whether or not to allow specific casts depends on the RealProxy-derived class. A RealProxy-derived class can allow a cast to any interface, intercept a method call on that interface, and direct that method call to another method on a completely different interface. Furthermore, a RealProxy-derived class could disallow some casts and restrict the methods that are accessible on the target object.
It should be noted that the capabilities I just outlined apply to both remotely referenced classes (those that derive from System.MarshalByRefObject) and non-remotable types.
The sample in Figure 3 wraps a target object in an Interposer. It follows the basic template for extending RealProxy, as shown in Figure 2. The TransparentProxy obtained from an Interposer supports the IInterposed interface, which allows a caller to retrieve the target object instance. The Interposer class implements the IInterposed interface locally within the Invoke method. Also, the implementation of IRemotingTypeInfo ensures that all caller-issued interface cast attempts against a TransparentProxy of an Interposer succeed only if the target object implements them. Even though I've already described some method interception implementations, the sample in Figure 3 forwards method calls to the target object using reflection. This call will either succeed or fail with the results returned. The Invoke method implementation in Figure 3 can be expanded to implement some of the more sophisticated method interception capabilities described previously.
COM custom marshaling allows you to customize the client view of objects across the managed/unmanaged code boundary. In his information-packed tome on COM Interop, .NET and COM: The Complete Interoperability Guide (SAMS, 2002), Adam Nathan devotes an entire chapter to the topic of COM custom marshaling. While this article is not meant to rehash the coverage contained in Adam's book, it may be helpful to summarize the premise behind COM custom marshaling.
In general, the .NET Framework COM Interop layer hides all that is COM. The Framework wraps COM objects that are accessed from the managed environment in what's called a runtime-callable wrapper (RCW). From the point of view of a .NET caller, an RCW makes a COM object look like any other .NET-based object. An RCW, in essence, acts as a proxy to a COM object. In a similar way, the .NET Framework wraps .NET-based objects in something called a COM-callable wrapper (CCW) to make them accessible to COM clients. The CCW exposes the public interfaces implemented on a given .NET object.
COM custom marshaling allows a programmer to tailor the view presented by an RCW or CCW. Such customization can be useful in situations where a programmer wants to enhance or change object functionality without affecting legacy code. As was the case when examining Remoting proxies, customized RCWs and CCWs can provide behavior completely orthogonal to the target object. Furthermore, COM custom marshaling permits customization of RCWs and CCWs at the method level, so customization can vary depending on the particular method called.
As an example, consider a legacy, COM-based arithmetic library. The library supports a number of calculator-like classes. One of these classes is an Adder, which returns the sum of two integers. The following IDL-based interface describes the function of the Adder:
[
object,
uuid(A67A7777-B16E-45D3-953B-F5915E3EBFBB),
oleautomation
]
interface IAdder : IUnknown
{
HRESULT Add( [in] int a1, [in] int a2, [out, retval] int *sum );
};
A COM client of the arithmetic library obtains an instance of an Adder through a calculator factory instance. The calculator factory has an IDL-based interface like the following:
[
object,
uuid(0A4043BF-D9D0-47EB-AC23-4D5E62154A7C),
oleautomation
]
interface ICalculatorFactory : IUnknown
{
HRESULT CreateAdder( [out, retval] IAdder **adder );
};
Now suppose you've been asked to change the Adder function itself when called via the common language runtime (CLR)—instead of returning the sum of two integers, the Adder is to return the sum for a vector of integers using the legacy COM library. In addition, suppose that a further requirement suggests that a change to the legacy library is not permitted.
You can implement the new Adder class through the use of COM custom marshaling, creating a customized RCW for the Adder. The implementation requires you to redefine the existing interface, define an Adapter class, and define the custom marshaler. In the following sections, I will describe these steps in detail.
The requirements for the new Adder class specify that an instance must return the sum for a vector of integers. So, given an instance of the calculator factory, the programmer's job is to provide a caller with an Adder whose public interface accepts an array of integers and returns the sum as an integer. Such a change to the calculator factory interface flies in the face of the legacy calculator factory interface provided by the type library import process.
The process of importing a type library for the legacy COM-based calculator library generates a COM Interop assembly. In this example, the COM Interop assembly is called CalculatorLibraryLib. This assembly provides a .NET-based client with all the metadata required to instantiate and use objects within the legacy library. However, the Adder returned by the calculator factory simply returns the sum of two integers. The first change requires that the calculator factory interface provide an Adder class that supports returning the sum for a vector of integers.
Because the other requirement for the new Adder class stipulates that the legacy library can't be changed, you have to resort to another technique to change the return type of the ICalculatorFactory.CreateAdder method. You can make this change through the use of the ComImport attribute, which lets you define the exact binary representation of a COM interface. So the interface for the calculator factory can now be defined within the .NET Framework environment as follows:
[
ComImport,
Guid( "0A4043BF-D9D0-47EB-AC23-4D5E62154A7C" ),
InterfaceType( ComInterfaceType.InterfaceIsIUnknown )
]
public interface INewCalculatorFactory
{
[return: MarshalAs(
UnmanagedType.CustomMarshaler,
MarshalTypeRef = typeof( AdderMarshaler ) )]
NewAdder CreateAdder();
}
There are three important points of interest in this interface definition besides the use of the ComImport attribute. The first is that the interface Guid and IUnknown interface inheritance is the same as that specified in the original IDL definition of the interface. Note that the interface name is also different from the original IDL definition. Such a name change is not strictly necessary, but it helps to disambiguate references in this article pertaining to the original IDL definition versus the new C# definition.
The second point of interest is that the return type from CreateAdder is no longer IAdder. Instead, CreateAdder returns a new type called NewAdder. The MarshalAs attribute specified for the return type of CreateAdder facilitates the return of the NewAdder instance. The parameters to the MarshalAs attribute state that a COM custom marshaler instance of type AdderMarshaler will be used to generate the NewAdder instance.
The third point of interest is that a programmer specifies COM custom marshaling on a per-method-parameter basis. That is, custom RCWs and CCWs can only be used when required. In general, there is no way to specify the use of custom marshaling on a per-type basis.
You can refer to the NewAdder type as an Adapter class. An Adapter class is an intermediate type that supports the new public view which the Adapter class can implement using any reasonable approach. Typically, it will use the functionality of the legacy object to implement the new public view. Such will be the case with the NewAdder implementation shown in Figure 4. In effect, the Adapter class is an intermediary or proxy that stands between a caller and the ultimate target CCW or RCW.
The constructor for a NewAdder takes as input a single parameter that specifies the legacy instance responsible for generating the vector summation. The COM custom marshaler will supply this legacy instance. The following section describes the complete actions of the COM custom marshaler. The public Add method takes as input a vector of integers and uses the legacy COM Adder to do the real work.
The definition of a COM custom marshaler is the final step in completing the implementation of the new Adder class. The custom marshaler is responsible for generating the proper transition environment between the managed and unmanaged worlds. This transition environment is either a CCW or RCW Adapter object.
A COM custom marshaler has two implementation requirements. The first is that a COM custom marshaler must implement the interface, ICustomMarshaler, which can be found in the System.Runtime.InteropServices namespace. The following code snippet describes the ICustomMarshaler interface:
interface ICustomMarshaler
{
IntPtr MarshalManagedToNative( object ManagedObj );
object MarshalNativeToManaged( IntPtr pNativeData );
void CleanUpManagedData( object ManagedObj );
void CleanUpNativeData( IntPtr pNativeData );
int GetNativeDataSize();
}
The key methods in this interface are MarshalManagedToNative and MarshalNativeToManaged. The .NET Framework calls MarshalManagedToNative when a method parameter makes a transition from the .NET-managed environment to the unmanaged COM environment. A call to MarshalManagedToNative results in the creation of a CCW Adapter object. Likewise, the .NET Framework calls MarshalNativeToManaged when a method parameter makes a transition from the unmanaged COM environment to the .NET-managed environment. In this particular case, a call to MarshalNativeToManaged will result in the creation of an RCW Adapter object.
The .NET Framework calls the methods CleanUpManagedData and CleanUpNativeData after any of the previously described marshaling calls have been completed. The .NET Framework invokes CleanUpManagedData to release any resources that may have been allocated during the MarshalManagedToNative call. Likewise, the .NET Framework invokes CleanUpNativeData to release any resources that may have been allocated during the MarshalNativeToManaged call. In general, you should find marshalers to be essentially stateless. As a result, releasing managed or unmanaged resources should not be required.
Finally, GetNativeDataSize returns the size required to marshal a value type parameter. The current release of the .NET Framework (version 1.1) does not support the marshaling of value types. This method should simply return -1.
The second requirement for a COM custom marshaler is the implementation of the following method:
static ICustomMarshaler GetInstance( string cookie )
A typical implementation of GetInstance makes the custom marshaler a singleton. A singleton is used because the .NET Framework calls GetInstance only once per custom marshaler type, no matter how many parameters or return types specify the use of the particular custom marshaler type. A reasonable GetInstance implementation might look like this:
class Marshaler
{
•••
public static ICustomMarshaler GetInstance( string cookie )
{
return m_marshaler;
}
private static ICustomMarshaler m_marshaler = new Marshaler();
•••
}
The cookie parameter can be specified in the public MarshalCookie field of the MarshalAs attribute. The cookie value can be used in any manner by the GetInstance method of the custom marshaler. Note that the cookie value can even be ignored as in the example that was previously shown.
The code in Figure 5 represents a complete implementation of a COM custom marshaler for the improved Adder class. The only method of interest is MarshalNativeToManaged. As presented earlier, MarshalNativeToManaged creates an RCW Adapter object that implements the new public view using the legacy COM Adder. MarshalNativeToManaged does this by first obtaining an RCW for the legacy Adder using the Marshal.GetObjectForIUnknown method. It then creates and returns a NewAdder Adapter instance to wrap the RCW.
At this point, a .NET-based client program can make use of the NewAdder instance with code like the following:
object rawFactory = new CalculatorLibraryLib.CalculatorFactoryClass();
INewCalculatorFactory factory = (INewCalculatorFactory) rawFactory;
NewAdder adder = factory.CreateAdder();
int[] ints = new int[] { 1, 2, 3, 4 };
int sum = adder.Add( ints );
Notice that the client program instantiates a legacy factory instance using the original Interop assembly. It then caches the legacy factory in the rawFactory variable. Following that, it casts the legacy factory instance into INewCalculatorFactory. The .NET Framework permits such a cast because the Guid attribute supplied on the INewCalculatorFactory definition corresponds to the one that's already supported by the legacy factory. From this point on, any reference to INewCalculatorFactory.CreateAdder will fire up the COM custom marshaling infrastructure in the .NET Framework, resulting in an instance of a NewAdder. With a NewAdder instance in hand, the client program can initiate the summation of an integer vector.
The Interposer sample and COM custom marshaling together form the basis for more complex custom marshaling scenarios. It's worth considering one more example in which a RealProxy-based interception method coupled with COM custom marshaling may be useful. The sample in question goes beyond the typical uses of method interception and attempts to correct a shortcoming that's in the .NET Framework.
Let's consider a sample .NET-based application that is required to work with instances of .NET-based classes as well as instances of legacy COM IDispatch-based classes. From the application's point of view, it would be convenient to operate homogeneously across all instances, whether the instance type is based in COM or in the .NET Framework.
Assume that the basic operation of the sample .NET-based application uses reflection to determine the methods implemented by these various instances. Based on the methods that these instances implement, the application performs some internal event monitoring. When the application detects an internal event, the application calls the instance methods corresponding to the event.
The goal of this sample application appears simple enough; however, it should be noted that a reader familiar with COM Interop will discover an insidious problem. While the .NET Framework allows an application to invoke IDispatch-based COM object methods through reflection, strangely enough it does not permit an application to directly discover the method signatures of such COM objects using reflection.
Based on this information, it's clear that the sample application can't homogeneously reflect upon the various instances that it may encounter. Assuming that IDispatch-based COM instances can, and generally do, return type information through their IDispatch implementations, it is possible to overcome the inherent shortcomings of .NET Framework reflection with respect to IDispatch. Obtaining this type information is the first step toward accomplishing the application goal of treating all classes equally. Moreover, a RealProxy-derived class combined with COM custom marshaling is a key element in supporting this goal. This is because a TransparentProxy is the only type that can be dynamically configured as supporting a given interface.
The IDL code shown in Figure 6 contains the canonical IDispatch interface declaration. The IDispatch GetTypeInfo method can be used in order to obtain an ITypeInfo interface. Given an ITypeInfo interface, it is possible for you to obtain instance method names and parameter information.
As stated earlier, the .NET Framework hides any notion of COM from .NET-based callers. This holds true even for an IDispatch-based COM instance where the RCW completely obscures the IDispatch interface. The problem then becomes how to obtain the IDispatch interface from a given RCW. This can be done by using the COM Interop method presented previously. Recall that a binary version of a COM interface can be defined in C# using the ComImport attribute. The code in Figure 7 uses this attribute to define a somewhat radical view of IDispatch compared to the definition in canonical IDL.
Because the .NET-based application will exclusively call IDispatch.GetTypeInfo, it's only necessary to define the binary version of IDispatch up to and including that method. Intervening methods can simply be tagged with names like "Unused1" that act as placeholders.
The C# IDispatch definition shown in Figure 7 also plays a trick quite similar to that presented in the Adder example. Notice that in the original canonical IDispatch IDL (shown in Figure 6), the last parameter of GetTypeInfo returns an ITypeInfo interface; however, the C# version defines that same parameter to return a System.Type. It just so happens that there is a custom marshaling routine called TypeToTypeInfoMarshaler that transforms an ITypeInfo interface into a System.Type object. The System.Type object is actually a dynamically generated interface whose metadata describes the methods of the underlying IDispatch implementation. This custom marshaling routine can be found in the CustomMarshaler.dll assembly located in the .NET Framework installation directory. As described previously, the use of the MarshalAs attribute applies TypeToTypeInfoMarshaler to the System.Type parameter in IDispatch.GetTypeInfo.
Calling IDispatch.GetTypeInfo as I've defined results in the return of an interface type that describes the methods and parameters of the particular COM instance implementing IDispatch. The resulting interface type is a great boon to the sample .NET-based application and can be applied to a RealProxy-derived class. Furthermore, the resultant TransparentProxy will present the public view defined by this interface. The bottom line is that the sample .NET Framework-based application will now be able to reflect on the resulting TransparentProxy (using Type.GetMethod) as it would any other .NET Framework-based object. In addition, the sample application will also be able to invoke methods in a consistent way across all classes.
Figure 8 presents the code for a RealProxy-derived class called GenericReflectionProxy that handles reflection against IDispatch-based COM object instances. Note that GenericReflectionProxy follows the basic template illustrated in Figure 2. In addition to the basic template, GenericReflectionProxy adds some code to the Marshal method that discriminates between .NET-based objects and COM objects. That is, the Marshal method only returns a TransparentProxy when the instance provided is COM-based.
GenericReflectionProxy also adds a private static method called GetInterface. This method returns an interface type describing the COM object. If the COM object supports IDispatch, and that IDispatch implementation provides type information, GetInterface returns an interface type that describes the methods supported by the IDispatch implementation. If the COM object doesn't support IDispatch or the IDispatch implementation does not provide type information, GetInterface returns an internal interface type that supports no methods. In this way, a .NET-based application will find no supported methods when doing reflection.
This article has presented insight into important, yet little-known custom marshaling techniques available in .NET Remoting and COM custom marshaling. You can extend a RealProxy using interfaces to control the overall public view presented by the resulting TranparentProxy. In addition, COM custom marshaling provides capabilities to change object views across the managed/unmanaged boundary. In many cases, some powerful method interception behaviors can be achieved when combining these techniques.
Even though the custom marshaling techniques I've presented demonstrate the extreme flexibility of the .NET Framework, this flexibility doesn't come without a price. There is a significant performance penalty for using an Interposer-based solution. Compared with reflection, invoking a method on an Interposed object is an operation that turns out to be roughly 10 times slower. Likewise, COM custom marshaling adds about 50 percent more execution time to the marshaling process. For the designer, the usual consideration of performance versus flexibility arises; however, in many cases the flexibility of these marshaling techniques far outweigh any performance penalty.