数组的默认封送处理

在完全由托管代码组成的应用程序中,公共语言运行时将数组类型作为 In/Out 参数传递。 而互操作封送处理程序默认将数组作为 In 参数传递。

使用固定优化,blittable 数组在与同一单元中的对象交互时,可能看上去像是作为 In/Out 参数运行。 但是,如果随后将代码导出到用于生成跨计算机代理的类型库,且该库用于跨单元封送调用,则调用可还原为真正的 In 参数行为。

数组本就很复杂,而托管数组和非托管数组之间的差异使它们比其他 blittable 类型具有更多信息。

托管数组

可以有各种托管数组类型;但是,System.Array 类是所有数组类型的基类。 System.Array 类的属性可确定数组的秩、长度、下限和上限,其方法可用于访问、搜索、复制、创建数组以及对数组排序。

这些数组类型是动态类型,在基类库中未定义相应的静态类型。 将元素类型和秩的每一种组合视作不同类型的数组非常方便。 因此,一维整数数组与一维 double 类型数组是不同的类型。 同样,二维整数数组与一维整数数组也不同。 比较类型时,不考虑数组界限。

如下表所示,托管数组的所有实例都必须有特定的元素类型、秩和下限。

托管数组类型 元素类型 级别 下限 签名表示法
ELEMENT_TYPE_ARRAY 由类型指定。 由秩指定。 由界限视情况指定。 type [ n,m ]
ELEMENT_TYPE_CLASS 未知 未知 未知 System.Array
ELEMENT_TYPE_SZARRAY 由类型指定。 1 0 type [ n ]

非托管数组

非托管数组是 COM 样式安全数组或 C 样式数组,其长度固定或可变。 安全数组是自我描述的数组,带有关联数组数据的类型、秩和界限。 C 样式数组是具有固定下限 0 的一维类型化数组。 封送处理服务对两种数组类型的支持均有限。

将数组参数传递给 .NET 代码

C 样式数组和安全数组都可以作为安全数组或 C 样式数组从非托管代码传递给 .NET 代码。 下表显示非托管类型值和导入的类型。

非托管类型 导入的类型
SafeArray( Type ) ELEMENT_TYPE_SZARRAY < ConvertedType >

秩 = 1,下限 = 0。 只有在托管签名中提供大小时,才知道大小。 不满足秩 = 1 或下限 = 0 的安全数组不能作为 SZARRAY 封送。
Type [] ELEMENT_TYPE_SZARRAY < ConvertedType >

秩 = 1,下限 = 0。 只有在托管签名中提供大小时,才知道大小。

安全数组

从类型库将安全数组导入 .NET 程序集时,该数组转换为已知类型(例如 int)的一维数组。 适用于参数的类型转换规则同样适用于数组元素。 例如,BSTR 类型的安全数组可变为托管字符串数组,而变体的安全数组可变为托管对象数组。 从类型库中捕获 SAFEARRAY 元素类型并将它保存在 枚举的 SAFEARRAY 值中。

由于无法根据类型库确定安全数组的秩和界限,因此假定秩等于 1,下限等于 0。 必须在由类型库导入程序 (Tlbimp.exe) 生成的托管签名中定义秩和界限。 如果运行时传递给方法的秩不同,则会引发 SafeArrayRankMismatchException。 如果运行时传递的数组的类型不同,则会引发 SafeArrayTypeMismatchException。 下面的示例演示托管代码和非托管代码中的安全数组。

非托管的签名

HRESULT New1([in] SAFEARRAY( int ) ar);
HRESULT New2([in] SAFEARRAY( DATE ) ar);
HRESULT New3([in, out] SAFEARRAY( BSTR ) *ar);

托管的签名

Sub New1(<MarshalAs(UnmanagedType.SafeArray, SafeArraySubType:=VT_I4)> _
   ar() As Integer)
