Managed arrays come in many flavors. The class System.Array is the base class of all array types. The System.Array class has properties for determining the rank, length, lower and upper bounds of an array as well as methods for accessing, sorting, searching, copying and creating arrays.
There are also 2 specialized array types:
ELEMENT_TYPE_ARRAY is a typed array with any rank and any lower bound. Both the element type and the rank must be specified with this type of array.
ELEMENT_TYPE_SZARRAY is a typed array with rank 1 and 0 lower bounds. The element type must be specified with this type of array. The rank and bounds are fixed.
Both array types are dynamic and therefore do not have a corresponding static type defined in the base class library. It’s convenient to think of each combination of element type, rank as a distinct type of array. Therefore, a 1-dimensional array of integers is of a different type than a 1-dimensional array of doubles. Similarly a 2-dimensional array of longs is different than a 1-dimensional array of longs. The bounds of the array are not considered when doing type comparisons.
Any instance of a managed array must be of a specific element type, rank and low bound. The type System.Array cannot be instantiated because element type, rank and bounds are not specified. However, parameters are commonly typed as System.Array in order to support polymorphism.
Managed Array Type | ElementType | Rank | Low Bound | Signature Notation |
---|---|---|---|---|
ET_CLASS <System.Array> | Unknown | Unknown | Unknown | System.Array |
ET_SZARRAY <Type> | Specified by Type | 1 | 0 | Type[n] |
ET_ARRAY <Type><Rank>[<Bounds>…] | Specified by Type | Specified by Rank | Optionally specified by Bounds | Type[n,m] |
Table 11 – Overview of managed array types
Unmanaged arrays are either COM style SafeArrays or C-style arrays with fixed or variable length. SafeArrays are self-describing arrays that carry the type, rank and bounds of the associated array data. C-style arrays are single dimensional, typed arrays with fixed lower bound of 0.
The marshaling service has limited support for both types of arrays.
Both C-style arrays and SafeArrays can be passed into the NGWS runtime from unmanaged code.
Unmanaged Type | Imported as… |
---|---|
SafeArray(Type) | ET_SZARRAY <ConvertedType>
Rank = 1, low bound = 0. Size known only if provided in managed signature. SafeArrays that are not Rank =1 or low bound = 0 are not marshalable. |
Type [] | ET_SZARRAY <ConvertedType>
Rank = 1, low bound = 0. Size known only if provided in managed signature. |
When a SafeArray is imported from a type library to a NGWS runtime assembly, the array is converted to a single dimensional array of a known type (i.e. int[]). The same type conversion rules that are applied to parameters also apply to array elements. So, for example, a SafeArray of BSTRs becomes a managed array of Strings and a SafeArray of Variants becomes a managed array of Objects. The SafeArray element type is captured with SafeArraySubType named argument passed to the MarshalAs attribute applied to the parameter.
Because the rank and bounds of the SafeArray cannot be determined from the type library, the rank is assumed to 1 and the lower bound is assumed to be 0. The rank and bounds must be defined in the managed signature produced by TlbImp. If the actual rank or low bounds passed to the method at runtime differ, a SafeArrayTypeMismatchException is thrown. Multi dimensional or not 0 bound SafeArrays therefore cannot be marshaled from COM to the NGWS runtime.
For example
HRESULT Foo1([in] SAFEARRAY( int ) ar); HRESULT Foo2([in] SAFEARRAY( DATE ) ar); HRESULT Foo3([in, out] SAFEARRAY( BSTR ) *ar);
Is converted to:
void Foo1([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VT_I4)] int[] ar) ; void Foo2([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VT_DATE)] DateTime[] ar); void Foo3([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VT_BSTR)] ref String[] ar);
It should be noted that if the type of the actual array passed at runtime does not match the type indicated in the type library the marshaling may fail.
When a C-style array is imported from a type library to a NGWS runtime assembly, the array is converted to ELEMENT_TYPE_SZARRAY.
The array element type is determined from the type library and preserved during the import. The same conversion rules that are applied to parameters also apply to array elements. So, for example, an array of LPSTRs becomes an array of Strings. The array element type is captured with ArraySubType named argument passed to the MarshalAs attribute and applied to the parameter.
The array rank is assumed to always be 1. If the rank is greater than 1, the array is marshaled as a single dimension array in column major order. The lowers bound is always 0.
Type libraries can contain arrays of fixed or variable length. Only fixed length arrays can be imported from type libraries with TlbImp. Type libraries containing variable length arrays must be defined manually as shown below. With fixed length arrays, the size is imported from the type library and captured in the MarshalAs attribute applied to the parameter.
For example:
HRESULT Foo1(int ar[10]); HRESULT Foo2(double ar[10][20]); HRESULT Foo3(LPWSTR ar[10]);
Is converted to:
void Foo1([MarshalAs(UnmanangedType.LPArray, SizeConst=10)] int[] ar); void Foo2([MarshalAs(UnmanangedType.LPArray, SizeConst=200)] double[] ar); void Foo2([MarshalAs(UnmanangedType.LPArray, ArraySubType=UnmanangedType.LPWSTR, SizeConst=10)] String[] ar);
Unfortunately, only fixed size arrays can be imported from type libraries with TlbImp because the type libraries lack the information needed to marshal variable length arrays. Even though IDL allows an array to be attributed with the size_is or length_is attribute to convey the size to the callee, the MIDL compiler does not propagate that information to the type library. Without knowing the size, the marshaling service cannot marshal the array elements.
Consequently, the variable length arrays are simply imported as reference arguments. For example:
HRESULT Foo1(int ar[]); HRESULT Foo2(int ArSize, [size_is(ArSize)] double ar[]); HRESULT Foo3(int ElemCnt, [length_is(ElemCnt)] LPSTR ar[]);
Is converted to:
void Foo1(ref int ar); void Foo2(ref double ar); void Foo3(ref String ar);
However, it is possible to provide the marshaler with the array size by defining the managed method signature manually (as opposed to using TlbImp) using the MarshalAs attribute.
The MarshalAs attribute is applied to the array parameter of the managed method definition. The attribute can be used to indicate the number of elements in the array in 3 different ways.
void Foo( int ElemCnt, [MarshalAs(UnmanagedType.LPArray, SizeParam=0)] int[] ar );
void Foo( int ElemCnt, [MarshalAs(UnmanagedType.LPArray,SizeParam=1,SizeMultiplier=4] int[] ar);
void Foo( [MarshalAs(UnmanagedType.LPArray, SizeConst=128)] int[] ar );
At runtime, the marshaler refers to the MarshalAs attribute associated with the parameter to determine the actual array size.
All managed array types can be passed into unmanaged code from the NGWS runtime. Depending on the managed type and the attributes applied to it, the array may be accessed as a SafeArray or a C-style array.
Managed Array Type | Exported as… |
---|---|
ET_SZARRAY <type> | UnmanagedType.SafeArray(type)
UnmanagedType.LPArray Type provided in signature. Rank always 1, low bound always 0. Size always known at runtime. |
ET_ARRAY <type> <rank>[<bounds>] | UnmanagedType.SafeArray(type)
UnmanagedType.LPArray Type, Rank, Bounds provided in signature. Size always known at runtime. |
ET_CLASS <System.Array> | UT_Interface
UnmanagedType.SafeArray(type) Type, rank, bounds and size always known runtime. |
When a method containing an ET_SZARRAY parameter (single dimensional array) is exported from a NGWS runtime assembly to a type library, the array parameter is converted to a SafeArray of a given type. Again the same conversion rules apply to the array element types. The contents of the managed array are automatically copied from managed memory into the SafeArray. For example:
void Foo(long[] ar ); void Foo(String[] ar );
Is exported as:
HRESULT Foo([in] SAFEARRAY( long ) ar); HRESULT Foo([in] SAFEARRAY( BSTR ) ar);
The rank of the SafeArrays is always 1 and the lower bound is always 0. The size is determined at runtime by the size of the managed array being passed.
The array can also be marshaled as a c-style array by using the MarshalAs attribute. For example:
void Foo([MarshalAs(UnmanagedType.LPArray)] long [] ar ); void Foo([MarshalAs(UnmanagedType.LPArray)] String [] ar ); void Foo([MarshalAs(UnmanagedType.LPArray, ArraySubType= UnmanagedType.LPSTR)] String [] ar );
Is exported as:
HRESULT Foo(long ar[]); HRESULT Foo(BSTR ar[]); HRESULT Foo(LPSTR ar[]);
Although the marshaler has the length information needed to marshal the array, the array length is usually passed as a separate argument in order to convery the length to the callee.
When a method containing an ET_ARRAY parameter is exported from an NGWS runtime assembly to a type library, the array parameter is converted to a SafeArray of a given type. The contents of the managed array are automatically copied from managed memory into the SafeArray. For example
void Foo( long [,] ar ) void Foo( String [,] ar )
Is exported as:
HRESULT Foo([in] SAFEARRAY( long ) ar); HRESULT Foo([in] SAFEARRAY( BSTR ) ar);
The rank, size and bounds of the SafeArrays are determined at runtime by the characteristics of the managed array.
The array can also be marshaled as a c-style array by using the MarshalAs attribute. For example:
void Foo([MarshalAs(UnmanagedType.LPARRAY)] long [,] ar ) void Foo([MarshalAs(UnmanagedType.LPARRAY, ArraySubType= UnmanagedType.LPSTR)] String [,] ar )
Is exported as:
HRESULT Foo(long ar[]); HRESULT Foo(LPSTR ar[]);
Nested arrays are not supported.
For example:
void Foo(long [][][] ar )
Would cause an error when exported with TlbExp.
When a method containing a System.Array parameter is exported from a NGWS runtime assembly to a type library, the array parameter is converted to an _Array interface. The contents of the managed array are only accessible through the methods and properties of the _Array interface. System.Array can also be marshaled as a SafeArray by using the MarshalAs attribute. When marshaled as a SafeArray the array elements are marshaled as variants.
void Foo1( System.Array ar ) void Foo2( MarshalAs(UnmanagedType.SafeArray)], System.Array ar )
Is exported as:
HRESULT Foo([in] _Array *ar); HRESULT Foo([in] SAFAERRAY(VARIANT) ar);