Udostępnij za pośrednictwem


Generowanie źródła dla niestandardowego marshalingu

Platforma .NET 7 wprowadza nowy mechanizm dostosowywania sposobu działania typu podczas korzystania z międzyoperacyjności generowanej przez źródło. Generator źródła dla P/Invoke rozpoznajeMarshalUsingAttribute i NativeMarshallingAttribute jako wskaźniki dla niestandardowego marshallingu typu.

NativeMarshallingAttribute Można zastosować do typu, aby wskazać domyślne niestandardowe marshalling dla tego typu. Można MarshalUsingAttribute go zastosować do parametru lub wartości zwracanej, aby wskazać niestandardowe marshalling dla tego konkretnego użycia typu, mając pierwszeństwo przed dowolnym NativeMarshallingAttribute , który może znajdować się w samym typie. Oba te atrybuty oczekują Typetypu marshallera punktu wejścia , który jest oznaczony co najmniej jednym CustomMarshallerAttribute atrybutem. Każdy wskazuje, która implementacja CustomMarshallerAttribute marshallera powinna być używana do marshalingu określonego typu zarządzanego dla określonego MarshalModetypu .

Implementacja Marshallera

Implementacje Marshallera mogą być bezstanowe lub stanowe. Jeśli typ marshaller jest klasą static , jest uważany za bezstanowy. Jeśli jest to typ wartości, jest uważany za stanowy, a jedno wystąpienie tego marshallera będzie używane do marshalingu określonego parametru lub wartości zwracanej. Różne kształty implementacji marshallera są oczekiwane na podstawie tego, czy marshaller jest bezstanowy, czy stanowy i czy obsługuje marshalling z zarządzanego do niezarządzanego, niezarządzanego do zarządzanego, czy obu. Zestaw SDK platformy .NET zawiera analizatory i poprawki kodu, które ułatwiają implementowanie marshallerów zgodnych z wymaganymi kształtami.

MarshalMode

Określony MarshalMode w elemecie CustomMarshallerAttribute określa oczekiwane wsparcie i kształt dla wdrożenia marshallera. Wszystkie tryby obsługują bezstanowe implementacje marshallerów. Tryby marshalingu elementów nie obsługują implementacji marshallera stanowego.

MarshalMode Oczekiwana obsługa Może być stanowy
ManagedToUnmanagedIn Zarządzanie niezarządzane Tak
ManagedToUnmanagedRef Zarządzana do zarządzania niezarządzanych i niezarządzanych Tak
ManagedToUnmanagedOut Niezarządzane do zarządzania Tak
UnmanagedToManagedIn Niezarządzane do zarządzania Tak
UnmanagedToManagedRef Zarządzana do zarządzania niezarządzanych i niezarządzanych Tak
UnmanagedToManagedOut Zarządzanie niezarządzane Tak
ElementIn Zarządzanie niezarządzane Nie.
ElementRef Zarządzana do zarządzania niezarządzanych i niezarządzanych Nie.
ElementOut Niezarządzane do zarządzania Nie.

MarshalMode.Default wskazuje, że implementacja marshallera powinna być używana dla dowolnego obsługiwanego trybu. Jeśli określono również implementację marshallera dla bardziej szczegółowego MarshalMode , pierwszeństwo ma wartość MarshalMode.Default.

Podstawowy sposób użycia

Możemy określić NativeMarshallingAttribute typ wskazujący typ marshallera punktu wejścia, który jest klasą static lub .struct

[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
    public string Message;
    public int Flags;
}

ExampleMarshaller, typ marshallera punktu wejścia, jest oznaczony znakiem CustomMarshallerAttribute, wskazując na typ implementacji marshallera. W tym przykładzie jest to zarówno punkt wejścia, ExampleMarshaller jak i implementacja. Jest on zgodny z kształtami marshaller oczekiwanymi dla niestandardowego marshallingu wartości.

[CustomMarshaller(typeof(Example), MarshalMode.Default, typeof(ExampleMarshaller))]
internal static class ExampleMarshaller
{
    public static ExampleUnmanaged ConvertToUnmanaged(Example managed)
        => throw new NotImplementedException();