Sub New2(<MarshalAs(UnmanagedType.SafeArray, SafeArraySubType:=VT_DATE)> _
   ar() As DateTime)
Sub New3(ByRef <MarshalAs(UnmanagedType.SafeArray, SafeArraySubType:=VT_BSTR)> _
   ar() As String)
void New1([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VT_I4)] int[] ar) ;
void New2([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VT_DATE)]
   DateTime[] ar);
void New3([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VT_BSTR)]
   ref String[] ar);

如果修改由 Tlbimp.exe 产生的方法签名以指示元素类型 ELEMENT_TYPE_ARRAY 而非 ELEMENT_TYPE_SZARRAY,则可将多维或非零界限安全数组封送到托管代码中。 或者,可将 /sysarray 开关与 Tlbimp.exe 一起使用,将所有数组作为 对象导入。 如果已知正在传递的数组是多维数组,则可编辑由 Tlbimp.exe 生成的公共中间语言 (CIL) 代码,然后重新编译它。 有关如何修改 CIL 代码的详细信息,请参阅自定义运行时可调用包装器

C 样式数组

将 C 样式数组从类型库导入 .NET 程序集中时,数组被转换为 ELEMENT_TYPE_SZARRAY。

数组元素类型根据类型库确定并在导入期间保留。 适用于参数的转换规则同样适用于数组元素。 例如,LPStr 类型的数组变为字符串类型的数组 。 Tlbimp.exe 捕获数组元素类型并将 MarshalAsAttribute 属性应用于该参数。

假定数组秩等于 1。 如果秩大于 1,则将按列主序将数组作为一维数组封送。 下限始终等于 0。

类型库可包含定长数组或变长数组。 Tlbimp.exe 只能从类型库中导入定长数组,这是因为类型库缺少封送变长数组所需的信息。 对于定长数组,从类型库导入大小,并在应用于参数的 MarshalAsAttribute 中捕获大小。

必须手动定义含变长数组的类型库,如以下示例所示。

非托管的签名

HRESULT New1(int ar[10]);
HRESULT New2(double ar[10][20]);
HRESULT New3(LPWStr ar[10]);

托管的签名

Sub New1(<MarshalAs(UnmanagedType.LPArray, SizeConst=10)> _
   ar() As Integer)
Sub New2(<MarshalAs(UnmanagedType.LPArray, SizeConst=200)> _
   ar() As Double)
Sub New2(<MarshalAs(UnmanagedType.LPArray, _
   ArraySubType=UnmanagedType.LPWStr, SizeConst=10)> _
   ar() As String)
void New1([MarshalAs(UnmanagedType.LPArray, SizeConst=10)] int[] ar);
void New2([MarshalAs(UnmanagedType.LPArray, SizeConst=200)] double[] ar);
void New2([MarshalAs(UnmanagedType.LPArray,
   ArraySubType=UnmanagedType.LPWStr, SizeConst=10)] String[] ar);

虽然可将 size_is 或 length_is 属性应用于接口定义语言 (IDL) 源中的数组,以便将大小传达给客户端,但是 Microsoft 接口定义语言 (MIDL) 编译器不会将该信息传送到类型库 。 如果不知道大小,互操作封送处理服务就无法封送数组元素。 因此,将变长数组作为引用参数导入。 例如:

非托管的签名

HRESULT New1(int ar[]);
HRESULT New2(int ArSize, [size_is(ArSize)] double ar[]);
HRESULT New3(int ElemCnt, [length_is(ElemCnt)] LPStr ar[]);

托管的签名

Sub New1(ByRef ar As Integer)
Sub New2(ByRef ar As Double)
Sub New3(ByRef ar As String)
void New1(ref int ar);
void New2(ref double ar);
void New3(ref String ar);

