Поделиться через


Интерфейс 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 Описание
Преобразование (из машинного кода в управляемый код) 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. Этот static метод вызывается слоем COM-взаимодействия в CLR для создания экземпляра пользовательского маршаллера. Строка, передаваемая в 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 определяет конкретную оболочку. Куки передаётся в метод GetInstance обработчика.

Атрибут MarshalAsAttribute определяет пользовательский маршализатор, чтобы он смог активировать соответствующую оболочку. Затем служба взаимодействия общей среды выполнения проверяет атрибут и создает пользовательский механизм маршалинга при первом случае, когда требуется маршалинг аргумента (параметра или поля).

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

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

После завершения пользовательского обработчика маршаллинга его можно использовать в качестве пользовательской оболочки для определенного типа. В следующем примере показано определение управляемого 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.CustomMarshalerUnmanagedType.CustomMarshaler.

Второй параметр — это поле MarshalType, которое предоставляет полное имя сборки настраиваемого маршализатора. Это имя состоит из пространства имен и класса пользовательского маршаллера (MarshalType="MyCompany.NewOldMarshaler").