Default Marshalling for Objects

Parameters and fields typed as System.Object can be exposed to unmanaged code as one of the following types:

  • A variant when the object is a parameter.

  • An interface when the object is a structure field.

Only COM interop supports marshalling for object types. The default behavior is to marshal objects to COM variants. These rules apply only to the type Object and do not apply to strongly typed objects that derive from the Object class.

Marshalling Options

The following table shows the marshalling options for the Object data type. The MarshalAsAttribute attribute provides several UnmanagedType enumeration values to marshal objects.

Enumeration type Description of unmanaged format
UnmanagedType.Struct

(default for parameters)
A COM-style variant.
UnmanagedType.Interface An IDispatch interface, if possible; otherwise, an IUnknown interface.
UnmanagedType.IUnknown

(default for fields)
An IUnknown interface.
UnmanagedType.IDispatch An IDispatch interface.

The following example shows the managed interface definition for MarshalObject.

Interface MarshalObject
   Sub SetVariant(o As Object)
   Sub SetVariantRef(ByRef o As Object)
   Function GetVariant() As Object

   Sub SetIDispatch( <MarshalAs(UnmanagedType.IDispatch)> o As Object)
   Sub SetIDispatchRef(ByRef <MarshalAs(UnmanagedType.IDispatch)> o _
      As Object)
   Function GetIDispatch() As <MarshalAs(UnmanagedType.IDispatch)> Object
   Sub SetIUnknown( <MarshalAs(UnmanagedType.IUnknown)> o As Object)
   Sub SetIUnknownRef(ByRef <MarshalAs(UnmanagedType.IUnknown)> o _
      As Object)
   Function GetIUnknown() As <MarshalAs(UnmanagedType.IUnknown)> Object
End Interface
interface MarshalObject {
   void SetVariant(Object o);
   void SetVariantRef(ref Object o);
   Object GetVariant();

   void SetIDispatch ([MarshalAs(UnmanagedType.IDispatch)]Object o);
   void SetIDispatchRef([MarshalAs(UnmanagedType.IDispatch)]ref Object o);
   [MarshalAs(UnmanagedType.IDispatch)] Object GetIDispatch();
   void SetIUnknown ([MarshalAs(UnmanagedType.IUnknown)]Object o);
   void SetIUnknownRef([MarshalAs(UnmanagedType.IUnknown)]ref Object o);
   [MarshalAs(UnmanagedType.IUnknown)] Object GetIUnknown();
}

The following code exports the MarshalObject interface to a type library.

interface MarshalObject {
   HRESULT SetVariant([in] VARIANT o);
   HRESULT SetVariantRef([in,out] VARIANT *o);
   HRESULT GetVariant([out,retval] VARIANT *o)
   HRESULT SetIDispatch([in] IDispatch *o);
   HRESULT SetIDispatchRef([in,out] IDispatch **o);
   HRESULT GetIDispatch([out,retval] IDispatch **o)
   HRESULT SetIUnknown([in] IUnknown *o);
   HRESULT SetIUnknownRef([in,out] IUnknown **o);
   HRESULT GetIUnknown([out,retval] IUnknown **o)
}

Note

The interop marshaller automatically frees any allocated object inside the variant after the call.

The following example shows a formatted value type.

Public Structure ObjectHolder
   Dim o1 As Object
   <MarshalAs(UnmanagedType.IDispatch)> Public o2 As Object
End Structure
public struct ObjectHolder {
   Object o1;
   [MarshalAs(UnmanagedType.IDispatch)]public Object o2;
}

The following code exports the formatted type to a type library.

struct ObjectHolder {
   VARIANT o1;
   IDispatch *o2;
}

Marshalling Object to Interface

When an object is exposed to COM as an interface, that interface is the class interface for the managed type Object (the _Object interface). This interface is typed as an IDispatch (UnmanagedType) or an IUnknown (UnmanagedType.IUnknown) in the resulting type library. COM clients can dynamically invoke the members of the managed class or any members implemented by its derived classes through the _Object interface. The client can also call QueryInterface to obtain any other interface explicitly implemented by the managed type.

Marshalling Object to Variant

When an object is marshalled to a variant, the internal variant type is determined at run time, based on the following rules:

  • If the object reference is null (Nothing in Visual Basic), the object is marshalled to a variant of type VT_EMPTY.

  • If the object is an instance of any type listed in the following table, the resulting variant type is determined by the rules built into the marshaller and shown in the table.

  • Other objects that need to explicitly control the marshalling behavior can implement the IConvertible interface. In that case, the variant type is determined by the type code returned from the IConvertible.GetTypeCode method. Otherwise, the object is marshalled as a variant of type VT_UNKNOWN.

Marshalling System Types to Variant

The following table shows managed object types and their corresponding COM variant types. These types are converted only when the signature of the method being called is of type System.Object.

