Default Marshaling 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 marshaling 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.
This topic provides the following additional information on marshaling object types:
- Marshaling Options
- Marshaling Object to Interface
- Marshaling Object to Variant
- Marshaling Variant to Object
- Marshaling ByRef Variants
Marshaling Options
The following table shows the marshaling 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 | A COM-style variant. |
UnmanagedType.Interface | An IDispatch interface, if possible; otherwise, an IUnknown interface. |
UnmanagedType.IUnknown | 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
[C#]
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 marshaler 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
[C#]
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;
}
Marshaling 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.IDispatch) 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.
Marshaling Object to Variant
When an object is marshaled 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 marshaled 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 marshaler and shown in the table.
- Other objects that need to explicitly control the marshaling 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 marshaled as a variant of type VT_UNKNOWN.
Marshaling 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.
[C#]
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 marshaled 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)))
[C#]
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.InteropSevices namespace.
Marshaling the IConvertible Interface to Variant
Types other than those listed in the previous section can control how they are marshaled 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.ToType interface, where ToType 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 marshaled 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.
Marshaling Variant to Object
When marshaling a variant to an object, the type, and sometimes the value, of the marshaled variant determines the type of object produced. The following table identifies each variant type and the corresponding object type that the marshaler creates 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 marshaling, the variant is converted to a System.Object. If the Object is then passed back to COM, it is marshaled back to a variant of type VT_UNKNOWN. There is no guarantee that the variant produced when an object is marshaled from managed code to COM will be the same type as the variant initially used to produce the object.
Marshaling 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 marshaling variants by reference and marshaling a variant with the VT_BYREF flag set can be confusing. The following illustration clarifies the differences.
Variants passed by value and by reference
Default behavior for marshaling 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 marshaler, using the rules defined in Marshaling 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 Marshaling 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 marshaling 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 marshaler 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 marshaler 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 marshaling 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 marshaled to an object because the variant is being passed by value. The marshaler 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 marshaled to a ref object because the variant is being passed by reference. The marshaler 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
Default Marshaling Behavior | Blittable and Non-Blittable Types | Directional Attributes | Copying and Pinning