复制和锁定

封送数据时,互操作封送处理程序可以复制或锁定正在封送的数据。 复制数据是将数据的副本从一个内存位置复制到另一个内存位置。 下图显示从托管内存向非托管内存复制值类型和复制按引用传递的类型之间的差异。

展示如何复制值和引用类型的图。

按值传递的方法参数作为堆栈上的值封送到非托管代码。 复制过程是直接的。 按引用传递的参数作为堆栈上的指针传递。 引用类型也按值和按引用传递。 如下图所示,按值传递的引用类型被复制或锁定:

展示按值和按引用传递的引用类型的图。

锁定操作将数据暂时锁定在其当前内存位置,从而阻止公共语言运行时的垃圾回收器将其重定位。 封送处理程序锁定数据可减小复制的开销并提高性能。 数据的类型确定在封送处理过程中是复制数据,还是锁定数据。 在封送 String 等对象的过程中自动执行锁定操作;然而,也可使用 GCHandle 类手动锁定内存。

已设置格式的 Blittable 类

在托管和非托管内存中,已设置格式的 blittable 类具有固定布局(已设置格式)以及通用数据表示形式。 当这些类型需要封送处理时,指向堆中对象的指针被直接传递给被调用方。 被调用方可以更改该指针所引用的内存位置的内容。

注释

如果参数标记为 Out 或 In/Out,则被调用方可以更改内存内容。相反,当参数设置为作为 In(已设置格式的 blittable 类型的默认值)封送时,被调用方应当避免更改内存内容。 将同一类导出到类型库并用于进行跨单元调用时,修改 In 对象将生成问题。

已设置格式的非 Blittable 类

在托管和非托管内存中,已设置格式的 blittable 类具有固定布局(已设置格式),但数据表示形式不同。 在以下情况下,数据可能需要转换:

  • 如果按值封送非 blittable 类,则被调用方接收指向该数据结构副本的指针。

  • 如果按引用封送非 blittable 类,则被调用方接收指向数据结构副本的指针的指针。

  • 如果设置了 InAttribute 属性,则始终通过实例的状态初始化此副本,并在必要时进行封送处理。

  • 如果设置了 OutAttribute 属性,则始终在返回时将状态复制回实例,并在必要时进行封送处理。

  • 如果同时设置了 InAttributeOutAttribute ,则需要两个副本。 如果省略了其中任一个属性,则封送处理程序可以通过删掉其中一个副本进行优化。

引用类型

可按值或按引用传递引用类型。 按值传递它们时,指向该类型的指针将传递到堆栈上。 按引用传递时,在堆栈上传递指向类型指针的指针。

引用类型具有以下条件行为:

  • 如果引用类型按值传递且具有非 blittable 类型的成员,则将类型转换两次:

    • 参数传递到非托管端时转换一次。

    • 从调用返回时转换一次。

    为避免不必要的复制和转换,将这些类型作为 In 参数封送。 必须将 InAttributeOutAttribute 属性显式应用于调用方的参数,才能查看被调用方所做的更改。

  • 如果引用类型按值传递且只具有 blittable 类型的成员,则可在封送处理期间将其锁定,且调用方可看见被调用方对类型成员所做的所有更改。 如果需要此行为,请显式应用 InAttributeOutAttribute 。 如果没有这些方向属性,互操作封送处理程序就不会将方向信息导出到类型库(默认情况下作为 In 导出),这会造成 COM 跨单元封送处理方面的问题。

  • 如果引用类型按引用传递,则默认将它作为 In/Out 封送。

System.String 和 System.Text.StringBuilder

当数据按值或引用封送到非托管代码时,封送器通常会将数据复制到辅助缓冲区(可能在复制期间转换字符集),并将对缓冲区的引用传递给被调用方。 除非引用是使用 SysAllocString 分配的 BSTR,否则引用始终是使用 CoTaskMemAlloc 分配的。

作为 StringStringBuilder 按值(如 Unicode 字符串)封送时的一种优化,封送处理程序将指向内部 Unicode 缓冲区中托管字符串的直接指针传递给被调用方,而不是将其复制到新缓冲区。

谨慎

当字符串按值传递时,被调用方绝不能更改封送器传递的引用。 这样做会损坏托管堆。

通过引用传递 a System.String 时,封送器将字符串的内容复制到辅助缓冲区,然后再进行调用。 然后,它会在调用返回时将缓冲区的内容复制到新字符串中。 此方法可确保不可变的托管字符串保持不变。

当按值传递 System.Text.StringBuilder 时,封送器会将 StringBuilder 内部缓冲区的临时副本的引用传递给调用方。 调用方和被调用方必须就缓冲区的大小达成一致。 调用方负责创建长度足够长的 StringBuilder 。 被调用方必须采取必要措施确保不溢出缓冲区。 StringBuilder 是规则的例外,即默认情况下,引用值传递的引用类型作为 In 参数传递。 StringBuilder始终作为In/Out传递。

另请参阅