System.Runtime.InteropServices.ICustomMarshaler 接口

本文提供了此 API 参考文档的补充说明。

ICustomMarshaler 接口提供用于处理方法调用的自定义包装器。

封送器在旧接口和新接口的功能之间提供了桥梁。 自定义封送处理具有以下优势:

  • 它使设计为使用旧接口的客户端应用程序还可以使用实现新接口的服务器。
  • 它使构建的客户端应用程序能够使用新接口来处理实现旧接口的服务器。

如果接口引入了不同的封送处理行为,或者以不同的方式公开给组件对象模型(COM),则可以设计自定义封送器,而不是使用互操作封送器。 通过使用自定义封送器,可以最大程度地减少新 .NET Framework 组件和现有 COM 组件之间的区别。

例如,假设你要开发名为 <a0/> 的托管接口。 当此接口通过标准 COM 可调用包装器(CCW)向 COM 公开时,它具有与托管接口相同的方法,并使用内置于互操作封送器中的封送规则。 现在假设调用 IOld 的已知 COM 接口已提供与 INew 接口相同的功能。 通过设计自定义封送器,可以提供非托管实现, IOld 该实现只需将调用委托给接口的 INew 托管实现。 因此,自定义封送器充当托管接口和非托管接口之间的桥梁。

注意

从托管代码调用到仅调度接口上的非托管代码时,不会调用自定义封送器。

定义封送处理类型

在生成自定义封送器之前,必须定义将封送的托管和非托管接口。 这些接口通常执行相同的功能,但以不同的方式公开给托管和非托管对象。

托管编译器从元数据生成托管接口,生成的接口类似于任何其他托管接口。 以下示例显示了一个典型的接口。

public interface INew
{
    void NewMethod();
}
Public Interface INew
    Sub NewMethod()
End Interface

在接口定义语言(IDL)中定义非托管类型,并使用 Microsoft 接口定义语言(MIDL)编译器对其进行编译。 在库语句中定义接口,并为其分配具有通用唯一标识符(UUID)属性的接口 ID,如以下示例所示。

 [uuid(9B2BAADA-0705-11D3-A0CD-00C04FA35826)]
library OldLib {
     [uuid(9B2BAADD-0705-11D3-A0CD-00C04FA35826)]
     interface IOld : IUnknown
         HRESULT OldMethod();
}

MIDL 编译器生成多个输出文件。 如果接口在 Old.idl 中定义,则输出文件 Old_i.c 定义具有 const 接口标识符(IID)的变量,如以下示例所示。

const IID IID_IOld = {0x9B2BAADD,0x0705,0x11D3,{0xA0,0xCD,0x00,0xC0,0x4F,0xA3,0x58,0x26}};

Old.h 文件也由 MIDL 生成。 它包含可包含在 C++ 源代码中的接口的 C++ 定义。

实现 ICustomMarshaler 接口

自定义封送器必须实现 ICustomMarshaler 接口,以便为运行时提供适当的包装器。

以下 C# 代码显示必须由所有自定义封送器实现的基本接口。

public interface ICustomMarshaler
{
    Object MarshalNativeToManaged(IntPtr pNativeData);
    IntPtr MarshalManagedToNative(Object ManagedObj);
    void CleanUpNativeData(IntPtr pNativeData);
    void CleanUpManagedData(Object ManagedObj);
    int GetNativeDataSize();
}
Public Interface ICustomMarshaler
     Function MarshalNativeToManaged( pNativeData As IntPtr ) As Object
     Function MarshalManagedToNative( ManagedObj As Object ) As IntPtr
     Sub CleanUpNativeData( pNativeData As IntPtr )
     Sub CleanUpManagedData( ManagedObj As Object )
     Function GetNativeDataSize() As Integer
End Interface

ICustomMarshaler 接口包括提供转换支持、清理支持和有关要封送的数据的信息的方法。

操作类型 ICustomMarshaler 方法 说明
转换(从本机代码转换为托管代码) MarshalNativeToManaged 将指向本机数据的指针封送到托管对象中。 此方法返回一个自定义运行时可调用包装器(RCW),该包装器可以封送作为参数传递的非托管接口。 封送器应返回该类型的自定义 RCW 实例。
转换(从托管代码转换为本机代码) MarshalManagedToNative 将托管对象封送到指向本机数据的指针中。 此方法返回一个自定义 COM 可调用包装器(CCW),该包装器可以封送作为参数传递的托管接口。 封送器应返回该类型的自定义 CCW 实例。
清理(本机代码) CleanUpNativeData 使封送器能够清理方法返回 MarshalManagedToNative 的本机数据(CCW)。
清理(托管代码) CleanUpManagedData 使封送器能够清理方法返回 MarshalNativeToManaged 的托管数据(RCW)。
信息(有关本机代码) GetNativeDataSize 返回要封送的非托管数据的大小。

转换

ICustomMarshaler.MarshalNativeToManaged

将指向本机数据的指针封送到托管对象中。 此方法返回一个自定义运行时可调用包装器(RCW),该包装器可以封送作为参数传递的非托管接口。 封送器应返回该类型的自定义 RCW 实例。

ICustomMarshaler.MarshalManagedToNative

将托管对象封送到指向本机数据的指针中。 此方法返回一个自定义 COM 可调用包装器(CCW),该包装器可以封送作为参数传递的托管接口。 封送器应返回该类型的自定义 CCW 实例。

清理

ICustomMarshaler.CleanUpNativeData

