Parameters and fields that are explicitly typed as System.Object can be exposed to the unmanaged world as either a Variant or as an interface. The default behavior is to marshal Objects to COM Variants. These rules only apply to the type System.Object and do not apply to strongly types object that derive from System.Object.
The marshaling options for ELEMENT_TYPE_OBJECT are as follows:
Unmanaged Type | Description of unmanaged format |
---|---|
UnmanagedType.Variant | A COM style Variant. |
UnmanagedType.Interface | An IDispatch interface |
Consider the following managed interface definition:
interface MarshalObject { Void SetVariant(Object o); Void SetVariantRef(Ref Object o); Object GetVariant(); Void SetIDispatch ([MarshalAs(UnmanagedType.Interface)]Object o); Void SetIDispatchRef([MarshalAs(UnmanagedType.Interface)]Ref Object o); [MarshalAs(UnmanagedType.Interface)] Object GetIDispatch(); }
The MarshalObject interface defined above would be exported to a type library as shown below:
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) }
Likewise, the following formatted value type
class ObjectHolder : public System.Value { Object o1; [MarshalAs(UnmanagedType.Interface)]Object o2; }
Would be exported to a type library as shown below:
struct ObjectHolder { Variant o1; IDispatch *o2; }
When an object is exposed to COM as an interface, the interface that exposed 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 it subclasses through the _Object interface. The client can also call QueryInterface to obtain any other interface explicitly implemented by the managed type.
When an object is marshaled to a variant, the internal variant type is determined at runtime based on the following rules:
The system value types shown in the table below are automatically converted to the variant types shown in the table when marshaled as an object. These conversions only apply when the signature of the method being called is of type System.Object.
Type of object instance | COM Variant Type used for marshaling |
---|---|
Null | VT_EMPTY |
System.DBNull | VT_NULL |
System.ErrorWrapper | VT_ERROR |
System.Missing | VT_ERROR with E_PARAMNOTFOUND |
System.Exception | VT_ERROR |
System.IDispatchWrapper | VT_DISPATCH |
System.IUnknownWrapper | VT_UNKNOWN |
System.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.Currency | VT_CY |
System.DateTime | VT_DATE |
System.String | VT_BSTR |
System.Int | VT_INT |
System.Uint | VT_UINT |
System.Array | VT_ARRAY |
For example, using the MarshalObject interface defined above, the code below demonstrates how to pass various types of variants to a COM server.
MarshalObject mo = new MarshalObject(); mo.SetVariant(null) // marshal as variant of type VT_EMPTY mo.SetVariant(System.DBNull) // 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 wrappers classes such as ErrorWrapper, IDispatchWrapper, IUnknownWrapper, and CurrencyWrapper. The following code demonstrates how to use wrappers to pass various types of variants to a COM server.
// pass ifoo as a Variant of type VT_UNKNOWN interface mo.SetVariant(IUnknownWrapper(ifoo)); // pass ifoo as a Variant of type VT_DISPATCH interface cs.SetVariant(IDispatchWrapper(ifoo)); // pass ifoo as a Variant of type VT_ERROR interface cs.SetVariant(IErrorWrapper(0x80054002)); // pass ifoo as a Variant of type VT_CURRENCY interface cs.SetVariant(ICurrencyWrapper(System.Currency(5.25)));
The wrapper classes are defined in the System.Runtime.InteropSevices namespace.
Types other than those listed above 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 runtime by the TypeCode returned from IConvertible.GetTypeCode. The table below shows the TypeCodes along with the corresponding COM variant type for each type code.
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 |
Table 7 – Type Code to Variant type mapping
The value of the COM variant is determined by calling the IConvertible.ToXXX (Where ToXXX is the conversion routine that corresponds to the type was returned from IConvertible.GetTypeCode). For example, an object that returns TypeCode.Double from IConvertible.GetTypeCode will be marshaled as a COM Variant of type VT_R8. The value of the variant (stored in the dblVal field of the COM variant) is obtained by calling Object.ToDouble().
When marshaling a variant to an object, the type, and sometimes the value, of the Variant being marshaled determine the type of object produced. The table below identifies each variant type and the corresponding object type that the marshaler creates when a variant is passed from COM to the NGWS runtime.
COM VT Type | Marshal to this type of Object |
---|---|
VT_EMPTY | Null object reference |
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.Currency (This Will change to System.Decimal post PDC) |
VT_RECORD | Not Supported |
VT_VARIANT | Not Supported |
Table 8 – Variant to Type mapping
It should be noted that Variants passed from COM to the NGWS runtime and then back out to COM may 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 NGWS runtime. During marshaling, the Variant is converted to a System.Object. If the object is then passed back out to COM, the Object is marshaled back to a variant of type VT_UNKNOWN. There is no guarantee that the variant produce when a object is marshaled from the NGWS runtime to COM will be the same type as the variant used to produce the object initially.
While 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 as opposed to by value.
The difference between marshaling variants by reference and marshaling a variant with the BY_REF flag set can be confusing. The following diagrams show the difference between passing a variant by reference and passing a variant whose BYREF flag is set to indicate that the variant contains a reference.
When passing objects from the NGWS runtime to COM, the objects contents are copied into a new variant created by the marshaler according to the rules defined in section 5.3.3. Changes made to the variant on the unmanaged side are not back propagated to the original object on return from the call. Similarly, when passing variants from COM to the NGWS runtime, the variants contents are copied to a newly created object according to the rules defined in section 5.3.4. Changes made to the object on the managed side are not back propagated to the original variant on return from the call.
In order to get back propagation, the parameters must be passed by reference. For example, in C# the ref keyword is used to pass parameters by reference. In COM, reference parameters are passed through a pointer such as a variant *.
When passing an object to COM by reference, the marshaler creates a new variant and the contents of the object reference are copied 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. Upon return from the call, any changes made to the variant on the unmanaged side are back propagated to the original object. If the type of the variant differs from the type of the variant passed in to the call, then the changes are back propagate to an object of a different type. . In other words, the type of the object passed into the call may differ from the type of the object returned from the call.
When passing a variant to the NGWS runtime by reference, the marshaler creates a new object and the contents of the variant are copied into the object before the call is made. A reference to the object is passed to the managed function where the user is free to change the object. Upon return from the call, any changes made to the referenced object are back propagated to the original variant. If the type of the object differs from the type of the object passed in to the call then the type of the original variant is changed and the value is back propagate into the variant. In other words, the type of the variant passed into the call may differ from the type of the variant returned from the call.
A variant being passed to the NGWS runtime by value can also have the VT_BYREF flag set to indicate that the variant contains a reference as opposed to a value. In this case, the variant is still marshaled to an object because the variant is being passed by value. The marshaler will automatically de-reference the contents of the variant and copy it into a newly created object before making the call. The object is then passed into the managed function however, upon return from the call, the object is NOT back propagated into the original variant. Any changes made to the managed object are lost. There is no way of changing the value of a variant passed by value, even if the variant has the BYREF flag set.
A variant being passed to the NGWS runtime by reference can also have the VT_BYREF flag set to indicate that the variant contains another reference. In this case the variant is marshaled to a ref object because the variant is being passed by reference. The marshaler will automatically de-reference the contents of the variant and copy it into a newly created object before making the call. Upon return from the call, the value of the object is back propagated to the reference within the original variant only if the object is the same type as the object passed in. In other words, back propagation will never change the type of a variant with the VT_BYREF flag set. If the type if the object is changed during the call an InvalidCastException will occur on return from the call.
From | To | Chgs Back Propagated |
---|---|---|
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 type hasn’t changed |
A formatted type is a type that contains information that explicitly controls the layout of it members. The member layout information is provided with the StructLayout attribute. The layout can be LayoutKind.Sequential or LayoutKind.Explicit. LayoutKind.Sequential layout indicates that the members of the typed are to be laid out in memory exactly as they appear in the type definition. LayoutKind.Explicit is used to indicate that the members are laid out according to the FieldOffset attribute supplied with each filed. For example, the Point and Rect types defined below provide member layout information using the StructLayout attribute.
using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential] public struct Point { public int x; public int y; } [StructLayout(LayoutKind.Explicit] public struct Rect { [FieldOffset(0)] public int left; [FieldOffset(4)] public int top; [FieldOffset(8)] public int right; [FieldOffset(8)] public int bottom; }
When marshaled to unmanaged code these formatted types are marshaled as C-style structures. This provides a handy way of calling unmanaged API’s that have structure arguments. For example, the Point and Rect structures can be passed to the Win32 API PtInRect:
BOOL PtInRect(const RECT *lprc, POINT pt);
using the P/Invoke definition shown below:
class Win32API { [DllImport(“User32.dll”)] public static extern Bool PtInRect(ref Rect r, Point p);
}
The managed Rect must be passed by reference because the unmanaged API is expecting a pointer to a RECT to be passed to the function. The managed point is passed by value because the unmanaged API expects the POINT to be passed on the stack. This subtlety is very important and is pointed out in section 2.3. References are passed to unmanaged code as pointers. Values are passed to unmanaged code on the stack.
Reference types (classes) can also be marshaled to unmanaged code as a C-style structure provided they have fixed member layout. The member layout information for a class is also provided with the StructLayout attribute. The main difference between value types with fixed layout and a reference types with fixed layout is in how they are passed as method arguments. Value types are passed by value (on the stack) and consequently any changes made to the members of the type by the callee are not seen by the caller. Reference types are passed by reference (a reference to the type is passed on the stack) and consequently any changes made to the members of the type by the callee are seen by the caller. The same rules apply when reference types are passed to unmanaged code. For example, the SystemTime class has sequential member layout and could be passed to the Win32 API GetSystemTime.
[StructLayout(LayoutKind.Sequential)]
public class SystemTime { public ushort wYear; public ushort wMonth; public ushort wDayOfWeek; public ushort wDay; public ushort wHour; public ushort wMinute; public ushort wSecond; public ushort wMilliseconds; }
GetSystemTime is defined as:
void GetSystemTime(SYSTEMTIME* SystemTime);
The P/Invoke definition would be:
class Win32API { [DllImport(“User32.dll”)] public static extern void GetSystemTime(SystemTime st);
}
Notice that the SystemTime argument is not typed as a ref argument because SystemTime is a class as opposed to a value type. Unlike value types, classes are always passed by reference.
Unmanaged Type | Description of unmanaged format |
---|---|
UnmanagedType.Struct | A C style structure |
Table 9 – Class Types
When a formatted type is marshaled as a structure, only the fields within the type are accessible. If the type has methods, properties or events, there are inaccessible from unmanaged code.
For example, the following code shows the Point class that has a method called SetXY. Because the type has sequential layout, in can be passed to unmanaged code and marshaled as a structure. however, the SetXY member is not callable from unmanaged code (even through the object is passed by reference).
[StructLayout(LayoutKind.Sequential)] public class Point { int x, y; public void SetXY(int x, int y){ this.x = x; this.y = y; } };
Formatted types can also be passed to COM Interop method calls. In fact, when exported to a type library value types are automatically converted to structures. For example, the Point value type becomes a typedef with the name Point. All references to the Point value type elsewhere in the type library are replaced with the Point Typedef.
typedef struct tagPoint { int x; int y; } Point; interface _Graphics { … HResult SetPoint ([in] Point p) HResult SetPointRef ([in out] Point *p) HResult GetPoint ([out retval] Point *p) }
.
The same rules used to marshal values and references to PInvoke calls are used when marshaling through COM interfaces. When an instance of the Point value type is passed from the NGWS runtime to COM, the Point is passed by value. If the Point value type is passed by reference, a pointer to a Point is passed on the stack.
The marshaler does not support higher levels of indirection (Point **) in either direction.
The System namespace has several value types that represent the boxed form of the runtime primitive types. For example, the value type System.Int32 represents the boxed form of the ELEMENT_TYPE_I4. Instead of marshaling these types as structures (as other formatted types would be), they are marshaled exactly the same as the primitive types they box. System.Int32 therefore is marshaled as ELEMENT_TYPE_I4 instead of a structure containing a single member of type long. The following table contains a list of the value types in the system namespace that are boxed representations of primitive types.
System Value Type | Marshaled as Element Type |
---|---|
System.Boolean | ELEMENT_TYPE_BOOLEAN |
System.Sbyte | ELEMENT_TYPE_I1 |
System.Byte | ELEMENT_TYPE_UI1 |
System.Char | ELEMENT_TYPE_CHAR |
System.Int16 | ELEMENT_TYPE_I2 |
System.Uint16 | ELEMENT_TYPE_U2 |
System.Int32 | ELEMENT_TYPE_I4 |
System.Uint32 | ELEMENT_TYPE_U4 |
System.Int64 | ELEMENT_TYPE_I8 |
System.Uint64 | ELEMENT_TYPE_U8 |
System.Single | ELEMENT_TYPE_R4 |
System.Double | ELEMENT_TYPE_R8 |
System.String | ELEMENT_TYPE_STRING |
System.Int | ELEMENT_TYPE_I |
System.Uint | ELEMENT_TYPE_U |
Some other value types in the System namespace are also handled a bit different. Because the unmanaged world already has well established formats for these types, the marshaler has special rules for how these types are marshaled. The following table lists the special value types in the System namespace along with the unmanaged type that the marshaler that the marshaling service will marshal these types as.
System Value Type | Marshaled as IDL Type |
---|---|
System.DateTime |
typedef double DATE; |
System.Decimal |
typedef struct tagDEC { USHORT wReserved; BYTE scale; BYTE sign; ULONG Hi32; ULONGLONG Lo64; } DECIMAL; |
System.Currency |
typedef struct tagCY { ulong Lo; long Hi; } CY; |
System.Guid |
typedef struct tagGUID { DWORD Data1; WORD Data2; WORD Data3; BYTE Data4[ 8 ]; } GUID; |