让我们面对现实吧。这个世界并不完美。几乎很少有公司在完全用托管代码开发程序,除此之外仍存在很多需要您处理的旧式非托管代码。您怎样将托管和非托管项目集成起来呢?在形式上是采用从托管应用程序调用非托管代码,还是从非托管代码应用程序调用托管代码?
幸运的是,Microsoft .NET Framework 互操作在托管和非托管代码之间开辟了一条通道,而封送处理则在该连接中扮演着非常重要的角色,因为它允许在两者之间进行数据交换(请参见图 1)。有很多因素会影响 CLR 在非托管和托管领域之间封送数据的方式,包括诸如 [MarshalAs]、[StructLayout]、[InAttribute] 和 [OutAttribute] 等属性,以及 C# 中 out 和 ref 之类的语言关键字。
Figure 1 Bridging the Gap between Managed and Unmanaged Code
因为这些因素很多,所以它可能是进行正确封送的一大难题,因为这项工作要求了解很多有关非托管和托管代码的详细情况。在本专栏中,我们会介绍您在日常工作中尝试进行封送处理时将遇到的一些基本却又容易混淆的主题。我们不会介绍自定义封送处理、封送处理复杂的结构或其他高级主题,但是如果真正理解了这些基本的概念,您就为处理这些问题做好准备了。
[InAttribute] 和 [OutAttribute]
我们要讨论的第一个封送处理主题是关于 InAttribute 和 OutAttribute 的使用,这是位于 System.Runtime.InteropServices 命名空间中的两种属性类型。(在将这些属性应用到您的代码中时,C# 和 Visual Basic 允许使用缩写形式 [In] 和 [Out],但是为了避免混淆我们坚持使用全名。)
当应用于方法参数和返回值时,这些属性会控制封送处理的方向,因此它们又被称为方向属性。[InAttribute] 告知 CLR 在调用开始的时候将数据从调用方封送到被调用方,[OutAttribute] 则告知 CLR 在返回的时候将数据从被调用方封送回调用方。调用方和被调用方都可以是非托管或托管代码。例如,在 P/Invoke 调用中,是托管代码在调用非托管代码。但是在反向 P/Invoke 调用中,就可能是非托管代码通过函数指针调用托管代码。
[InAttribute] 和 [OutAttribute] 有四种可能的使用组合:只用 [InAttribute]、只用 [OutAttribute]、同时使用 [InAttribute, OutAttribute] 以及两者都不用。如果没有指定任何一个属性,那就是要 CLR 自己确定方向属性,默认情况下通常是使用 [InAttribute]。但是,如果是 StringBuilder 类,则在没有指定任何一个属性的情况下,会同时使用 [InAttribute] 和 [OutAttribute]。(有关详细信息,请参阅后面有关 StringBuilder 的部分。)另外,使用 C# 中的 out 和 ref 关键字可能会更改已应用的属性,如图 2 所示。请注意,如果没有为参数指定关键字,就意味着它是默认的输入参数。
Figure 2 Out and Ref and Their Associated Attributes
C# 关键字 | 属性 |
(未指定) | [InAttribute] |
out | [OutAttribute] |
ref | [InAttribute],[OutAttribute] |