Object type COM variant type
Null object reference (Nothing in Visual Basic). VT_EMPTY
System.DBNull VT_NULL
System.Runtime.InteropServices.ErrorWrapper VT_ERROR
System.Reflection.Missing VT_ERROR with E_PARAMNOTFOUND
System.Runtime.InteropServices.DispatchWrapper VT_DISPATCH
System.Runtime.InteropServices.UnknownWrapper VT_UNKNOWN
System.Runtime.InteropServices.CurrencyWrapper VT_CY
System.Boolean VT_BOOL
System.SByte VT_I1
System.Byte VT_UI1
System.Int16 VT_I2
System.UInt16 VT_UI2
System.Int32 VT_I4
System.UInt32 VT_UI4
System.Int64 VT_I8
System.UInt64 VT_UI8
System.Single VT_R4
System.Double VT_R8
System.Decimal VT_DECIMAL
System.DateTime VT_DATE
System.String VT_BSTR
System.IntPtr VT_INT
System.UIntPtr VT_UINT
System.Array VT_ARRAY

Using the MarshalObject interface defined in the previous example, the following code example demonstrates how to pass various types of variants to a COM server.

Dim mo As New MarshalObject()
mo.SetVariant(Nothing)         ' Marshal as variant of type VT_EMPTY.
mo.SetVariant(System.DBNull.Value) ' Marshal as variant of type VT_NULL.
mo.SetVariant(CInt(27))        ' Marshal as variant of type VT_I2.
mo.SetVariant(CLng(27))        ' Marshal as variant of type VT_I4.
mo.SetVariant(CSng(27.0))      ' Marshal as variant of type VT_R4.
mo.SetVariant(CDbl(27.0))      ' Marshal as variant of type VT_R8.
MarshalObject mo = new MarshalObject();
mo.SetVariant(null);            // Marshal as variant of type VT_EMPTY.
mo.SetVariant(System.DBNull.Value); // Marshal as variant of type VT_NULL.
mo.SetVariant((int)27);          // Marshal as variant of type VT_I2.
mo.SetVariant((long)27);          // Marshal as variant of type VT_I4.
mo.SetVariant((single)27.0);   // Marshal as variant of type VT_R4.
mo.SetVariant((double)27.0);   // Marshal as variant of type VT_R8.

COM types that do not have corresponding managed types can be marshalled using wrapper classes such as ErrorWrapper, DispatchWrapper, UnknownWrapper, and CurrencyWrapper. The following code example demonstrates how to use these wrappers to pass various types of variants to a COM server.

Imports System.Runtime.InteropServices
' Pass inew as a variant of type VT_UNKNOWN interface.
mo.SetVariant(New UnknownWrapper(inew))
' Pass inew as a variant of type VT_DISPATCH interface.
mo.SetVariant(New DispatchWrapper(inew))
' Pass a value as a variant of type VT_ERROR interface.
mo.SetVariant(New ErrorWrapper(&H80054002))
' Pass a value as a variant of type VT_CURRENCY interface.
mo.SetVariant(New CurrencyWrapper(New Decimal(5.25)))
using System.Runtime.InteropServices;
// Pass inew as a variant of type VT_UNKNOWN interface.
mo.SetVariant(new UnknownWrapper(inew));
// Pass inew as a variant of type VT_DISPATCH interface.
mo.SetVariant(new DispatchWrapper(inew));
// Pass a value as a variant of type VT_ERROR interface.
mo.SetVariant(new ErrorWrapper(0x80054002));
// Pass a value as a variant of type VT_CURRENCY interface.
mo.SetVariant(new CurrencyWrapper(new Decimal(5.25)));

The wrapper classes are defined in the System.Runtime.InteropServices namespace.

Marshalling the IConvertible Interface to Variant

Types other than those listed in the previous section can control how they are marshalled by implementing the IConvertible interface. If the object implements the IConvertible interface, the COM variant type is determined at run time by the value of the TypeCode enumeration returned from the IConvertible.GetTypeCode method.

The following table shows the possible values for the TypeCode enumeration and the corresponding COM variant type for each value.

TypeCode COM variant type
TypeCode.Empty VT_EMPTY
TypeCode.Object VT_UNKNOWN
TypeCode.DBNull VT_NULL
TypeCode.Boolean VT_BOOL
TypeCode.Char VT_UI2
TypeCode.Sbyte VT_I1
TypeCode.Byte VT_UI1
TypeCode.Int16 VT_I2
TypeCode.UInt16 VT_UI2
TypeCode.Int32 VT_I4
TypeCode.UInt32 VT_UI4
TypeCode.Int64 VT_I8
TypeCode.UInt64 VT_UI8
TypeCode.Single VT_R4
TypeCode.Double VT_R8
TypeCode.Decimal VT_DECIMAL
TypeCode.DateTime VT_DATE
TypeCode.String VT_BSTR
Not supported. VT_INT
Not supported. VT_UINT
Not supported. VT_ARRAY
Not supported. VT_RECORD
Not supported. VT_CY
Not supported. VT_VARIANT

The value of the COM variant is determined by calling the IConvertible.To Type interface, where To Type is the conversion routine that corresponds to the type that was returned from IConvertible.GetTypeCode. For example, an object that returns TypeCode.Double from IConvertible.GetTypeCode is marshalled as a COM variant of type VT_R8. You can obtain the value of the variant (stored in the dblVal field of the COM variant) by casting to the IConvertible interface and calling the ToDouble method.