编辑由 Tlbimp.exe 生成的公共中间语言 (CIL) 代码,再重新编译该代码,即可向封送处理程序提供数组大小。 有关如何修改 CIL 代码的详细信息,请参阅自定义运行时可调用包装器。 要指示数组中的元素数,请采用以下任一方式将 MarshalAsAttribute 类型应用于托管方法定义的数组参数:

  • 标识含数组中的元素数的另一个参数。 按位置标识参数,从第一个参数开始,将第一个参数标识为数字 0。

    Sub [New](ElemCnt As Integer, _
       \<MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=1)> _
       ar() As Integer)
    
    void New(
       int ElemCnt,
       [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] int[] ar );
    
  • 将数组大小定义为常数。 例如:

    Sub [New](\<MarshalAs(UnmanagedType.LPArray, SizeConst:=128)> _
       ar() As Integer)
    
    void New(
       [MarshalAs(UnmanagedType.LPArray, SizeConst=128)] int[] ar );
    

将数组从非托管代码封送到托管代码时,封送处理程序会检查与参数关联的 MarshalAsAttribute 以确定数组大小。 如果未指定数组大小,则只封送一个元素。

注意

将托管数组封送到非托管代码不会受 MarshalAsAttribute 的影响。 在该方向上,数组大小通过检查确定。 无法封送托管数组的子集。

互操作封送处理程序使用 Windows 上的 CoTaskMemAlloc 和 CoTaskMemFree 方法或其他操作系统上的 malloc 和 free 方法来分配和检索内存。 非托管代码所执行的内存分配也必须使用这些方法。

将数组传递给 COM

所有托管数组类型都可以从托管代码传递给非托管代码。 根据托管类型和应用于它的属性,可将数组作为安全数组或 C 样式数组进行访问,如下表所示。

托管数组类型 导出结果
ELEMENT_TYPE_SZARRAY < type > UnmanagedType .SafeArray( type )

UnmanagedType.LPArray

签名中提供了类型。 秩始终为 1,下限始终为 0。 在运行时大小始终为已知。
ELEMENT_TYPE_ARRAY < type > < rank >[< bounds >] UnmanagedType.SafeArray( type )

UnmanagedType.LPArray

签名中提供了类型、秩和界限。 在运行时大小始终为已知。
ELEMENT_TYPE_CLASS <System.Array> UT_Interface

UnmanagedType.SafeArray( type )

在运行时类型、秩、界限和大小始终为已知。

在与含有 LPSTR 或 LPWSTR 的结构数组相关的 OLE 自动化中,存在一项限制。 因此,必须将 String 字段作为 UnmanagedType.BSTR 封送。 否则,将引发异常。

ELEMENT_TYPE_SZARRAY

将包含 ELEMENT_TYPE_SZARRAY 参数(一维数组)的方法从 .NET 程序集导出到类型库时,会将该数组参数转换为给定类型的 SAFEARRAY 。 同样的转换规则也适用于数组元素类型。 自动将托管数组的内容从托管内存复制到 SAFEARRAY 中。 例如:

托管的签名

Sub [New](ar() As Long)
Sub [New](ar() As String)
void New(long[] ar );
void New(String[] ar );

非托管的签名

HRESULT New([in] SAFEARRAY( long ) ar);
HRESULT New([in] SAFEARRAY( BSTR ) ar);

安全数组的秩始终为 1,下限始终为 0。 大小在运行时由所传递的托管数组的大小确定。

还可以通过使用 MarshalAsAttribute 属性将数组作为 C 样式数组封送。 例如:

托管的签名

Sub [New](<MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=1)> _
   ar() As Long, size as Integer)
Sub [New](<MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=1)> _
   ar() As String, size as Integer)
Sub [New](<MarshalAs(UnmanagedType.LPArray, _
   ArraySubType= UnmanagedType.LPStr, SizeParamIndex:=1)> _
   ar() As String, size as Integer)
void New([MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)]
   long [] ar, int size );
void New([MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)]
   String [] ar, int size );
