Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
.NET 7 introducerar en ny mekanism för anpassning av hur en typ är ordnad när du använder källgenererad interop. Källgeneratorn för P/Invokes känner igen MarshalUsingAttribute och NativeMarshallingAttribute som indikatorer för anpassad överföring av en typ.
NativeMarshallingAttribute kan tillämpas på en typ för att ange standardanpassad marshalling för den typen. MarshalUsingAttribute Kan tillämpas på en parameter eller ett returvärde för att ange anpassad sortering för den specifika användningen av typen, med företräde framför alla NativeMarshallingAttribute som kan finnas på själva typen. Båda dessa attribut förväntar sig en Type, startpunktsmarshallertypen, som är markerad med ett eller flera CustomMarshallerAttribute-attribut. Varje CustomMarshallerAttribute anger vilken marshaller-implementering som ska användas för att hantera den specificerade hanterade typen för den angivna MarshalMode.
Implementering av marshaller
Anpassade marshaller-implementeringar kan antingen vara tillståndslösa eller tillståndsbevarande. Om marshallertypen är en static klass anses den vara tillståndslös och implementeringsmetoderna bör inte spåra tillstånd mellan anrop. Om det är en värdetyp anses den vara tillståndskänslig och en instans av den marshallern används för att konvertera en specifik parameter eller ett returvärde. Användningen av en unik instans möjliggör att tillstånd bevaras genom processen för marshalling och unmarshalling.
Marshallerformer
Den uppsättning metoder som marshallinggeneratorn förväntar sig av en anpassad marshallertyp kallas marshallerformen. För att stödja tillståndslösa, statiska anpassade marshallertyper i .NET Standard 2.0 (som inte stöder statiska gränssnittsmetoder) och förbättra prestanda används inte gränssnittstyper för att definiera och implementera marshaller-formerna. Formerna dokumenteras i stället i artikeln Anpassade marshallerformer . De förväntade metoderna (eller strukturen) beror på om marshallern är tillståndslös eller tillståndsbevarande, och om den stöder marshalling från hanterad till ohanterad, ohanterad till hanterad, eller både och (deklarerad med CustomMarshallerAttribute.MarshalMode). .NET SDK innehåller analysverktyg och kodkorrigeringar som hjälper dig att implementera marshallers som överensstämmer med de former som krävs.
MarshalMode
Den MarshalMode som anges i en CustomMarshallerAttribute bestämmer det förväntade stödet för marshalling samt formen på marshallerimplementeringen. Alla lägen stöder tillståndslösa marshallerimplementeringar. Element marshalling-lägen stöder inte tillståndskänsliga marshallerimplementeringar.
MarshalMode |
Förväntat stöd | Kan vara tillståndsberoende |
|---|---|---|
| ManagedToUnmanagedIn | Från hanterat till ohanterat | Ja |
| ManagedToUnmanagedRef | Från hanterad till ohanterad och från ohanterad till hanterad | Ja |
| ManagedToUnmanagedOut | Ohanterad till hanterad | Ja |
| UnmanagedToManagedIn | Ohanterad till hanterad | Ja |
| UnmanagedToManagedRef | Från hanterad till ohanterad och från ohanterad till hanterad | Ja |
| UnmanagedToManagedOut | Från hanterat till ohanterat | Ja |
| ElementIn | Från hanterat till ohanterat | Nej |
| ElementRef | Från hanterad till ohanterad och från ohanterad till hanterad | Nej |
| ElementOut | Ohanterad till hanterad | Nej |
Använd MarshalMode.Default för att ange att marshaller-implementeringen gäller för alla lägen som stöds, baserat på de metoder som implementeras. Om du anger en marshaller för en mer specifik MarshalModehar den marshallern företräde framför en som har markerats som Default.
Grundläggande användning
Hantera ett enda värde
Om du vill skapa en anpassad marshaller för en typ måste du definiera en marshallertyp för startpunkt som implementerar de nödvändiga marshallmetoderna. Entry-point marshaller-typen kan vara en static klass eller en struct, och den måste markeras med CustomMarshallerAttribute.
Tänk dig till exempel en enkel typ som du vill konvertera mellan hanterad och ohanterad kod:
public struct Example
{
public string Message;
public int Flags;
}
Definiera marshallertypen
Du kan skapa en typ med namnet ExampleMarshaller som är markerad med CustomMarshallerAttribute för att indikera att det är den startpunkts marshallertyp som tillhandahåller anpassad marshallinginformation för Example typen. Det första argumentet i CustomMarshallerAttribute är den hanterade typen som marshallern är riktad mot. Det andra argumentet är MarshalMode, vilket marshaller stöder. Det tredje argumentet är själva marshallertypen, dvs. den typ som implementerar metoderna i den förväntade formen.
[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;
}
}
Det ExampleMarshaller som visas här implementerar tillståndslös marshalling från den hanterade Example typen till en blittable-representation i det format som den interna koden förväntar sig (ExampleUnmanaged) och tillbaka. Metoden Free används för att frigöra ohanterade resurser som allokerats under marshallingprocessen. Marshallinglogik styrs helt av marshaller-implementeringen. Att markera fält på en struct med MarshalAsAttribute har ingen effekt på den genererade koden.
ExampleMarshaller Här är både startpunktstypen och implementeringstypen. Om det behövs kan du dock anpassa marshallingen för olika lägen genom att skapa separata marshallertyper för varje läge. Lägg till en ny CustomMarshallerAttribute för varje läge som i följande klass. Vanligtvis är detta bara nödvändigt för tillståndsbevarande marshallers, där marshallertypen är en struct som bibehåller tillstånd över flera anrop. Enligt konvention kapslas implementeringstyperna i entry-point marshallertypen.
[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();
}
}
Deklarera vilken marshaller som ska användas
När du har skapat marshallertypen kan du använda signaturen MarshalUsingAttribute för interop-metoden för att ange att du vill använda den här marshallern för en specifik parameter eller ett returvärde.
MarshalUsingAttribute tar entry-point-marshaller-typen som ett argument, i det här fallet ExampleMarshaller.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ExampleMarshaller))]
internal static partial Example ConvertExample(
[MarshalUsing(typeof(ExampleMarshaller))] Example example);
Om du vill undvika att behöva ange marshallertypen för varje användning av Example-typen kan du även tillämpa NativeMarshallingAttribute på själva Example-typen. Detta anger att den angivna marshallern ska användas som standard för alla användningar av Example typen i interop-källgenerering.
[NativeMarshalling(typeof(ExampleMarshaller))]
public struct Example
{
public string Message;
public int Flags;
}
Typen Example kan sedan användas i källgenererade P/Invoke-metoder utan att ange marshallertypen. I följande P/Invoke-exempel ExampleMarshaller används för att konvertera parametern från hanterad till ohanterad. Det används också för att konvertera returvärdet från ohanterat till hanterat.
[LibraryImport("nativelib")]
internal static partial Example ConvertExample(Example example);
Om du vill använda en annan marshaller för en specifik parameter eller ett returvärde av typen Example anger du MarshalUsingAttribute på användningsplatsen. I följande P/Invoke-exempel ExampleMarshaller används för att konvertera parametern från hanterad till ohanterad.
OtherExampleMarshaller används för att konvertera returvärdet från ohanterat till hanterat.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(OtherExampleMarshaller))]
internal static partial Example ConvertExample(Example example);
Marshalling-samlingar
Icke-generiska samlingar
För samlingar som inte är generiska med avseende på elementtypen bör du skapa en enkel marshallertyp som visats tidigare.
Allmänna samlingar
Om du vill skapa en anpassad marshaller för en allmän samlingstyp kan du använda attributet ContiguousCollectionMarshallerAttribute . Det här attributet anger att marshaller är för sammanhängande samlingar, till exempel matriser eller listor, och det innehåller en uppsättning metoder som marshaller måste implementera för att stödja marshalling av samlingens element. Elementtypen för samlingen som manipuleras måste också ha en marshaller definierad för sig med hjälp av de metoder som beskrevs tidigare.
Applicera ContiguousCollectionMarshallerAttribute på en marshaller-startpunktstyp för att indikera att den är för sammanhängande samlingar. Marshaller-startpunktstypen måste ha en fler typparameter än den associerade hanterade typen. Den sista typparametern är en platshållare och fylls i av källgeneratorn med den ohanterade typen för samlingens elementtyp.
Du kan till exempel ange anpassad marshalling för en List<T>. I följande kod ListMarshaller är både startpunkten och implementeringen. Överensstämmer med en av de marshaller-strukturer som förväntas för anpassad marshalling av en samling. (Observera att det är ett ofullständigt exempel.)
[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);
}
}
I exemplet finns en tillståndslös samlingsmarshaller som implementerar stöd för marshalling från hanterad till ohanterad och från ohanterad till hanterad för en ListMarshaller. I följande P/Invoke-exempel ListMarshaller används för att konvertera samlingscontainern för parametern från hanterad till ohanterad och för att konvertera samlingscontainern för returvärdet från ohanterad till hanterad. Källgeneratorn genererar kod för att kopiera elementen från parametern list till containern som tillhandahålls av marshallern. Eftersom int är blittable, behöver elementen själva inte hanteras.
CountElementName anger att parametern numValues ska användas som elementantal när du samlar returvärdet från ohanterat till hanterat.
[LibraryImport("nativelib")]
[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = nameof(numValues))]
internal static partial List<int> ConvertList(
[MarshalUsing(typeof(ListMarshaller<,>))] List<int> list,
out int numValues);
När elementtypen i samlingen är en anpassad typ kan du ange element-marshaller för det genom att använda ytterligare MarshalUsingAttribute med ElementIndirectionDepth = 1.
ListMarshaller hanterar samlingsbehållaren och ExampleMarshaller konverterar varje element från ohanterat läge till hanterat läge och vice versa.
ElementIndirectionDepth Anger att marshaller ska tillämpas på elementen i samlingen, som är en nivå djupare än själva samlingen.
[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);