Интерфейс System.Runtime.InteropServices.ICustomMarshaler

В этой статье приводятся дополнительные замечания к справочной документации по этому API.

Интерфейс ICustomMarshaler предоставляет пользовательские оболочки для обработки вызовов методов.

Маршаллизатор предоставляет мост между функциональностью старых и новых интерфейсов. Пользовательское маршалинг обеспечивает следующие преимущества:

  • Он позволяет клиентским приложениям, предназначенным для работы со старым интерфейсом, также работать с серверами, реализующими новый интерфейс.
  • Это позволяет клиентским приложениям работать с новым интерфейсом для работы с серверами, реализующими старый интерфейс.

Если у вас есть интерфейс, который вводит другое поведение маршалинга или который предоставляется объектной модели компонента (COM), можно создать пользовательский маршализатор вместо использования маршализатора взаимодействия. С помощью пользовательского маршаллатора можно свести к минимуму различие между новыми компонентами платформа .NET Framework и существующими COM-компонентами.

Например, предположим, что вы разрабатываете управляемый интерфейс INew. Если этот интерфейс предоставляется com через стандартную вызываемую оболочку COM (CCW), он имеет те же методы, что и управляемый интерфейс и использует правила маршалинга, встроенные в маршализатор взаимодействия. Теперь предположим, что известный COM-интерфейс IOld уже предоставляет те же функции, что INew и интерфейс. Создав пользовательский маршаллировщик, вы можете предоставить неуправляемую реализацию IOld , которая просто делегирует вызовы управляемой INew реализации интерфейса. Поэтому пользовательский маршаллировщик выступает в качестве моста между управляемыми и неуправляемными интерфейсами.

Примечание.

Пользовательские маршаллеры не вызываются при вызове управляемого кода в неуправляемый код в интерфейсе только для отправки.

Определение типа маршалинга

Прежде чем создать пользовательский маршаллировщик, необходимо определить управляемые и неуправляемые интерфейсы, которые будут маршалированы. Эти интерфейсы обычно выполняют ту же функцию, но предоставляются по-разному для управляемых и неуправляемых объектов.

Управляемый компилятор создает управляемый интерфейс из метаданных, а полученный интерфейс выглядит как любой другой управляемый интерфейс. В следующем примере показан типичный интерфейс.

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

Вы определяете неуправляемый тип в языке определения интерфейса (IDL) и компилируете его с помощью компилятора языка определения интерфейса (MIDL). Вы определяете интерфейс в инструкции библиотеки и назначаете ему идентификатор интерфейса с атрибутом универсального уникального идентификатора (UUID), как показано в следующем примере.

 [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 Description
Преобразование (из машинного кода в управляемый код) MarshalNativeToManaged Маршалирует указатель на собственные данные в управляемый объект. Этот метод возвращает вызываемую оболочку среды выполнения (RCW), которая может маршалить неуправляемый интерфейс, передаваемый в качестве аргумента. Маршаллизатор должен возвращать экземпляр пользовательского RCW для этого типа.
Преобразование (из управляемого в машинный код) MarshalManagedToNative Маршалирует управляемый объект в указатель на собственные данные. Этот метод возвращает настраиваемую вызываемую оболочку COM(CCW), которая может маршалал управляемого интерфейса, передаваемого в качестве аргумента. Маршаллизатор должен возвращать экземпляр пользовательского CCW для этого типа.
Очистка (машинного кода) CleanUpNativeData Позволяет маршаллировщику очистить собственные данные (CCW), возвращаемые методом MarshalManagedToNative .
Очистка (управляемого кода) CleanUpManagedData Позволяет маршаллизатору очистить управляемые данные (RCW), возвращаемые методом MarshalNativeToManaged .
Сведения (о машинный код) GetNativeDataSize Возвращает размер неуправляемых данных, которые необходимо маршалировать.

Преобразование

ICustomMarshaler.MarshalNativeToManaged

Маршалирует указатель на собственные данные в управляемый объект. Этот метод возвращает вызываемую оболочку среды выполнения (RCW), которая может маршалить неуправляемый интерфейс, передаваемый в качестве аргумента. Маршаллизатор должен возвращать экземпляр пользовательского RCW для этого типа.

ICustomMarshaler.MarshalManagedToNative

Маршалирует управляемый объект в указатель на собственные данные. Этот метод возвращает настраиваемую вызываемую оболочку COM(CCW), которая может маршалал управляемого интерфейса, передаваемого в качестве аргумента. Маршаллизатор должен возвращать экземпляр пользовательского CCW для этого типа.

Очистка

ICustomMarshaler.CleanUpNativeData

Позволяет маршаллировщику очистить собственные данные (CCW), возвращаемые методом MarshalManagedToNative .

ICustomMarshaler.CleanUpManagedData

Позволяет маршаллизатору очистить управляемые данные (RCW), возвращаемые методом MarshalNativeToManaged .

Сведения о размере

ICustomMarshaler.GetNativeDataSize

Возвращает размер неуправляемых данных, которые необходимо маршалировать.

Примечание.

Если пользовательский маршаллировщик вызывает любые методы, которые задают последнюю ошибку P/Invoke при маршалинге из собственного кода в управляемый или при очистке, значение Marshal.GetLastWin32Error() , возвращаемое и Marshal.GetLastPInvokeError() будет представлять вызов в вызовах маршалинга или очистки. Это может привести к пропуску ошибок при использовании пользовательских маршаллеров с P/Invokes с DllImportAttribute.SetLastError заданным значением true. Чтобы сохранить последнюю ошибку P/Invoke, используйте Marshal.GetLastPInvokeError()Marshal.SetLastPInvokeError(Int32) методы в ICustomMarshaler реализации.

Реализация метода GetInstance

Помимо реализации ICustomMarshaler интерфейса настраиваемые маршаллеры должны реализовать static метод, который GetInstance принимает в String качестве параметра и имеет тип возвращаемого ICustomMarshalerзначения. Этот static метод вызывается уровнем COM-взаимодействия среды CLR для создания экземпляра пользовательского маршаллатора. Передаваемая строка представляет собой файл cookie, который GetInstance метод может использовать для настройки возвращаемого пользовательского маршаллера. В следующем примере показана минимальная, но полная реализация 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 определяет пользовательский маршализатор, чтобы он смог активировать соответствующую оболочку. Затем служба взаимодействия среды cl language проверяет атрибут и создает пользовательский маршализатор при первом маршале аргумента (параметр или поле).

Затем среда выполнения вызывает MarshalNativeToManaged методы и MarshalManagedToNative методы пользовательского маршаллатора, чтобы активировать правильную оболочку для обработки вызова.

Использование пользовательского маршаллатора

После завершения пользовательского маршаллатора его можно использовать в качестве пользовательской оболочки для определенного типа. В следующем примере показано определение управляемого IUserData интерфейса:

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

В следующем примере интерфейс использует NewOldMarshaler пользовательский маршализатор для IUserData включения неуправляемых клиентских приложений для передачи IOld интерфейса методуDoSomeStuff. Управляемое описание DoSomeStuff метода принимает интерфейс, как показано в предыдущем примере, в то время как неуправляемая версия DoSomeStuffIOld принимает INew указатель интерфейса, как показано в следующем примере.

[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").