void New([MarshalAs(UnmanagedType.LPArray, ArraySubType=
   UnmanagedType.LPStr, SizeParamIndex=1)]
   String [] ar, int size );

非托管的签名

HRESULT New(long ar[]);
HRESULT New(BSTR ar[]);
HRESULT New(LPStr ar[]);

虽然封送处理程序具有封送数组所需的长度信息,但通常会将数组长度作为单独的参数传递,以便将长度传达给被调用方。

ELEMENT_TYPE_ARRAY

将包含 ELEMENT_TYPE_ARRAY 参数的方法从 .NET 程序集导出到类型库时,会将该数组参数转换为给定类型的 SAFEARRAY 。 自动将托管数组的内容从托管内存复制到 SAFEARRAY 中。 例如:

托管的签名

Sub [New](ar(,) As Long)
Sub [New](ar(,) As String)
void New( long [,] ar );
void New( String [,] ar );

非托管的签名

HRESULT New([in] SAFEARRAY( long ) ar);
HRESULT New([in] SAFEARRAY( BSTR ) ar);

安全数组的秩、大小和界限在运行时由托管数组的特征确定。

还可以通过应用 MarshalAsAttribute 属性将数组作为 C 样式数组封送。 例如:

托管的签名

Sub [New](<MarshalAs(UnmanagedType.LPARRAY, SizeParamIndex:=1)> _
   ar(,) As Long, size As Integer)
Sub [New](<MarshalAs(UnmanagedType.LPARRAY, _
   ArraySubType:=UnmanagedType.LPStr, SizeParamIndex:=1)> _
   ar(,) As String, size As Integer)
void New([MarshalAs(UnmanagedType.LPARRAY, SizeParamIndex=1)]
   long [,] ar, int size );
void New([MarshalAs(UnmanagedType.LPARRAY,
   ArraySubType= UnmanagedType.LPStr, SizeParamIndex=1)]
   String [,] ar, int size );

非托管的签名

HRESULT New(long ar[]);
HRESULT New(LPStr ar[]);

无法封送嵌套数组。 例如,使用类型库导出程序 (Tlbexp.exe) 进行导出时,以下签名将生成错误。

托管的签名

Sub [New](ar()()() As Long)
void New(long [][][] ar );

ELEMENT_TYPE_CLASS <System.Array>

将包含 System.Array 参数的方法从 .NET 程序集导出到类型库时,会将该数组参数转换为 _Array 接口。 只能通过 _Array 接口的方法和属性访问托管数组的内容。 还可通过使用 MarshalAsAttribute 属性将 System.Array 作为 SAFEARRAY 封送。 作为安全数组封送时,将数组元素视作变体封送。 例如:

托管的签名

Sub New1( ar As System.Array )
Sub New2( <MarshalAs(UnmanagedType.SafeArray)> ar As System.Array )
void New1( System.Array ar );
void New2( [MarshalAs(UnmanagedType.SafeArray)] System.Array ar );

非托管的签名

HRESULT New([in] _Array *ar);
HRESULT New([in] SAFEARRAY(VARIANT) ar);

结构中的数组

非托管结构可包含嵌入数组。 默认情况下,将这些嵌入数组字段作为 SAFEARRAY 封送。 在以下示例中,s1 是直接在结构本身中分配的嵌入数组。

非托管表示形式

struct MyStruct {
    short s1[128];
}

数组可作为 UnmanagedType 封送,这需要设置 MarshalAsAttribute 字段。 只能将大小设置为常数。 以下代码演示 MyStruct 的相应托管定义。

Public Structure <StructLayout(LayoutKind.Sequential)> MyStruct
   Public <MarshalAs(UnmanagedType.ByValArray, SizeConst := 128)> _
     s1() As Short
End Structure
[StructLayout(LayoutKind.Sequential)]
public struct MyStruct {
   [MarshalAs(UnmanagedType.ByValArray, SizeConst=128)] public short[] s1;
}

请参阅