Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
.NET 7 представляет новый механизм настройки маршалинга типов при использовании взаимодействия, генерируемого из исходного кода. Генератор источника для P/Invokes распознает MarshalUsingAttribute и NativeMarshallingAttribute как индикаторы для пользовательского маршалинга типа.
NativeMarshallingAttribute можно применить к типу, чтобы указать на настройку маршализации по умолчанию для этого типа. Элемент MarshalUsingAttribute можно применить к параметру или возвращаемому значению, чтобы указать настраиваемое маршалирование для конкретного использования типа, имеющий приоритет над любым NativeMarshallingAttribute, который может быть на самом типе. Оба этих атрибута ожидают Typeтип маршаллера точки входа, помеченный одним или несколькими CustomMarshallerAttribute атрибутами. Каждый CustomMarshallerAttribute указывает, какую реализацию маршалера следует использовать для маршалирования указанного управляемого типа для указанного MarshalMode.
Реализация Маршаллера
Пользовательские реализации маршаллировщика могут быть бездефисными или с отслеживанием состояния. Если тип маршаллера является классом static, он считается бездеятельностным, а методы реализации не должны отслеживать состояние в процессе вызовов. Если это тип значения, он считается имеющим состояние, и один экземпляр этого маршалера будет использоваться для маршалирования определенного параметра или возвращаемого значения. Использование уникального экземпляра позволяет сохранять состояние в процессе маршаллинга и демаршаллинга.
Фигуры Маршаллера
Набор методов, ожидаемых генератором маршаллинга от пользовательского типа маршаллера, называется фигурой маршаллера. Для поддержки безгосударственных, статических пользовательских типов маршализаторов в .NET Standard 2.0 (который не поддерживает статические методы интерфейсов), а также для повышения производительности типы интерфейсов не используются для определения и реализации форм маршализации. Вместо этого фигуры документируются в статье "Пользовательские фигуры маршаллера ". Ожидаемые методы (или форма) зависят от того, является ли маршаллизатор без состояния или с состоянием, а также поддерживает ли он маршаллирование от управляемого к неуправляемому, от неуправляемого к управляемому или оба направления (объявленные с CustomMarshallerAttribute.MarshalMode). Пакет SDK для .NET включает анализаторы и инструменты исправления кода, которые помогают в реализации маршаллеров, соответствующих необходимым форматам.
MarshalMode
Указанный MarshalMode в CustomMarshallerAttribute определяет ожидаемую поддержку маршаллирования и структуру реализации маршаллизатора. Все режимы поддерживают реализации маршаллера без отслеживания состояния. Режимы маршаллинга элементов не поддерживают реализации маршализатора с отслеживанием состояния.
MarshalMode |
Ожидаемая поддержка | Может быть с сохранением состояния |
|---|---|---|
| ManagedToUnmanagedIn | Управляемый в неуправляемый | Да |
| ManagedToUnmanagedRef | Переход от управляемого к неуправляемому и от неуправляемого к управляемому | Да |
| ManagedToUnmanagedOut | От неуправляемого к управляемому | Да |
| UnmanagedToManagedIn | От неуправляемого к управляемому | Да |
| UnmanagedToManagedRef | Переход от управляемого к неуправляемому и от неуправляемого к управляемому | Да |
| UnmanagedToManagedOut | Управляемый в неуправляемый | Да |
| ElementIn | Управляемый в неуправляемый | нет |
| ElementRef | Переход от управляемого к неуправляемому и от неуправляемого к управляемому | нет |
| ElementOut | От неуправляемого к управляемому | нет |
Используйте MarshalMode.Default для указания того, что реализация маршаллера применяется к любому поддерживаемому режиму, основываясь на реализуемых им методах. Если указать маршаллировщик для более конкретного MarshalMode, то этот маршаллировщик имеет приоритет над тем, который помечен как Default.
Базовое использование
Маршаллирование одного значения
Чтобы создать пользовательский маршаллировщик для типа, необходимо определить основной тип маршаллировщика, реализующий необходимые методы маршаллинга. Тип маршаллера точки входа может быть классом static или struct, и он должен быть помечен CustomMarshallerAttribute.
Например, рассмотрим простой тип, который нужно передавать между управляемым и неуправляемым кодом.
public struct Example
{
public string Message;
public int Flags;
}
Определите тип маршаллера
Вы можете создать тип ExampleMarshaller, который помечен с помощью CustomMarshallerAttribute, чтобы указать, что это маркер типа точки входа, предоставляющий пользовательские сведения о маршаллинге для типа Example. Первым аргументом CustomMarshallerAttribute является управляемый тип, предназначенный для маршализатора. Второй аргумент — это MarshalMode, который поддерживает маршаллизатор. Третий аргумент — сам тип обработчика данных, то есть тип, реализующий методы в ожидаемой структуре.
[CustomMarshaller(typeof(Example), MarshalMode.Default, typeof(ExampleMarshaller))]
internal static unsafe class ExampleMarshaller
{
public static ExampleUnmanaged ConvertToUnmanaged(Example managed)
{
return new ExampleUnmanaged()
{
Message = (IntPtr)Utf8StringMarshaller.ConvertToUnmanaged(managed.Message),
Flags = managed.Flags
};
}
public static Example ConvertToManaged(ExampleUnmanaged unmanaged)
{
return new Example()
{
Message = Utf8StringMarshaller.ConvertToManaged((byte*)unmanaged.Message),
Flags = unmanaged.Flags
};
}
public static void Free(ExampleUnmanaged unmanaged)
{
Utf8StringMarshaller.Free((byte*)unmanaged.Message);
}
internal struct ExampleUnmanaged
{
public IntPtr Message;
public int Flags;
}
}
В приведенном здесь ExampleMarshaller примере реализуется маршалирование без состояния из управляемого Example типа в оборачиваемое представление в формате, который ожидает машинный код (ExampleUnmanaged), и обратно. Метод Free используется для освобождения любых неуправляемых ресурсов, выделенных в процессе маршаллинга. Логика маршаллинга полностью контролируется реализацией маршаллером. Маркировка полей в структуре с помощью MarshalAsAttribute не оказывает никакого влияния на создаваемый код.
ExampleMarshaller Ниже приведен тип точки входа и тип реализации. Однако при необходимости маршалирование можно настроить для разных режимов, создав отдельные типы маршаллеров для каждого режима. Добавьте новый CustomMarshallerAttribute для каждого режима, как в следующем классе. Как правило, это необходимо только для маршаллеров с состоянием, где тип маршалера — это struct, который сохраняет состояние между вызовами. По общепринятым правилам, типы реализации вложены в тип маршаллера входной точки.
[CustomMarshaller(typeof(Example), MarshalMode.ManagedToUnmanagedIn, typeof(ExampleMarshaller.ManagedToUnmanagedIn))]
[CustomMarshaller(typeof(Example), MarshalMode.ManagedToUnmanagedOut, typeof(ExampleMarshaller.UnmanagedToManagedOut))]
internal static class ExampleMarshaller
{
internal struct ManagedToUnmanagedIn
{
public void FromManaged(TManaged managed) => throw new NotImplementedException();
public TNative ToUnmanaged() => throw new NotImplementedException();
public void Free() => throw new NotImplementedException()
}
internal struct UnmanagedToManagedOut
{
public void FromUnmanaged(TNative unmanaged) => throw new NotImplementedException();
public TManaged ToManaged() => throw new NotImplementedException();
public void Free() => throw new NotImplementedException();
}
}
Укажите, какой маршалер следует использовать
После создания типа маршаллировщика можно использовать MarshalUsingAttribute на сигнатуре метода взаимодействия, чтобы указать, что вы хотите использовать этот маршаллировщик для определенного параметра или возвращаемого значения. В MarshalUsingAttribute тип маршаллера точки входа принимается в качестве аргумента, в данном случае ExampleMarshaller.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ExampleMarshaller))]
internal static partial Example ConvertExample(
[MarshalUsing(typeof(ExampleMarshaller))] Example example);
Чтобы избежать необходимости указывать тип маршаллера для каждого использования типа Example, можно также применить NativeMarshallingAttribute непосредственно к типу Example. Это означает, что указанный маршалер должен использоваться по умолчанию для всех видов использования Example типа в генерации источников взаимодействия.
[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
public string Message;
public int Flags;
}
Затем Example тип можно использовать в методах P/Invoke, генерируемых на основе исходного кода, без указания типа маршаллатора. В следующем примере ExampleMarshaller P/Invoke будет использоваться для маршалирования параметра из управляемого в неуправляемый. Он также будет использоваться для маршалирования возвращаемого значения из неуправляемого в управляемый.
[LibraryImport("nativelib")]
internal static partial Example ConvertExample(Example example);
Чтобы использовать другой маршализатор для конкретного параметра или возвращаемого значения типа Example, укажите MarshalUsingAttribute в месте использования. В следующем примере ExampleMarshaller P/Invoke будет использоваться для маршалирования параметра из управляемого в неуправляемый.
OtherExampleMarshaller будет использоваться для преобразования возвращаемого значения из неуправляемого кода в управляемый.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(OtherExampleMarshaller))]
internal static partial Example ConvertExample(Example example);
Маршаллинг коллекций
Неуниверсальные коллекции
Для коллекций, которые не являются универсальными по типу элемента, следует создать простой тип маршаллера, как показано ранее.
Универсальные коллекции
Чтобы создать кастомный маршаллер для обобщенного типа коллекции, можно использовать атрибут ContiguousCollectionMarshallerAttribute. Этот атрибут указывает, что маршализатор предназначен для смежных коллекций, таких как массивы или списки, и предоставляет набор методов, которые маршаллировщик должен реализовать для поддержки маршаллинга элементов коллекции. Тип элемента маршализированной коллекции также должен иметь маршализатор, определенный для него с помощью методов, описанных ранее.
Примените ContiguousCollectionMarshallerAttribute к типу точки входа маршаллера, чтобы обозначить, что он предназначен для смежных коллекций. Тип точки входа маршаллера должен иметь на один параметр типа больше, чем связанный управляемый тип. Последний параметр типа является заполнителем и будет заполнен генератором исходного кода неуправляемым типом для элемента коллекции.
Например, можно указать настраиваемый List<T>маршалинг. В следующем коде ListMarshaller является как точкой входа, так и реализацией. Он соответствует одной из фигур маршаллера, ожидаемых для пользовательского маршаллинга коллекции. (Обратите внимание, что это неполный пример.)
[ContiguousCollectionMarshaller]
[CustomMarshaller(typeof(List<>), MarshalMode.Default, typeof(ListMarshaller<,>.DefaultMarshaller))]
public unsafe static class ListMarshaller<T, TUnmanagedElement> where TUnmanagedElement : unmanaged
{
public static class DefaultMarshaller
{
public static byte* AllocateContainerForUnmanagedElements(List<T> managed, out int numElements)
{
numElements = managed.Count;
nuint collectionSizeInBytes = managed.Count * /* size of T */;
return (byte*)NativeMemory.Alloc(collectionSizeInBytes);
}
public static ReadOnlySpan<T> GetManagedValuesSource(List<T> managed)
=> CollectionsMarshal.AsSpan(managed);
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(byte* unmanaged, int numElements)
=> new Span<TUnmanagedElement>((TUnmanagedElement*)unmanaged, numElements);
public static List<T> AllocateContainerForManagedElements(byte* unmanaged, int length)
=> new List<T>(length);
public static Span<T> GetManagedValuesDestination(List<T> managed)
=> CollectionsMarshal.AsSpan(managed);
public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(byte* nativeValue, int numElements)
=> new ReadOnlySpan<TUnmanagedElement>((TUnmanagedElement*)nativeValue, numElements);
public static void Free(byte* unmanaged)
=> NativeMemory.Free(unmanaged);
}
}
В примере ListMarshaller используется маршализатор коллекции без состояния, который поддерживает маршаринг из управляемой в неуправляемую среду и из неуправляемой в управляемую для List<T>. В следующем примере P/Invoke с использованием ListMarshaller будет применяться для маршалирования контейнера коллекции параметра из управляемого в неуправляемый и для маршалирования контейнера коллекции возвращаемого значения из неуправляемого в управляемый. Генератор исходного кода создаст код для копирования элементов из параметра list в контейнер, предоставленный маршаллером. Поскольку int является blittable, сами элементы не нуждаются в маршаллировании.
CountElementName указывает, что numValues параметр должен использоваться в качестве количества элементов при маршалировании возвращаемого значения из неуправляемого кода в управляемый код.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = nameof(numValues))]
internal static partial List<int> ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
out int numValues);
Если тип элемента коллекции является пользовательским типом, можно указать маршализатор элементов для этого с дополнительным MarshalUsingAttributeElementIndirectionDepth = 1.
ListMarshaller будет обрабатывать контейнер коллекции, а ExampleMarshaller будет маршалировать каждый элемент, преобразуя из неуправляемого состояния в управляемое и наоборот.
ElementIndirectionDepth указывает, что маршализатор должен применяться к элементам коллекции, которые на уровень глубже, чем коллекция.
[LibraryImport("nativelib")]
[MarshalUsing(typeof(ListMarshaller<,>), CountElementName = nameof(numValues))]
[MarshalUsing(typeof(ExampleMarshaller), ElementIndirectionDepth = 1)]
internal static partial void ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))]
[MarshalUsing(typeof(ExampleMarshaller), ElementIndirectionDepth = 1)]
List<Example> list,
out int numValues);