Marshalling Variant to Object

When marshalling a variant to an object, the type, and sometimes the value, of the marshalled variant determines the type of object produced. The following table identifies each variant type and the corresponding object type that the m marshallerreates when a variant is passed from COM to the .NET Framework.

COM variant type Object type
VT_EMPTY Null object reference (Nothing in Visual Basic).
VT_NULL System.DBNull
VT_DISPATCH System.__ComObject or null if (pdispVal == null)
VT_UNKNOWN System.__ComObject or null if (punkVal == null)
VT_ERROR System.UInt32
VT_BOOL System.Boolean
VT_I1 System.SByte
VT_UI1 System.Byte
VT_I2 System.Int16
VT_UI2 System.UInt16
VT_I4 System.Int32
VT_UI4 System.UInt32
VT_I8 System.Int64
VT_UI8 System.UInt64
VT_R4 System.Single
VT_R8 System.Double
VT_DECIMAL System.Decimal
VT_DATE System.DateTime
VT_BSTR System.String
VT_INT System.Int32
VT_UINT System.UInt32
VT_ARRAY | VT_* System.Array
VT_CY System.Decimal
VT_RECORD Corresponding boxed value type.
VT_VARIANT Not supported.

Variant types passed from COM to managed code and then back to COM might not retain the same variant type for the duration of the call. Consider what happens when a variant of type VT_DISPATCH is passed from COM to the .NET Framework. During marshalling, the variant is converted to a System.Object. If the Object is then passed back to COM, it is marshalled back to a variant of type VT_UNKNOWN. There is no guarantee that the variant produced when an object is marshalled from managed code to COM will be the same type as the variant initially used to produce the object.

Marshalling ByRef Variants

Although variants themselves can be passed by value or by reference, the VT_BYREF flag can also be used with any variant type to indicate that the contents of the variant are being passed by reference instead of by value. The difference between marshalling variants by reference and marshalling a variant with the VT_BYREF flag set can be confusing. The following illustration clarifies the differences:

Diagram that shows variant passed on the stack. Variants passed by value and by reference

Default behavior for marshalling objects and variants by value

  • When passing objects from managed code to COM, the contents of the object are copied into a new variant created by the marshaller, using the rules defined in Marshalling Object to Variant. Changes made to the variant on the unmanaged side are not propagated back to the original object on return from the call.

  • When passing variants from COM to managed code, the contents of the variant are copied to a newly created object, using the rules defined in Marshalling Variant to Object. Changes made to the object on the managed side are not propagated back to the original variant on return from the call.

Default behavior for marshalling objects and variants by reference

To propagate changes back to the caller, the parameters must be passed by reference. For example, you can use the ref keyword in C# (or ByRef in Visual Basic managed code) to pass parameters by reference. In COM, reference parameters are passed using a pointer such as a variant *.

  • When passing an object to COM by reference, the marshaller creates a new variant and copies the contents of the object reference into the variant before the call is made. The variant is passed to the unmanaged function where the user is free to change the contents of the variant. On return from the call, any changes made to the variant on the unmanaged side are propagated back to the original object. If the type of the variant differs from the type of the variant passed to the call, then the changes are propagated back to an object of a different type. That is, the type of the object passed into the call can differ from the type of the object returned from the call.

  • When passing a variant to managed code by reference, the marshaller creates a new object and copies the contents of the variant into the object before making the call. A reference to the object is passed to the managed function, where the user is free to change the object. On return from the call, any changes made to the referenced object are propagated back to the original variant. If the type of the object differs from the type of the object passed in to the call, the type of the original variant is changed and the value is propagated back into the variant. Again, the type of the variant passed into the call can differ from the type of the variant returned from the call.

Default behavior for marshalling a variant with the VT_BYREF flag set

  • A variant being passed to managed code by value can have the VT_BYREF flag set to indicate that the variant contains a reference instead of a value. In this case, the variant is still marshalled to an object because the variant is being passed by value. The marshaller automatically dereferences the contents of the variant and copies it into a newly created object before making the call. The object is then passed into the managed function; however, on return from the call, the object is not propagated back into the original variant. Changes made to the managed object are lost.

    Caution

    There is no way to change the value of a variant passed by value, even if the variant has the VT_BYREF flag set.

  • A variant being passed to managed code by reference can also have the VT_BYREF flag set to indicate that the variant contains another reference. If it does, the variant is marshalled to a ref object because the variant is being passed by reference. The marshaller automatically dereferences the contents of the variant and copies it into a newly created object before making the call. On return from the call, the value of the object is propagated back to the reference within the original variant only if the object is the same type as the object passed in. That is, propagation does not change the type of a variant with the VT_BYREF flag set. If the type of the object is changed during the call, an InvalidCastException occurs on return from the call.

The following table summarizes the propagation rules for variants and objects.

From To Changes propagated back
Variant v Object o Never
Object o Variant v Never
Variant * pv Ref Object o Always
Ref object o Variant * pv Always
Variant v (VT_BYREF | VT_*) Object o Never
Variant v (VT_BYREF | VT_) Ref Object o Only if the type has not changed.

See also