    public static Example ConvertToManaged(ExampleUnmanaged unmanaged)
        => throw new NotImplementedException();

    public static void Free(ExampleUnmanaged unmanaged)
        => throw new NotImplementedException();

    internal struct ExampleUnmanaged
    {
        public IntPtr Message;
        public int Flags;
    }
}

W ExampleMarshaller tym przykładzie jest bezstanowy marshaller, który implementuje obsługę marshalingu z zarządzanego do niezarządzanego i niezarządzanego do zarządzanego. Logika marshallingu jest całkowicie kontrolowana przez implementację marshallera. Oznaczanie pól w strukturze bez MarshalAsAttribute wpływu na wygenerowany kod.

Typ Example może być następnie używany w generowaniu źródła P/Invoke. W poniższym przykładzie ExampleMarshaller P/Invoke będzie używany do marshalingu parametru z zarządzanego do niezarządzanego. Będzie ona również używana do marshalingu wartości zwracanej z niezarządzanej do zarządzanej.

[LibraryImport("nativelib")]
internal static partial Example ConvertExample(Example example);

Aby użyć innego marshallera dla określonego Example użycia typu, określ MarshalUsingAttribute w lokacji użycia. W poniższym przykładzie ExampleMarshaller P/Invoke będzie używany do marshalingu parametru z zarządzanego do niezarządzanego. OtherExampleMarshaller będzie używany do marshalingu wartości zwracanej z niezarządzanej do zarządzanej.

[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(OtherExampleMarshaller))]
internal static partial Example ConvertExample(Example example);

Kolekcje

Zastosuj element ContiguousCollectionMarshallerAttribute do typu punktu wejścia marshallera, aby wskazać, że jest przeznaczony dla ciągłych kolekcji. Typ musi mieć jeszcze jeden parametr typu niż skojarzony typ zarządzany. Ostatni parametr typu jest symbolem zastępczym i zostanie wypełniony przez generator źródłowy z typem niezarządzanym dla typu elementu kolekcji.

Można na przykład określić niestandardowe marshalling dla elementu List<T>. W poniższym kodzie jest zarówno punktem wejścia, ListMarshaller jak i implementacją. Jest on zgodny z kształtami marshaller oczekiwanymi do niestandardowego marshallingu kolekcji.

[ContiguousCollectionMarshaller]
[CustomMarshaller(typeof(List<>), MarshalMode.Default, typeof(ListMarshaller<,>))]
public unsafe static class ListMarshaller<T, TUnmanagedElement> where TUnmanagedElement : unmanaged
{
    public static byte* AllocateContainerForUnmanagedElements(List<T> managed, out int numElements)
        => throw new NotImplementedException();

    public static ReadOnlySpan<T> GetManagedValuesSource(List<T> managed)
        => throw new NotImplementedException();

    public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(byte* unmanaged, int numElements)
        => throw new NotImplementedException();

    public static List<T> AllocateContainerForManagedElements(byte* unmanaged, int length)
        => throw new NotImplementedException();

    public static Span<T> GetManagedValuesDestination(List<T> managed)
        => throw new NotImplementedException();

    public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(byte* nativeValue, int numElements)
        => throw new NotImplementedException();

    public static void Free(byte* unmanaged)
        => throw new NotImplementedException();
}

W ListMarshaller tym przykładzie jest bezstanowy marshaller kolekcji, który implementuje obsługę marshalingu z zarządzanego do niezarządzanego i niezarządzanego do zarządzanego dla elementu List<T>. W poniższym przykładzie ListMarshaller P/Invoke będzie używany do marshalingu parametru z zarządzanego do niezarządzanego i do marshalingu wartości zwracanej z niezarządzanej do zarządzanej. CountElementName wskazuje, że numValues parametr powinien być używany jako liczba elementów podczas określania wartości zwracanej z niezarządzanej do zarządzanej.

[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = "numValues")]
internal static partial void ConvertList(
    [MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
    out int numValues);

Zobacz też