使封送器能够清理方法返回 MarshalManagedToNative 的本机数据(CCW)。

ICustomMarshaler.CleanUpManagedData

使封送器能够清理方法返回 MarshalNativeToManaged 的托管数据(RCW)。

大小信息

ICustomMarshaler.GetNativeDataSize

返回要封送的非托管数据的大小。

注意

如果自定义封送器调用在从本机封送到托管或清理时设置最后一个 P/Invoke 错误的任何方法,则返回Marshal.GetLastWin32Error()Marshal.GetLastPInvokeError()的值将在封送或清理调用中表示调用。 这可能会导致在将自定义封送器与设置为 <a0/a0> 的 P/Invokes DllImportAttribute.SetLastError 配合使用时错过错误。 若要保留最后一个 P/Invoke 错误,请使用Marshal.GetLastPInvokeError()实现中的ICustomMarshaler方法和Marshal.SetLastPInvokeError(Int32)方法。

实现 GetInstance 方法

除了实现 ICustomMarshaler 接口之外,自定义封送器还必须实现一个 static 调用 GetInstance 的方法,该方法接受 String 作为参数并具有返回类型 ICustomMarshaler。 公共 static 语言运行时的 COM 互操作层调用此方法以实例化自定义封送器实例。 传递给 GetInstance 的字符串是一个 Cookie,该方法可用于自定义返回的自定义封送器。 以下示例演示了一个最少但完整的 ICustomMarshaler 实现。

public class NewOldMarshaler : ICustomMarshaler
{
    public static ICustomMarshaler GetInstance(string pstrCookie)
        => new NewOldMarshaler();

    public Object MarshalNativeToManaged(IntPtr pNativeData) => throw new NotImplementedException();
    public IntPtr MarshalManagedToNative(Object ManagedObj) => throw new NotImplementedException();
    public void CleanUpNativeData(IntPtr pNativeData) => throw new NotImplementedException();
    public void CleanUpManagedData(Object ManagedObj) => throw new NotImplementedException();
    public int GetNativeDataSize() => throw new NotImplementedException();
}

应用 MarshalAsAttribute

若要使用自定义封送器,必须将属性应用于 MarshalAsAttribute 正在封送的参数或字段。

还必须将 UnmanagedType.CustomMarshaler 枚举值传递给 MarshalAsAttribute 构造函数。 此外,还必须使用以下命名参数之一指定 MarshalType 字段:

  • MarshalType (必需):自定义封送器程序集限定的名称。 名称应包括自定义封送器命名空间和类。 如果未在所使用的程序集中定义自定义封送器,则必须指定在其中定义它的程序集的名称。

    注意

    可以使用字段 MarshalTypeRef 而不是 MarshalType 字段。 MarshalTypeRef 采用更易于指定的类型。

  • MarshalCookie (可选):传递给自定义封送器的 Cookie。 可以使用 Cookie 向封送器提供其他信息。 例如,如果使用相同的封送器来提供许多包装器,则 Cookie 会标识特定的包装器。 Cookie 将 GetInstance 传递给封送器的方法。

MarshalAsAttribute 属性标识自定义封送器,以便它可以激活相应的包装器。 然后,公共语言运行时的互操作服务检查属性,并在第一次需要封送参数(参数或字段)时创建自定义封送器。

然后,运行时对自定义封送器调用 MarshalNativeToManagedMarshalManagedToNative 方法,以激活正确的包装器来处理调用。

使用自定义封送器

自定义封送器完成后,可以将它用作特定类型的自定义包装器。 以下示例显示了托管接口的定义 IUserData

interface IUserData
{
    void DoSomeStuff(INew pINew);
}
Public Interface IUserData
    Sub DoSomeStuff(pINew As INew)
End Interface

在下面的示例中, IUserData 该接口使用 NewOldMarshaler 自定义封送器来启用非托管客户端应用程序,以便将 IOld 接口传递给 DoSomeStuff 该方法。 方法的 DoSomeStuff 托管说明采用接口 INew ,如前面的示例所示,而非托管版本的 DoSomeStuff 采用 IOld 接口指针,如以下示例所示。

[uuid(9B2BAADA-0705-11D3-A0CD-00C04FA35826)]
library UserLib {
     [uuid(9B2BABCD-0705-11D3-A0CD-00C04FA35826)]
     interface IUserData : IUnknown
         HRESULT DoSomeStuff(IUnknown* pIOld);
}

通过导出托管定义的类型库生成此示例中显示的非托管定义 IUserData ,而不是标准定义。 MarshalAsAttribute应用于INew方法托管定义DoSomeStuff中的参数的属性指示该参数使用自定义封送器,如以下示例所示。

using System.Runtime.InteropServices;
Imports System.Runtime.InteropServices
interface IUserData
{
    void DoSomeStuff(
        [MarshalAs(UnmanagedType.CustomMarshaler,
         MarshalType="NewOldMarshaler")]
    INew pINew
    );
}
Public Interface IUserData
    Sub DoSomeStuff( _
        <MarshalAs(UnmanagedType.CustomMarshaler, _
        MarshalType := "MyCompany.NewOldMarshaler")> pINew As INew)
End Interface

在前面的示例中,提供给 MarshalAsAttribute 属性的第一个参数是 UnmanagedType.CustomMarshaler 枚举值 UnmanagedType.CustomMarshaler

第二个参数是 MarshalType 字段,该字段提供自定义封送器程序集限定的名称。 此名称由自定义封送器(MarshalType="MyCompany.NewOldMarshaler")的命名空间和类组成。