From a3edc7e69684c77fb427d0bdc36122ce0f4f8655 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:01:24 -0700 Subject: [PATCH 1/4] Add marshaller shape docs to docs with better explanations --- .../custom-marshalling-source-generation.md | 111 ++++- .../native-interop/value-marshaller-shapes.md | 384 ++++++++++++++++++ 2 files changed, 477 insertions(+), 18 deletions(-) create mode 100644 docs/standard/native-interop/value-marshaller-shapes.md diff --git a/docs/standard/native-interop/custom-marshalling-source-generation.md b/docs/standard/native-interop/custom-marshalling-source-generation.md index 04fdd4bfd408c..1455ee9208120 100644 --- a/docs/standard/native-interop/custom-marshalling-source-generation.md +++ b/docs/standard/native-interop/custom-marshalling-source-generation.md @@ -12,7 +12,11 @@ ms.date: 08/09/2022 ## Marshaller implementation -Marshaller implementations can either be stateless or stateful. If the marshaller type is a `static` class, it's considered stateless. If it's a value type, it's considered stateful and one instance of that marshaller will be used to marshal a specific parameter or return value. Different [shapes for the marshaller implementation][value_shapes] are expected based on whether a marshaller is stateless or stateful and whether it supports marshalling from managed to unmanaged, unmanaged to managed, or both. The .NET SDK includes analyzers and code fixers to help with implementing marshallers that conform to the require shapes. +Custom marshaller implementations can either be stateless or stateful. If the marshaller type is a `static` class it's considered stateless, and the implementation methods should do no tracking of state across calls. If it's a value type, it's considered stateful and one instance of that marshaller will be used to marshal a specific parameter or return value, allowing for state to be preserved across the marshalling and unmarshalling process. + +# Marshaller shapes + +The set of methods that the marshalling generator expects from a custom marshaller type is referred to as the marshaller shape. Since the marshalling generator supports stateless, static custom marshaller types in .NET Standard 2.0 (which doesn't support static interface methods), there are not interface types provided that define the marshaller shapes. Instead, the shapes are documented below. The marshaller shape expected depends on the whether the marshaller is stateless or stateful, and whether it supports marshalling from managed to unmanaged, unmanaged to managed, or both (declared with `CustomMarshallerAttribute.MarshalMode`). The .NET SDK includes analyzers and code fixers to help with implementing marshallers that conform to the required shapes. ### `MarshalMode` @@ -30,14 +34,17 @@ The specified in a | | Managed to unmanaged and unmanaged to managed | No | | | Unmanaged to managed | No | - indicates that the marshaller implementation should be used for any mode that it supports. If a marshaller implementation for a more specific `MarshalMode` is also specified, it takes precedence over `MarshalMode.Default`. + indicates that the marshaller implementation should be used for any mode that it supports (assumed by the methods it implements). If a marshaller implementation for a more specific `MarshalMode` is also specified, it takes precedence over `MarshalMode.Default`. ## Basic usage -We can specify on a type, pointing at an entry-point marshaller type that is either a `static` class or a `struct`. +### Marshalling a single value + +To create a custom marshaller for a type, you need to define an entry-point marshaller type that implements the required marshalling methods. The entry-point marshaller type can be a `static` class or a `struct`, and it must be marked with . + +For example, consider a simple type that we want to marshal between managed and unmanaged code: ```csharp -[NativeMarshalling(typeof(ExampleMarshaller))] public struct Example { public string Message; @@ -45,7 +52,9 @@ public struct Example } ``` -`ExampleMarshaller`, the entry-point marshaller type, is marked with , pointing at a [marshaller implementation](#marshaller-implementation) type. In this example, `ExampleMarshaller` is both the entry point and the implementation. It conforms to [marshaller shapes][value_shapes] expected for custom marshalling of a value. +#### Define the marshaller type + +We can create a type called `ExampleMarshaller` that is marked with to indicate that it is the entry-point marshaller type which provides custom marshalling information for the `Example` type. The first argument of the `CustomMarshallerAttribute` is the managed type that the marshaller targets. The second argument is the `MarshalMode` that the marshaller supports. The third argument is the marshaller type itself, that is, the type that implements the methods in the shape expected. ```csharp [CustomMarshaller(typeof(Example), MarshalMode.Default, typeof(ExampleMarshaller))] @@ -68,16 +77,65 @@ internal static class ExampleMarshaller } ``` -The `ExampleMarshaller` in the example is a stateless marshaller that implements support for marshalling from managed to unmanaged and from unmanaged to managed. The marshalling logic is entirely controlled by your marshaller implementation. Marking fields on a struct with has no effect on the generated code. +The `ExampleMarshaller` above implements stateless marshalling from the managed `Example` type to a blittable representation in the format that the native code expects (`ExampleUnmanaged`) and back. The `Free` method is used to release any unmanaged resources allocated during the marshalling process. The marshalling logic is entirely controlled by the marshaller implementation. Marking fields on a struct with has no effect on the generated code. + +Here, `ExampleMarshaller` is both the entry-point type and the implementation type. However, if necessary you can customize the marshalling for different modes by creating separate marshaller types for each mode, adding a new CustomMarshallerAttribute for each mode like in the class below. Typically this is only necessary for stateful marshallers, where the marshaller type is a `struct` that maintains state across calls. By convention, the implementation types are nested inside the entry-point marshaller type. + +```csharp +[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(); + } +} +``` + +#### Declare which marshaller to use -The `Example` type can then be used in P/Invoke source generation. In the following P/Invoke example, `ExampleMarshaller` will be used to marshal the parameter from managed to unmanaged. It will also be used to marshal the return value from unmanaged to managed. +Once we have created the marshaller type, we can use the on the interop method signature to indicate that we want to use this marshaller for a specific parameter or return value. The takes the entry-point marshaller type as an argument, in this case `ExampleMarshaller`. + +```csharp +[LibraryImport("nativelib")] +[return: MarshalUsing(typeof(ExampleMarshaller))] +internal static partial Example ConvertExample( + [MarshalUsing(typeof(ExampleMarshaller))] Example example); +``` + +To avoid having to specify the marshaller type for every usage of the `Example` type, we can also apply the to the `Example` type itself. This indicates that the specified marshaller should be used by default for all usages of the `Example` type in interop source generation. + +```csharp +[NativeMarshalling(typeof(ExampleMarshaller))] +public struct Example +{ + public string Message; + public int Flags; +} +``` + +The `Example` type can then be used in source-generated P/Invoke methods without specifying the marshaller type. In the following P/Invoke example, `ExampleMarshaller` will be used to marshal the parameter from managed to unmanaged. It will also be used to marshal the return value from unmanaged to managed. ```csharp [LibraryImport("nativelib")] internal static partial Example ConvertExample(Example example); ``` -To use a different marshaller for a specific usage of the `Example` type, specify at the use site. In the following P/Invoke example, `ExampleMarshaller` will be used to marshal the parameter from managed to unmanaged. `OtherExampleMarshaller` will be used to marshal the return value from unmanaged to managed. +To use a different marshaller for a specific parameter or return value of the `Example` type, specify at the use site. In the following P/Invoke example, `ExampleMarshaller` will be used to marshal the parameter from managed to unmanaged. `OtherExampleMarshaller` will be used to marshal the return value from unmanaged to managed. ```csharp [LibraryImport("nativelib")] @@ -85,9 +143,17 @@ To use a different marshaller for a specific usage of the `Example` type, specif internal static partial Example ConvertExample(Example example); ``` -### Collections +### Marshalling Collections + +#### Non-generic collections -Apply the to a marshaller entry-point type to indicate that it's for contiguous collections. The type must have one more type parameter than the associated managed type. The last type parameter is a placeholder and will be filled in by the source generator with the unmanaged type for the collection's element type. +For collections that aren't generic over the type of the element, you should create a simple marshaller type like above. + +### Generic collections + +To create a custom marshaller for a generic collection type, you can use the attribute. This attribute indicates that the marshaller is for contiguous collections, such as arrays or lists, and it provides a set of methods that the marshaller must implement to support marshalling of the collection's elements. The element type of the collection marshalled must also have a marshaller defined for it using the methods described above. + +Apply the to a marshaller entry-point type to indicate that it's for contiguous collections. The marshaller entry-point type must have one more type parameter than the associated managed type. The last type parameter is a placeholder and will be filled in by the source generator with the unmanaged type for the collection's element type. For example, you can specify custom marshalling for a . In the following code, `ListMarshaller` is both the entry point and the implementation. It conforms to [marshaller shapes][collection_shapes] expected for custom marshalling of a collection. @@ -119,24 +185,33 @@ public unsafe static class ListMarshaller where TUnmanaged } ``` -The `ListMarshaller` in the example is a stateless collection marshaller that implements support for marshalling from managed to unmanaged and from unmanaged to managed for a . In the following P/Invoke example, `ListMarshaller` will be used to marshal the parameter from managed to unmanaged and to marshal the return value from unmanaged to managed. indicates that the `numValues` parameter should be used as the element count when marshalling the return value from unmanaged to managed. +The `ListMarshaller` in the example is a stateless collection marshaller that implements support for marshalling from managed to unmanaged and from unmanaged to managed for a . In the following P/Invoke example, `ListMarshaller` will be used to marshal the collection container for the parameter from managed to unmanaged and to marshal the collection container for the return value from unmanaged to managed. The source generator will generate code to copy the elements from the parameter `list` to the container provided by the marshaller. Since `int` is blittable, the elements themselves do not need to be marshalled. indicates that the `numValues` parameter should be used as the element count when marshalling the return value from unmanaged to managed. ```csharp [LibraryImport("nativelib")] [return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = "numValues")] -internal static partial void ConvertList( +internal static partial List ConvertList( [MarshalUsing(typeof(ListMarshaller<,>))] List list, out int numValues); ``` +When the element type of the collection is a custom type, you can specify the element marshaller for that using an additional with `ElementIndirectionDepth = 1`. +The `ListMarshaller` will handle the collection container and `ExampleMarshaller` will marshal each element from unmanaged to managed and vice versa. The `ElementIndirectionDepth` indicates that the marshaller should be applied to the elements of the collection, which are one level deeper than the collection itself. + +```csharp +[LibraryImport("nativelib")] +[MarshalUsing(typeof(ListMarshaller<,>))] +[MarshalUsing(typeof(ListMarshaller<,>), ElementIndirectionDepth = 1)] +internal static partial void ConvertList( + [MarshalUsing(typeof(ListMarshaller<,>))] + [MarshalUsing(typeof(ListMarshaller<,>), ElementIndirectionDepth = 1)] + List list, + out int numValues); +``` + ## See also - [System.Runtime.InteropServices.Marshalling APIs](xref:System.Runtime.InteropServices.Marshalling) - [P/Invoke source generation](pinvoke-source-generation.md) - [Disabling runtime marshalling](disabled-marshalling.md) -- Marshaller shapes - - [Values][value_shapes] - - [Collections][collection_shapes] - -[value_shapes]:https://github.com/dotnet/runtime/blob/main/docs/design/libraries/LibraryImportGenerator/UserTypeMarshallingV2.md#value-marshaller-shapes -[collection_shapes]:https://github.com/dotnet/runtime/blob/main/docs/design/libraries/LibraryImportGenerator/UserTypeMarshallingV2.md#linear-array-like-collection-marshaller-shapes +- [Marshaller shapes](marshaller-shapes.md) diff --git a/docs/standard/native-interop/value-marshaller-shapes.md b/docs/standard/native-interop/value-marshaller-shapes.md new file mode 100644 index 0000000000000..007eb751386e9 --- /dev/null +++ b/docs/standard/native-interop/value-marshaller-shapes.md @@ -0,0 +1,384 @@ +--- +description: "Learn more about: Value Marshallers in Interop Source Generation" +title: "Custom Marshaller Shapes - Value Marshallers" +ms.date: "07/07/2025" +helpviewer_keywords: + - "unmanaged code, exceptions" + - "exceptions, unmanaged code" + - "interop, exceptions" + - "exceptions, interop" + - "interop source generation" + - "custom marshallers" + - "exceptions, custom marshallers" +--- + +## Value Marshallers + +This document describes the shapes of custom marshallers that can be used by the .NET interop source generator for marshalling types between managed and unmanaged code. + +### Stateless Managed to Unmanaged + +With this shape, the generated code will call `ConvertToUnmanaged` to marshal a value to native code, or `GetPinnableReference` when applicable. The generated code will call `Free` when applicable to allow the marshaller to free any unmanaged resources associated with the managed type once its lifetime ends. + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] +static class TMarshaller +{ + public static class ManagedToNative + { + /// + /// Converts a managed type to an unmanaged representation. May throw exceptions. + /// + public static TNative ConvertToUnmanaged(TManaged managed); + + /// + /// Optional. + /// Returns a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// + public static ref TNativeDereferenced GetPinnableReference(TManaged managed); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the marshalling of the managed type. + /// Must not throw exceptions. + /// + public static void Free(TNative unmanaged); + } +} +``` + +### Stateless Managed->Unmanaged with Caller-Allocated Buffer + +With this shape, the generator will allocate a buffer of the specified size and pass it to the `ConvertToUnmanaged` method to marshal a value to native code. The generated code will handle the lifetime of this buffer. + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] +static class TMarshaller +{ + public static class ManagedToNative + { + /// + /// The size of the buffer that will be allocated by the generator. + /// + public static int BufferSize { get; } + + /// + /// Converts a managed type to an unmanaged representation using a caller-allocated buffer. + /// + public static TNative ConvertToUnmanaged(TManaged managed, Span callerAllocatedBuffer); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the marshalling of the managed type. + /// Must not throw exceptions. + /// + public static void Free(TNative unmanaged); + } +} +``` + +### Stateless Unmanaged->Managed + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] +static class TMarshaller +{ + public static class NativeToManaged + { + /// + /// Converts an unmanaged representation to a managed type. May throw exceptions. + /// + public static TManaged ConvertToManaged(TNative unmanaged); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the marshalling of the managed type. + /// Must not throw exceptions. + /// + public static void Free(TNative unmanaged); + } +} +``` + +### Stateless Unmanaged->Managed with Guaranteed Unmarshalling + +This shape directs the generator to emit the unmarshaling code in a way that guarantees the unmarshaling will be called, even if a previous marshaller throws an exception. This is useful for types that need to be cleaned up or finalized regardless of the success of the previous operations. + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] +static class TMarshaller +{ + public static class NativeToManaged + { + /// + /// Converts an unmanaged representation to a managed type. + /// Should not throw exceptions. + /// + public static TManaged ConvertToManagedFinally(TNative unmanaged); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the marshalling of the managed type. + /// Must not throw exceptions. + /// + public static void Free(TNative unmanaged); + } +} +``` + +### Stateless Bidirectional + +This shape allows for both managed to unmanaged and unmanaged to managed conversions, with the marshaller being stateless. The generator will use the `ConvertToUnmanaged` method for managed to unmanaged conversions and the `ConvertToManaged` method for unmanaged to managed conversions. + +```csharp +[CustomMarshaller(typeof(TManaged<,,,...>), MarshalMode.ManagedToUnmanagedRef, typeof(Bidirectional))] +[CustomMarshaller(typeof(TManaged<,,,...>), MarshalMode.UnmanagedToManagedRef, typeof(Bidirectional))] +[CustomMarshaller(typeof(TManaged<,,,...>), MarshalMode.ElementRef, typeof(Bidirectional))] +static class TMarshaller +{ + public static class Bidirectional + { + // Include members from each of the following: + // - One Stateless Managed->Unmanaged Value shape + // - One Stateless Unmanaged->Managed Value shape + } +} +``` + +### Stateful Managed->Unmanaged + +This shape allows for stateful marshalling from managed to unmanaged. The generated code will use a unique marshaller instance for each parameter, so the marshaller can maintain state across marshalling + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] +static class TMarshaller +{ + public struct ManagedToNative // Can be ref struct + { + /// + /// Optional constructor. + /// May throw exceptions. + /// + public ManagedToNative(); + + /// + /// Takes a managed type to be converted to an unmanaged representation in ToUnmanaged or GetPinnableReference. + /// + public void FromManaged(TManaged managed); // Can throw exceptions. + + /// + /// Optional. + /// Returns a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// When applicable, this method is preferred to ToUnmanaged for marshalling. + /// + public ref TIgnored GetPinnableReference(); + + /// + /// Optional. + /// Returns a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// When applicable, only this method is called for the marshalling step. + /// May throw exceptions. + /// + public static ref TOther GetPinnableReference(TManaged managed); + + /// + /// Converts the managed type to an unmanaged representation. + /// May throw exceptions. + /// + public TNative ToUnmanaged(); + + /// + /// Optional. + /// In managed to unmanaged stubs, this method is called after call to the unmanaged code. + /// Must not throw exceptions. + /// + public void OnInvoked(); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the marshalling of the managed type. + /// Must not throw exceptions. + /// + public void Free(); + } +} +``` + +### Stateful Managed->Unmanaged with Caller Allocated Buffer + +This shape allows for stateful marshalling from managed to unmanaged, with the generator allocating a buffer of the specified size and passing it to the `FromManaged` method. The generated code will use a unique marshaller instance for each parameter, so the marshaller can maintain state across marshalling. + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] +static class TMarshaller +{ + public struct ManagedToNative // Can be ref struct + { + /// + /// The size of the buffer, in bytes, that will be allocated by the generator. + /// + public static int BufferSize { get; } + + /// + /// Optional constructor. + /// May throw exceptions. + /// + public ManagedToNative(); + + /// + /// Takes a managed type to be converted to an unmanaged representation in ToUnmanaged or GetPinnableReference. + /// May throw exceptions. + /// + public void FromManaged(TManaged managed, Span buffer); + + /// + /// Optional. + /// Returns a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// When applicable, this method is preferred to ToUnmanaged for marshalling. + /// + public ref TIgnored GetPinnableReference(); + + /// + /// Optional. + /// Returns a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// When applicable, only this method is called for the marshalling step. + /// May throw exceptions. + public static ref TOther GetPinnableReference(TManaged managed); + + /// + /// Returns the unmanaged representation of the managed value. + /// May throw exceptions. + /// + public TNative ToUnmanaged(); + + /// + /// Optional. + /// In managed to unmanaged stubs, this method is called after call to the unmanaged code. + /// Must not throw exceptions. + /// + public void OnInvoked(); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the marshalling of the managed type. + /// Must not throw exceptions. + /// + public void Free(); + } +} +``` + +### Stateful Unmanaged->Managed + +This shape allows for stateful unmarshalling from unmanaged to managed. The generated code will use a unique instance for each parameter, so the struct can maintain state across unmarshalling. + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] +static class TMarshaller +{ + public struct NativeToManaged // Can be ref struct + { + /// + /// Optional constructor. + /// May throw exceptions. + /// + public NativeToManaged(); + + /// + /// Takes an unmanaged representation to be converted to a managed type in ToManaged. + /// Should not throw exceptions. + /// + public void FromUnmanaged(TNative unmanaged); + + /// + /// Returns the managed value representation of the unmanaged value. + /// May throw exceptions. + /// + public TManaged ToManaged(); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the unmarshalling of the managed type. + /// Must not throw exceptions. + /// + public void Free(); + } +} +``` + +### Stateful Unmanaged->Managed with Guaranteed Unmarshalling + +This shape allows for stateful unmarshalling from unmanaged to managed, with the generator ensuring that the `ToManagedFinally` method is called even if a previous marshaller throws an exception. This is useful for types that need to be cleaned up or finalized regardless of the success of the previous operations. + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] +static class TMarshaller +{ + public struct NativeToManaged // Can be ref struct + { + /// + /// Optional constructor. + /// May throw exceptions. + /// + public NativeToManaged(); + + /// + /// Takes an unmanaged representation to be converted to a managed type in ToManagedFinally. + /// Should not throw exceptions. + /// + public void FromUnmanaged(TNative unmanaged); + + /// + /// Returns the managed value representation of the unmanaged value. + /// Should not throw exceptions. + /// + public TManaged ToManagedFinally(); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the unmarshalling of the managed type. + /// Must not throw exceptions. + /// + public void Free(); + } +} +``` + +### Stateful Bidirectional +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedRef, typeof(Bidirectional))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedRef, typeof(Bidirectional))] +static class TMarshaller +{ + public struct Bidirectional // Can be ref struct + { + // Include members from each of the following: + // - One Stateful Managed->Unmanaged Value shape + // - One Stateful Unmanaged->Managed Value shape + } +} +``` + From e6a27f5b196de590bb1e40e2f28ed9bbd927aaa1 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 8 Jul 2025 16:32:22 -0700 Subject: [PATCH 2/4] add collection marshalling shapes --- .../custom-marshaller-shapes.md | 854 ++++++++++++++++++ .../custom-marshalling-source-generation.md | 53 +- .../customize-parameter-marshalling.md | 2 +- .../native-interop/value-marshaller-shapes.md | 384 -------- 4 files changed, 883 insertions(+), 410 deletions(-) create mode 100644 docs/standard/native-interop/custom-marshaller-shapes.md delete mode 100644 docs/standard/native-interop/value-marshaller-shapes.md diff --git a/docs/standard/native-interop/custom-marshaller-shapes.md b/docs/standard/native-interop/custom-marshaller-shapes.md new file mode 100644 index 0000000000000..7e51482666332 --- /dev/null +++ b/docs/standard/native-interop/custom-marshaller-shapes.md @@ -0,0 +1,854 @@ +--- +description: "Learn more about: Value Marshallers in Interop Source Generation" +title: "Custom Marshaller Shapes - Value Marshallers" +ms.date: "07/07/2025" +helpviewer_keywords: + - "unmanaged code, exceptions" + - "exceptions, unmanaged code" + - "interop, exceptions" + - "exceptions, interop" + - "interop source generation" + - "custom marshallers" + - "exceptions, custom marshallers" +--- + +# Custom Marshaller Shapes + +This document describes the different "shapes" of custom marshallers that can be used with the .NET interop source generator. + +## Value Marshallers + +This document describes the shapes of custom marshallers that can be used by the .NET interop source generator for marshalling types between managed and unmanaged code. + +### Stateless Managed to Unmanaged + +With this shape, the generated code will call `ConvertToUnmanaged` to marshal a value to native code, or `GetPinnableReference` when applicable. The generated code will call `Free` when applicable to allow the marshaller to free any unmanaged resources associated with the managed type once its lifetime ends. + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] +static class TMarshaller +{ + public static class ManagedToNative + { + /// + /// Converts a managed type to an unmanaged representation. May throw exceptions. + /// + public static TNative ConvertToUnmanaged(TManaged managed); + + /// + /// Optional. + /// Returns a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// + public static ref TNativeDereferenced GetPinnableReference(TManaged managed); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the marshalling of the managed type. + /// Must not throw exceptions. + /// + public static void Free(TNative unmanaged); + } +} +``` + +### Stateless Managed->Unmanaged with Caller-Allocated Buffer + +With this shape, the generator will allocate a buffer of the specified size and pass it to the `ConvertToUnmanaged` method to marshal a value to native code. The generated code will handle the lifetime of this buffer. + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] +static class TMarshaller +{ + public static class ManagedToNative + { + /// + /// The size of the buffer that will be allocated by the generator. + /// + public static int BufferSize { get; } + + /// + /// Converts a managed type to an unmanaged representation using a caller-allocated buffer. + /// + public static TNative ConvertToUnmanaged(TManaged managed, Span callerAllocatedBuffer); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the marshalling of the managed type. + /// Must not throw exceptions. + /// + public static void Free(TNative unmanaged); + } +} +``` + +### Stateless Unmanaged->Managed + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] +static class TMarshaller +{ + public static class NativeToManaged + { + /// + /// Converts an unmanaged representation to a managed type. May throw exceptions. + /// + public static TManaged ConvertToManaged(TNative unmanaged); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the marshalling of the managed type. + /// Must not throw exceptions. + /// + public static void Free(TNative unmanaged); + } +} +``` + +### Stateless Unmanaged->Managed with Guaranteed Unmarshalling + +This shape directs the generator to emit the unmarshaling code in a way that guarantees the unmarshaling will be called, even if a previous marshaller throws an exception. This is useful for types that need to be cleaned up or finalized regardless of the success of the previous operations. + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] +static class TMarshaller +{ + public static class NativeToManaged + { + /// + /// Converts an unmanaged representation to a managed type. + /// Should not throw exceptions. + /// + public static TManaged ConvertToManagedFinally(TNative unmanaged); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the marshalling of the managed type. + /// Must not throw exceptions. + /// + public static void Free(TNative unmanaged); + } +} +``` + +### Stateless Bidirectional + +This shape allows for both managed to unmanaged and unmanaged to managed conversions, with the marshaller being stateless. The generator will use the `ConvertToUnmanaged` method for managed to unmanaged conversions and the `ConvertToManaged` method for unmanaged to managed conversions. + +```csharp +[CustomMarshaller(typeof(TManaged<,,,...>), MarshalMode.ManagedToUnmanagedRef, typeof(Bidirectional))] +[CustomMarshaller(typeof(TManaged<,,,...>), MarshalMode.UnmanagedToManagedRef, typeof(Bidirectional))] +[CustomMarshaller(typeof(TManaged<,,,...>), MarshalMode.ElementRef, typeof(Bidirectional))] +static class TMarshaller +{ + public static class Bidirectional + { + // Include members from each of the following: + // - One Stateless Managed->Unmanaged Value shape + // - One Stateless Unmanaged->Managed Value shape + } +} +``` + +### Stateful Managed->Unmanaged + +This shape allows for stateful marshalling from managed to unmanaged. The generated code will use a unique marshaller instance for each parameter, so the marshaller can maintain state across marshalling + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] +static class TMarshaller +{ + public struct ManagedToNative // Can be ref struct + { + /// + /// Optional constructor. + /// May throw exceptions. + /// + public ManagedToNative(); + + /// + /// Takes a managed type to be converted to an unmanaged representation in ToUnmanaged or GetPinnableReference. + /// + public void FromManaged(TManaged managed); + + /// + /// Optional. + /// Returns a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// When applicable, this method is preferred to ToUnmanaged for marshalling. + /// + public ref TIgnored GetPinnableReference(); + + /// + /// Optional. + /// Returns a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// When applicable, only this method is called for the marshalling step. + /// May throw exceptions. + /// + public static ref TOther GetPinnableReference(TManaged managed); + + /// + /// Converts the managed type to an unmanaged representation. + /// May throw exceptions. + /// + public TNative ToUnmanaged(); + + /// + /// Optional. + /// In managed to unmanaged stubs, this method is called after call to the unmanaged code. + /// Must not throw exceptions. + /// + public void OnInvoked(); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the marshalling of the managed type. + /// Must not throw exceptions. + /// + public void Free(); + } +} +``` + +### Stateful Managed->Unmanaged with Caller Allocated Buffer + +This shape allows for stateful marshalling from managed to unmanaged, with the generator allocating a buffer of the specified size and passing it to the `FromManaged` method. The generated code will use a unique marshaller instance for each parameter, so the marshaller can maintain state across marshalling. + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] +static class TMarshaller +{ + public struct ManagedToNative // Can be ref struct + { + /// + /// The length of the buffer that will be allocated by the generator. + /// + public static int BufferSize { get; } + + /// + /// Optional constructor. + /// May throw exceptions. + /// + public ManagedToNative(); + + /// + /// Takes a managed type to be converted to an unmanaged representation in ToUnmanaged or GetPinnableReference. + /// May throw exceptions. + /// + public void FromManaged(TManaged managed, Span buffer); + + /// + /// Optional. + /// Returns a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// When applicable, this method is preferred to ToUnmanaged for marshalling. + /// + public ref TIgnored GetPinnableReference(); + + /// + /// Optional. + /// Returns a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// When applicable, only this method is called for the marshalling step. + /// May throw exceptions. + public static ref TOther GetPinnableReference(TManaged managed); + + /// + /// Returns the unmanaged representation of the managed value. + /// May throw exceptions. + /// + public TNative ToUnmanaged(); + + /// + /// Optional. + /// In managed to unmanaged stubs, this method is called after call to the unmanaged code. + /// Must not throw exceptions. + /// + public void OnInvoked(); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the marshalling of the managed type. + /// Must not throw exceptions. + /// + public void Free(); + } +} +``` + +### Stateful Unmanaged->Managed + +This shape allows for stateful unmarshalling from unmanaged to managed. The generated code will use a unique instance for each parameter, so the struct can maintain state across unmarshalling. + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] +static class TMarshaller +{ + public struct NativeToManaged // Can be ref struct + { + /// + /// Optional constructor. + /// May throw exceptions. + /// + public NativeToManaged(); + + /// + /// Takes an unmanaged representation to be converted to a managed type in ToManaged. + /// Should not throw exceptions. + /// + public void FromUnmanaged(TNative unmanaged); + + /// + /// Returns the managed value representation of the unmanaged value. + /// May throw exceptions. + /// + public TManaged ToManaged(); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the unmarshalling of the managed type. + /// Must not throw exceptions. + /// + public void Free(); + } +} +``` + +### Stateful Unmanaged->Managed with Guaranteed Unmarshalling + +This shape allows for stateful unmarshalling from unmanaged to managed, with the generator ensuring that the `ToManagedFinally` method is called even if a previous marshaller throws an exception. This is useful for types that need to be cleaned up or finalized regardless of the success of the previous operations. + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] +static class TMarshaller +{ + public struct NativeToManaged // Can be ref struct + { + /// + /// Optional constructor. + /// May throw exceptions. + /// + public NativeToManaged(); + + /// + /// Takes an unmanaged representation to be converted to a managed type in ToManagedFinally. + /// Should not throw exceptions. + /// + public void FromUnmanaged(TNative unmanaged); + + /// + /// Returns the managed value representation of the unmanaged value. + /// Should not throw exceptions. + /// + public TManaged ToManagedFinally(); + + /// + /// Optional. + /// Frees any unmanaged resources associated with the unmarshalling of the managed type. + /// Must not throw exceptions. + /// + public void Free(); + } +} +``` + +### Stateful Bidirectional + +```csharp +[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedRef, typeof(Bidirectional))] +[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedRef, typeof(Bidirectional))] +static class TMarshaller +{ + public struct Bidirectional // Can be ref struct + { + // Include members from each of the following: + // - One Stateful Managed->Unmanaged Value shape + // - One Stateful Unmanaged->Managed Value shape + } +} +``` + +## Collection marshallers + +This section describes the shapes of custom collection marshallers that the .NET interop source generator supports for marshalling collections between managed and unmanaged code. Contiguous collection marshallers are used for collections which are represented as a contiguous block of memory in their unmanaged representation, such as arrays or lists. + +When the collection type being marshalled is a generic over it's element type (for example, `List`), the custom marshaller type will typically have two generic parameters, one for the managed element type and one for the unmanaged element type. In cases where the collection is not generic over it's element type (for example, `StringCollection`), the marshaller may only have a single generic parameter for the unmanaged element type. The marshaller shape examples will demonstrate a marshaller for a non-generic collection `TCollection` with elements of type `TManagedElement`. Since the elements may be marshalled using different value marshallers, it must be generic over the unmanaged element type, `TUnmanagedElement`. The native representation of the collection is called `TNative` (typically this is `*TUnmanagedElement`). + +### Stateless managed to unmanaged + +With this shape, the generator uses static methods to marshal a managed collection to unmanaged memory. The marshaller must provide methods to allocate the unmanaged container, access managed and unmanaged elements, and optionally free unmanaged resources. + +```csharp +[CustomMarshaller(typeof(TCollection), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] +[CustomMarshaller(typeof(TCollection), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] +[CustomMarshaller(typeof(TCollection), MarshalMode.ElementIn, typeof(ManagedToNative))] +[ContiguousCollectionMarshaller] +static class TMarshaller where TUnmanagedElement : unmanaged +{ + public static class ManagedToNative + { + /// + /// Gets the uninitialized unmanaged container for the elements and assigns the number of elements to numElements. + /// The return value is later passed to GetUnmanagedValuesDestination. + /// + public static TNative AllocateContainerForUnmanagedElements(TCollection managed, out int numElements); + + /// + /// Gets a span of managed elements in the collection. + /// The elements in this span are marshalled by the element marshaller and put into the unmanaged container. + /// + public static ReadOnlySpan GetManagedValuesSource(TCollection managed); + + /// + /// Gets a span of unmanaged elements from the TNative container. + /// The elements in this span are filled with the marshalled values from the managed collection. + /// + public static Span GetUnmanagedValuesDestination(TNative unmanaged, int numElements); + + /// + /// Optional. Returns a pinnable reference to the unmanaged representations of the elements. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// + public static ref TOther GetPinnableReference(TManaged managed); + + /// + /// Optional. Frees unmanaged resources. + /// Should not throw exceptions. + /// + public static void Free(TNative unmanaged); + } +} +``` + +### Stateless managed to unmanaged with caller-allocated buffer + +The generator allocates a buffer and passes it to the marshaller for use during marshalling. + +```csharp +[CustomMarshaller(typeof(TCollection), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] +[CustomMarshaller(typeof(TCollection), MarshalMode.ElementIn, typeof(ManagedToNative))] +[ContiguousCollectionMarshaller] +static class TMarshaller where TUnmanagedElement : unmanaged +{ + public static class ManagedToNative + { + /// + /// The length of the buffer that will be allocated by the generated code and passed to AllocateContainerForUnmanagedElements. + /// + public static int BufferSize { get; } + + /// + /// Creates the unmanaged container using a caller-allocated buffer, and assigns the number of elements in the collection to numElements. + /// The return value is later passed to GetUnmanagedValuesDestination. + /// + public static TNative AllocateContainerForUnmanagedElements(TCollection managed, Span buffer, out int numElements); + + /// + /// Gets a span of managed elements in the collection. + /// The elements in this span are marshalled by the element marshaller and put into the unmanaged container. + /// + public static ReadOnlySpan GetManagedValuesSource(TCollection managed); + + /// + /// Gets a span of unmanaged elements from the TNative container. + /// The elements in this span are filled with the marshalled values from the managed collection. + /// + public static Span GetUnmanagedValuesDestination(TNative unmanaged, int numElements); + + /// + /// Optional. Returns a pinnable reference to the unmanaged representations of the elements. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// + public static ref TOther GetPinnableReference(TManaged managed); + + /// + /// Optional. Frees unmanaged resources. + /// Should not throw exceptions. + /// + public static void Free(TNative unmanaged); + } +} +``` + +### Stateless unmanaged to managed + +This shape marshals from unmanaged memory to a managed collection. + +```csharp +[CustomMarshaller(typeof(TCollection), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] +[CustomMarshaller(typeof(TCollection), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] +[CustomMarshaller(typeof(TCollection), MarshalMode.ElementOut, typeof(NativeToManaged))] +[ContiguousCollectionMarshaller] +static class TMarshaller where TUnmanagedElement : unmanaged +{ + public static class NativeToManaged + { + /// + /// Allocates a new collection for unmarshalling. + /// May throw exceptions. + /// + public static TCollection AllocateContainerForManagedElements(TNative unmanaged, int numElements); + + /// + /// Gets the destination span that unmarshalled managed elements will be written to. + /// May throw exceptions. + /// + public static Span GetManagedValuesDestination(TCollection managed); + + /// + /// Gets the source span of unmanaged elements to unmarshal from. + /// May throw exceptions. + /// + public static ReadOnlySpan GetUnmanagedValuesSource(TNative unmanaged, int numElements); + + /// + /// Optional. Frees unmanaged resources. + /// Should not throw exceptions. + /// + public static void Free(TNative unmanaged); + } +} +``` + +### Stateless unmanaged to managed with guaranteed unmarshalling + +The generator ensures that unmarshalling occurs even if a previous marshaller throws an exception. + +```csharp +[CustomMarshaller(typeof(TCollection), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] +[ContiguousCollectionMarshaller] +static class TMarshaller where TUnmanagedElement : unmanaged +{ + public static class NativeToManaged + { + /// + /// Allocates the managed container for the elements. + /// Should not throw exceptions other than OutOfMemoryException. + /// + public static TCollection AllocateContainerForManagedElementsFinally(TNative unmanaged, int numElements); + + /// + /// Gets the destination span that unmarshalled managed elements will be written to. + /// May throw exceptions. + /// + public static Span GetManagedValuesDestination(TCollection managed); + + /// + /// Gets the source span of unmanaged elements to unmarshal from. + /// May throw exceptions. + /// + public static ReadOnlySpan GetUnmanagedValuesSource(TNative unmanaged, int numElements); + + /// + /// Optional. Frees unmanaged resources. + /// Should not throw exceptions. + /// + public static void Free(TNative unmanaged); + } +} +``` + +### Stateless bidirectional + +This shape supports both managed-to-unmanaged and unmanaged-to-managed conversions. + +```csharp +[CustomMarshaller(typeof(TCollection), MarshalMode.ManagedToUnmanagedRef, typeof(Bidirectional))] +[CustomMarshaller(typeof(TCollection), MarshalMode.UnmanagedToManagedRef, typeof(Bidirectional))] +[CustomMarshaller(typeof(TCollection), MarshalMode.ElementRef, typeof(Bidirectional))] +[ContiguousCollectionMarshaller] +static class TMarshaller where TUnmanagedElement : unmanaged +{ + public static class Bidirectional + { + // Include members from each of the following: + // - One Stateless Managed->Unmanaged Linear Collection shape + // - One Stateless Unmanaged->Managed Linear Collection shape + } +} +``` + +### Stateful managed to unmanaged + +This shape allows the marshaller to maintain state across the marshalling process. + +```csharp +[CustomMarshaller(typeof(TCollection), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] +[CustomMarshaller(typeof(TCollection), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] +[ContiguousCollectionMarshaller] +static class TMarshaller where TUnmanagedElement : unmanaged +{ + public struct ManagedToNative // Can be ref struct + { + /// + /// Optional constructor. + /// May throw exceptions. + /// + public ManagedToNative(); // Optional, can throw exceptions. + + /// + /// Takes a managed collection to be converted to an unmanaged representation in ToUnmanaged or GetPinnableReference. + /// + public void FromManaged(TCollection collection); // Can throw exceptions. + + /// + /// Gets the source span of managed elements to marshal. + /// May throw exceptions. + /// + public ReadOnlySpan GetManagedValuesSource(); // Can throw exceptions. + + /// + /// Gets the destination span of unmanaged elements to marshal to. + /// May throw exceptions. + /// + public Span GetUnmanagedValuesDestination(); // Can throw exceptions. + + /// + /// Optional. + /// Gets a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// When applicable, this method is preferred to ToUnmanaged for marshalling. + /// May throw exceptions. + /// + public ref TOther GetPinnableReference(); // Optional. Can throw exceptions. + + /// + /// Optional. + /// Gets a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// When applicable, this method is preferred to the instance version and ToUnmanaged for marshalling. + /// May throw exceptions. + /// + public static ref TOther GetPinnableReference(TCollection collection); // Optional. Can throw exceptions. Result pinnned and passed to Invoke. + + /// + /// Converts the managed collection to an unmanaged representation. + /// May throw exceptions. + /// + public TNative ToUnmanaged(); // Can throw exceptions. + + /// + /// Optional. + /// In managed to unmanaged stubs, this method is called after call to the unmanaged code. + /// Must not throw exceptions. + /// + public void OnInvoked(); // Optional. Should not throw exceptions. + } +} +``` + +### Stateful Managed->Unmanaged with Caller Allocated Buffer + + +```csharp +[CustomMarshaller(typeof(TCollection), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] +[ContiguousCollectionMarshaller] +static class TMarshaller where TUnmanagedElement : unmanaged +{ + public struct ManagedToNative // Can be ref struct + { + /// + /// The length of the buffer that will be allocated by the generated code and passed to FromManaged. + /// + public static int BufferSize { get; } + + /// + /// Optional constructor. + /// May throw exceptions. + /// + public ManagedToNative(); + + /// + /// Takes a managed collection to be converted to an unmanaged representation in ToUnmanaged or GetPinnableReference. + /// The caller-allocated buffer is passed to this method. + /// + public void FromManaged(TCollection collection, Span buffer); + + /// + /// Gets the source span of managed elements to marshal. + /// May throw exceptions. + /// + public ReadOnlySpan GetManagedValuesSource(); + + /// + /// Gets the destination span of unmanaged elements to marshal to. + /// May throw exceptions. + /// + public Span GetUnmanagedValuesDestination(); + + /// + /// Optional. + /// Gets a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// When applicable, this method is preferred to ToUnmanaged for marshalling. + /// May throw exceptions. + /// + public ref TOther GetPinnableReference(); + + /// + /// Optional. + /// Gets a pinnable reference to the unmanaged representation. + /// The reference will be pinned and passed as a pointer to the native code. + /// TOther should be a dereferenced type of TNative. + /// For example, if TNative is `int*`, then TOther should be `int`. + /// When applicable, this method is preferred to the instance GetPinnableReference and ToUnmanaged for marshalling. + /// May throw exceptions. + /// + public static ref TOther GetPinnableReference(TCollection collection); + + /// + /// Converts the managed collection to an unmanaged representation. + /// May throw exceptions. + /// + public TNative ToUnmanaged(); + + /// + /// Optional. + /// In managed to unmanaged stubs, this method is called after call to the unmanaged code. + /// Must not throw exceptions. + /// + public void OnInvoked(); + } +} +``` + +### Stateful Unmanaged->Managed + +This shape allows the marshaller to maintain state across the unmarshalling process. + +```csharp +[CustomMarshaller(typeof(TCollection), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] +[CustomMarshaller(typeof(TCollection), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] +[ContiguousCollectionMarshaller] +static class TMarshaller where TUnmanagedElement : unmanaged +{ + public struct NativeToManaged // Can be ref struct + { + /// + /// Optional constructor. + /// May throw exceptions. + /// + public NativeToManaged(); + + /// + /// Takes an unmanaged collection to be converted to a managed representation in ToManaged. + /// Should not throw exceptions. + /// + public void FromUnmanaged(TNative value); + + /// + /// Gets the source span of unmanaged elements to unmarshal from. + /// May throw exceptions. + /// + public ReadOnlySpan GetUnmanagedValuesSource(int numElements); + + /// + /// Gets the destination span that unmarshalled managed elements will be written to. + /// May throw exceptions. + /// + public Span GetManagedValuesDestination(int numElements); + + /// + /// Returns the managed value representation of the unmanaged value. + /// May throw exceptions. + /// + public TCollection ToManaged(); + + /// + /// Optional. Should not throw exceptions. + /// + public void Free(); + } +} +``` + +### Stateful unmanaged to managed with guaranteed unmarshalling + +```csharp +[CustomMarshaller(typeof(TCollection), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] +[ContiguousCollectionMarshaller] +static class TMarshaller where TUnmanagedElement : unmanaged +{ + public struct NativeToManaged // Can be ref struct + { + /// + /// Optional constructor. + /// May throw exceptions. + /// + public NativeToManaged(); // Optional, can throw exceptions. + + /// + /// Takes an unmanaged collection to be converted to a managed representation in ToManagedFinally. + /// Should not throw exceptions. + /// + public void FromUnmanaged(TNative value); + + /// + /// Gets the source span of unmanaged elements to unmarshal from. + /// May throw exceptions. + /// + public ReadOnlySpan GetUnmanagedValuesSource(int numElements); + + /// + /// Gets the destination span that unmarshalled managed elements will be written to. + /// May throw exceptions. + /// + public Span GetManagedValuesDestination(int numElements); + + /// + /// Returns the managed value representation of the unmanaged value. + /// Should not throw exceptions. + /// + public TCollection ToManagedFinally(); + + /// + /// Optional. Should not throw exceptions. + /// + public void Free(); + } +} +``` + +### Stateful Bidirectional + +```csharp +[CustomMarshaller(typeof(TCollection), MarshalMode.ManagedToUnmanagedRef, typeof(Bidirectional))] +[CustomMarshaller(typeof(TCollection), MarshalMode.UnmanagedToManagedRef, typeof(Bidirectional))] +[ContiguousCollectionMarshaller] +static class TMarshaller where TUnmanagedElement : unmanaged +{ + public struct Bidirectional // Can be ref struct + { + // Include members from each of the following: + // - One Stateful Managed->Unmanaged Contiguous Collection shape + // - One Stateful Unmanaged->Managed Contiguous Collection shape + } +} +``` diff --git a/docs/standard/native-interop/custom-marshalling-source-generation.md b/docs/standard/native-interop/custom-marshalling-source-generation.md index d69a4137b837b..13b51b8c6547f 100644 --- a/docs/standard/native-interop/custom-marshalling-source-generation.md +++ b/docs/standard/native-interop/custom-marshalling-source-generation.md @@ -14,9 +14,9 @@ ms.date: 08/09/2022 Custom marshaller implementations can either be stateless or stateful. If the marshaller type is a `static` class it's considered stateless, and the implementation methods should do no tracking of state across calls. If it's a value type, it's considered stateful and one instance of that marshaller will be used to marshal a specific parameter or return value, allowing for state to be preserved across the marshalling and unmarshalling process. -# Marshaller shapes +## Marshaller shapes -The set of methods that the marshalling generator expects from a custom marshaller type is referred to as the marshaller shape. Since the marshalling generator supports stateless, static custom marshaller types in .NET Standard 2.0 (which doesn't support static interface methods), there are not interface types provided that define the marshaller shapes. Instead, the shapes are documented below. The marshaller shape expected depends on the whether the marshaller is stateless or stateful, and whether it supports marshalling from managed to unmanaged, unmanaged to managed, or both (declared with `CustomMarshallerAttribute.MarshalMode`). The .NET SDK includes analyzers and code fixers to help with implementing marshallers that conform to the required shapes. +The set of methods that the marshalling generator expects from a custom marshaller type is referred to as the marshaller shape. To support stateless, static custom marshaller types in .NET Standard 2.0 (which doesn't support static interface methods), and improve performance, interface types are not used to define and implement the marshaller shapes. Instead, the shapes are documented in the [Custom Marshaller Shapes](custom-marshaller-shapes.md) article. The methods expected depends on the whether the marshaller is stateless or stateful, and whether it supports marshalling from managed to unmanaged, unmanaged to managed, or both (declared with `CustomMarshallerAttribute.MarshalMode`). The .NET SDK includes analyzers and code fixers to help with implementing marshallers that conform to the required shapes. ### `MarshalMode` @@ -169,37 +169,40 @@ To create a custom marshaller for a generic collection type, you can use the to a marshaller entry-point type to indicate that it's for contiguous collections. The marshaller entry-point type must have one more type parameter than the associated managed type. The last type parameter is a placeholder and will be filled in by the source generator with the unmanaged type for the collection's element type. -For example, you can specify custom marshalling for a . In the following code, `ListMarshaller` is both the entry point and the implementation. It conforms to [marshaller shapes][collection_shapes] expected for custom marshalling of a collection. Note that it is an incomplete example. +For example, you can specify custom marshalling for a . In the following code, `ListMarshaller` is both the entry point and the implementation. It conforms to one of the [marshaller shapes](./custom-marshaller-shapes.md) expected for custom marshalling of a collection. Note that it is an incomplete example. ```csharp [ContiguousCollectionMarshaller] -[CustomMarshaller(typeof(List<>), MarshalMode.Default, typeof(ListMarshaller<,>))] +[CustomMarshaller(typeof(List<>), MarshalMode.Default, typeof(ListMarshaller<,>.DefaultMarshaller))] public unsafe static class ListMarshaller where TUnmanagedElement : unmanaged { - public static byte* AllocateContainerForUnmanagedElements(List managed, out int numElements) + public static class DefaultMarshaller { - numElements = managed.Count; - nuint collectionSizeInBytes = managed.Count * /* size of T */; - return (byte*)NativeMemory.Alloc(collectionSizeInBytes); - } + public static byte* AllocateContainerForUnmanagedElements(List managed, out int numElements) + { + numElements = managed.Count; + nuint collectionSizeInBytes = managed.Count * /* size of T */; + return (byte*)NativeMemory.Alloc(collectionSizeInBytes); + } - public static ReadOnlySpan GetManagedValuesSource(List managed) - => CollectionsMarshal.AsSpan(managed); + public static ReadOnlySpan GetManagedValuesSource(List managed) + => CollectionsMarshal.AsSpan(managed); - public static Span GetUnmanagedValuesDestination(byte* unmanaged, int numElements) - => new Span((TUnmanagedElement*)unmanaged, numElements); + public static Span GetUnmanagedValuesDestination(byte* unmanaged, int numElements) + => new Span((TUnmanagedElement*)unmanaged, numElements); - public static List AllocateContainerForManagedElements(byte* unmanaged, int length) - => new List(length); + public static List AllocateContainerForManagedElements(byte* unmanaged, int length) + => new List(length); - public static Span GetManagedValuesDestination(List managed) - => CollectionsMarshal.AsSpan(managed); + public static Span GetManagedValuesDestination(List managed) + => CollectionsMarshal.AsSpan(managed); - public static ReadOnlySpan GetUnmanagedValuesSource(byte* nativeValue, int numElements) - => new ReadOnlySpan((TUnmanagedElement*)nativeValue, numElements); + public static ReadOnlySpan GetUnmanagedValuesSource(byte* nativeValue, int numElements) + => new ReadOnlySpan((TUnmanagedElement*)nativeValue, numElements); - public static void Free(byte* unmanaged) - => NativeMemory.Free(unmanaged); + public static void Free(byte* unmanaged) + => NativeMemory.Free(unmanaged); + } } ``` @@ -207,7 +210,7 @@ The `ListMarshaller` in the example is a stateless collection marshaller that im ```csharp [LibraryImport("nativelib")] -[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = "numValues")] +[return: MarshalUsing(typeof(ListMarshaller<,>), CountElementName = nameof(numValues))] internal static partial List ConvertList( [MarshalUsing(typeof(ListMarshaller<,>))] List list, out int numValues); @@ -218,11 +221,11 @@ The `ListMarshaller` will handle the collection container and `ExampleMarshaller ```csharp [LibraryImport("nativelib")] -[MarshalUsing(typeof(ListMarshaller<,>))] -[MarshalUsing(typeof(ListMarshaller<,>), ElementIndirectionDepth = 1)] +[MarshalUsing(typeof(ListMarshaller<,>), CountElementName = nameof(numValues))] +[MarshalUsing(typeof(ExampleMarshaller), ElementIndirectionDepth = 1)] internal static partial void ConvertList( [MarshalUsing(typeof(ListMarshaller<,>))] - [MarshalUsing(typeof(ListMarshaller<,>), ElementIndirectionDepth = 1)] + [MarshalUsing(typeof(ExampleMarshaller), ElementIndirectionDepth = 1)] List list, out int numValues); ``` diff --git a/docs/standard/native-interop/customize-parameter-marshalling.md b/docs/standard/native-interop/customize-parameter-marshalling.md index e9c964ef6d324..a076959efaefc 100644 --- a/docs/standard/native-interop/customize-parameter-marshalling.md +++ b/docs/standard/native-interop/customize-parameter-marshalling.md @@ -35,7 +35,7 @@ When interacting with COM or OLE interfaces, you'll likely find that the native If you're interacting with WinRT APIs, you can use the format to marshal a string as an `HSTRING`. -## Customizing array parameters +## Customize array parameters .NET also provides you multiple ways to marshal array parameters. If you're calling an API that takes a C-style array, use the unmanaged type. If the values in the array need customized marshalling, you can use the field on the `[MarshalAs]` attribute for that. diff --git a/docs/standard/native-interop/value-marshaller-shapes.md b/docs/standard/native-interop/value-marshaller-shapes.md deleted file mode 100644 index 007eb751386e9..0000000000000 --- a/docs/standard/native-interop/value-marshaller-shapes.md +++ /dev/null @@ -1,384 +0,0 @@ ---- -description: "Learn more about: Value Marshallers in Interop Source Generation" -title: "Custom Marshaller Shapes - Value Marshallers" -ms.date: "07/07/2025" -helpviewer_keywords: - - "unmanaged code, exceptions" - - "exceptions, unmanaged code" - - "interop, exceptions" - - "exceptions, interop" - - "interop source generation" - - "custom marshallers" - - "exceptions, custom marshallers" ---- - -## Value Marshallers - -This document describes the shapes of custom marshallers that can be used by the .NET interop source generator for marshalling types between managed and unmanaged code. - -### Stateless Managed to Unmanaged - -With this shape, the generated code will call `ConvertToUnmanaged` to marshal a value to native code, or `GetPinnableReference` when applicable. The generated code will call `Free` when applicable to allow the marshaller to free any unmanaged resources associated with the managed type once its lifetime ends. - -```csharp -[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] -[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] -static class TMarshaller -{ - public static class ManagedToNative - { - /// - /// Converts a managed type to an unmanaged representation. May throw exceptions. - /// - public static TNative ConvertToUnmanaged(TManaged managed); - - /// - /// Optional. - /// Returns a pinnable reference to the unmanaged representation. - /// The reference will be pinned and passed as a pointer to the native code. - /// TOther should be a dereferenced type of TNative. - /// For example, if TNative is `int*`, then TOther should be `int`. - /// - public static ref TNativeDereferenced GetPinnableReference(TManaged managed); - - /// - /// Optional. - /// Frees any unmanaged resources associated with the marshalling of the managed type. - /// Must not throw exceptions. - /// - public static void Free(TNative unmanaged); - } -} -``` - -### Stateless Managed->Unmanaged with Caller-Allocated Buffer - -With this shape, the generator will allocate a buffer of the specified size and pass it to the `ConvertToUnmanaged` method to marshal a value to native code. The generated code will handle the lifetime of this buffer. - -```csharp -[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] -[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] -static class TMarshaller -{ - public static class ManagedToNative - { - /// - /// The size of the buffer that will be allocated by the generator. - /// - public static int BufferSize { get; } - - /// - /// Converts a managed type to an unmanaged representation using a caller-allocated buffer. - /// - public static TNative ConvertToUnmanaged(TManaged managed, Span callerAllocatedBuffer); - - /// - /// Optional. - /// Frees any unmanaged resources associated with the marshalling of the managed type. - /// Must not throw exceptions. - /// - public static void Free(TNative unmanaged); - } -} -``` - -### Stateless Unmanaged->Managed - -```csharp -[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] -[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] -static class TMarshaller -{ - public static class NativeToManaged - { - /// - /// Converts an unmanaged representation to a managed type. May throw exceptions. - /// - public static TManaged ConvertToManaged(TNative unmanaged); - - /// - /// Optional. - /// Frees any unmanaged resources associated with the marshalling of the managed type. - /// Must not throw exceptions. - /// - public static void Free(TNative unmanaged); - } -} -``` - -### Stateless Unmanaged->Managed with Guaranteed Unmarshalling - -This shape directs the generator to emit the unmarshaling code in a way that guarantees the unmarshaling will be called, even if a previous marshaller throws an exception. This is useful for types that need to be cleaned up or finalized regardless of the success of the previous operations. - -```csharp -[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] -[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] -static class TMarshaller -{ - public static class NativeToManaged - { - /// - /// Converts an unmanaged representation to a managed type. - /// Should not throw exceptions. - /// - public static TManaged ConvertToManagedFinally(TNative unmanaged); - - /// - /// Optional. - /// Frees any unmanaged resources associated with the marshalling of the managed type. - /// Must not throw exceptions. - /// - public static void Free(TNative unmanaged); - } -} -``` - -### Stateless Bidirectional - -This shape allows for both managed to unmanaged and unmanaged to managed conversions, with the marshaller being stateless. The generator will use the `ConvertToUnmanaged` method for managed to unmanaged conversions and the `ConvertToManaged` method for unmanaged to managed conversions. - -```csharp -[CustomMarshaller(typeof(TManaged<,,,...>), MarshalMode.ManagedToUnmanagedRef, typeof(Bidirectional))] -[CustomMarshaller(typeof(TManaged<,,,...>), MarshalMode.UnmanagedToManagedRef, typeof(Bidirectional))] -[CustomMarshaller(typeof(TManaged<,,,...>), MarshalMode.ElementRef, typeof(Bidirectional))] -static class TMarshaller -{ - public static class Bidirectional - { - // Include members from each of the following: - // - One Stateless Managed->Unmanaged Value shape - // - One Stateless Unmanaged->Managed Value shape - } -} -``` - -### Stateful Managed->Unmanaged - -This shape allows for stateful marshalling from managed to unmanaged. The generated code will use a unique marshaller instance for each parameter, so the marshaller can maintain state across marshalling - -```csharp -[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] -[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] -static class TMarshaller -{ - public struct ManagedToNative // Can be ref struct - { - /// - /// Optional constructor. - /// May throw exceptions. - /// - public ManagedToNative(); - - /// - /// Takes a managed type to be converted to an unmanaged representation in ToUnmanaged or GetPinnableReference. - /// - public void FromManaged(TManaged managed); // Can throw exceptions. - - /// - /// Optional. - /// Returns a pinnable reference to the unmanaged representation. - /// The reference will be pinned and passed as a pointer to the native code. - /// TOther should be a dereferenced type of TNative. - /// For example, if TNative is `int*`, then TOther should be `int`. - /// When applicable, this method is preferred to ToUnmanaged for marshalling. - /// - public ref TIgnored GetPinnableReference(); - - /// - /// Optional. - /// Returns a pinnable reference to the unmanaged representation. - /// The reference will be pinned and passed as a pointer to the native code. - /// TOther should be a dereferenced type of TNative. - /// For example, if TNative is `int*`, then TOther should be `int`. - /// When applicable, only this method is called for the marshalling step. - /// May throw exceptions. - /// - public static ref TOther GetPinnableReference(TManaged managed); - - /// - /// Converts the managed type to an unmanaged representation. - /// May throw exceptions. - /// - public TNative ToUnmanaged(); - - /// - /// Optional. - /// In managed to unmanaged stubs, this method is called after call to the unmanaged code. - /// Must not throw exceptions. - /// - public void OnInvoked(); - - /// - /// Optional. - /// Frees any unmanaged resources associated with the marshalling of the managed type. - /// Must not throw exceptions. - /// - public void Free(); - } -} -``` - -### Stateful Managed->Unmanaged with Caller Allocated Buffer - -This shape allows for stateful marshalling from managed to unmanaged, with the generator allocating a buffer of the specified size and passing it to the `FromManaged` method. The generated code will use a unique marshaller instance for each parameter, so the marshaller can maintain state across marshalling. - -```csharp -[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] -[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedOut, typeof(ManagedToNative))] -static class TMarshaller -{ - public struct ManagedToNative // Can be ref struct - { - /// - /// The size of the buffer, in bytes, that will be allocated by the generator. - /// - public static int BufferSize { get; } - - /// - /// Optional constructor. - /// May throw exceptions. - /// - public ManagedToNative(); - - /// - /// Takes a managed type to be converted to an unmanaged representation in ToUnmanaged or GetPinnableReference. - /// May throw exceptions. - /// - public void FromManaged(TManaged managed, Span buffer); - - /// - /// Optional. - /// Returns a pinnable reference to the unmanaged representation. - /// The reference will be pinned and passed as a pointer to the native code. - /// TOther should be a dereferenced type of TNative. - /// For example, if TNative is `int*`, then TOther should be `int`. - /// When applicable, this method is preferred to ToUnmanaged for marshalling. - /// - public ref TIgnored GetPinnableReference(); - - /// - /// Optional. - /// Returns a pinnable reference to the unmanaged representation. - /// The reference will be pinned and passed as a pointer to the native code. - /// TOther should be a dereferenced type of TNative. - /// For example, if TNative is `int*`, then TOther should be `int`. - /// When applicable, only this method is called for the marshalling step. - /// May throw exceptions. - public static ref TOther GetPinnableReference(TManaged managed); - - /// - /// Returns the unmanaged representation of the managed value. - /// May throw exceptions. - /// - public TNative ToUnmanaged(); - - /// - /// Optional. - /// In managed to unmanaged stubs, this method is called after call to the unmanaged code. - /// Must not throw exceptions. - /// - public void OnInvoked(); - - /// - /// Optional. - /// Frees any unmanaged resources associated with the marshalling of the managed type. - /// Must not throw exceptions. - /// - public void Free(); - } -} -``` - -### Stateful Unmanaged->Managed - -This shape allows for stateful unmarshalling from unmanaged to managed. The generated code will use a unique instance for each parameter, so the struct can maintain state across unmarshalling. - -```csharp -[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] -[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] -static class TMarshaller -{ - public struct NativeToManaged // Can be ref struct - { - /// - /// Optional constructor. - /// May throw exceptions. - /// - public NativeToManaged(); - - /// - /// Takes an unmanaged representation to be converted to a managed type in ToManaged. - /// Should not throw exceptions. - /// - public void FromUnmanaged(TNative unmanaged); - - /// - /// Returns the managed value representation of the unmanaged value. - /// May throw exceptions. - /// - public TManaged ToManaged(); - - /// - /// Optional. - /// Frees any unmanaged resources associated with the unmarshalling of the managed type. - /// Must not throw exceptions. - /// - public void Free(); - } -} -``` - -### Stateful Unmanaged->Managed with Guaranteed Unmarshalling - -This shape allows for stateful unmarshalling from unmanaged to managed, with the generator ensuring that the `ToManagedFinally` method is called even if a previous marshaller throws an exception. This is useful for types that need to be cleaned up or finalized regardless of the success of the previous operations. - -```csharp -[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedOut, typeof(NativeToManaged))] -[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedIn, typeof(NativeToManaged))] -static class TMarshaller -{ - public struct NativeToManaged // Can be ref struct - { - /// - /// Optional constructor. - /// May throw exceptions. - /// - public NativeToManaged(); - - /// - /// Takes an unmanaged representation to be converted to a managed type in ToManagedFinally. - /// Should not throw exceptions. - /// - public void FromUnmanaged(TNative unmanaged); - - /// - /// Returns the managed value representation of the unmanaged value. - /// Should not throw exceptions. - /// - public TManaged ToManagedFinally(); - - /// - /// Optional. - /// Frees any unmanaged resources associated with the unmarshalling of the managed type. - /// Must not throw exceptions. - /// - public void Free(); - } -} -``` - -### Stateful Bidirectional -```csharp -[CustomMarshaller(typeof(TManaged), MarshalMode.ManagedToUnmanagedRef, typeof(Bidirectional))] -[CustomMarshaller(typeof(TManaged), MarshalMode.UnmanagedToManagedRef, typeof(Bidirectional))] -static class TMarshaller -{ - public struct Bidirectional // Can be ref struct - { - // Include members from each of the following: - // - One Stateful Managed->Unmanaged Value shape - // - One Stateful Unmanaged->Managed Value shape - } -} -``` - From 008637fbb1d7a15b80f5b66f297b4cc8cbf64291 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 8 Jul 2025 16:36:03 -0700 Subject: [PATCH 3/4] Fix lint/build issues --- docs/standard/native-interop/custom-marshaller-shapes.md | 1 - .../native-interop/custom-marshalling-source-generation.md | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/standard/native-interop/custom-marshaller-shapes.md b/docs/standard/native-interop/custom-marshaller-shapes.md index 7e51482666332..86bd4d8c111b9 100644 --- a/docs/standard/native-interop/custom-marshaller-shapes.md +++ b/docs/standard/native-interop/custom-marshaller-shapes.md @@ -664,7 +664,6 @@ static class TMarshaller where TUnmanagedElement : unmanaged ### Stateful Managed->Unmanaged with Caller Allocated Buffer - ```csharp [CustomMarshaller(typeof(TCollection), MarshalMode.ManagedToUnmanagedIn, typeof(ManagedToNative))] [ContiguousCollectionMarshaller] diff --git a/docs/standard/native-interop/custom-marshalling-source-generation.md b/docs/standard/native-interop/custom-marshalling-source-generation.md index 13b51b8c6547f..7d50a35d69636 100644 --- a/docs/standard/native-interop/custom-marshalling-source-generation.md +++ b/docs/standard/native-interop/custom-marshalling-source-generation.md @@ -20,7 +20,7 @@ The set of methods that the marshalling generator expects from a custom marshall ### `MarshalMode` -The specified in a determines the expected marshalling support and [shape][value_shapes] for the marshaller implementation. All modes support stateless marshaller implementations. Element marshalling modes do not support stateful marshaller implementations. +The specified in a determines the expected marshalling support and shape for the marshaller implementation. All modes support stateless marshaller implementations. Element marshalling modes do not support stateful marshaller implementations. | `MarshalMode` | Expected support | Can be stateful | | --- | --- | --- | @@ -235,4 +235,4 @@ internal static partial void ConvertList( - [System.Runtime.InteropServices.Marshalling APIs](xref:System.Runtime.InteropServices.Marshalling) - [P/Invoke source generation](pinvoke-source-generation.md) - [Disabling runtime marshalling](disabled-marshalling.md) -- [Marshaller shapes](marshaller-shapes.md) +- [Marshaller shapes](custom-marshaller-shapes.md) From 98f4fd78fc27f1616d178e341ef3258c3f2b088f Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 9 Jul 2025 10:27:27 -0700 Subject: [PATCH 4/4] Reword MarshalMode.Default explanation --- .../native-interop/custom-marshalling-source-generation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/standard/native-interop/custom-marshalling-source-generation.md b/docs/standard/native-interop/custom-marshalling-source-generation.md index 7d50a35d69636..db1f6bb8b382b 100644 --- a/docs/standard/native-interop/custom-marshalling-source-generation.md +++ b/docs/standard/native-interop/custom-marshalling-source-generation.md @@ -34,7 +34,7 @@ The specified in a | | Managed to unmanaged and unmanaged to managed | No | | | Unmanaged to managed | No | - indicates that the marshaller implementation should be used for any mode that it supports (assumed by the methods it implements). If a marshaller implementation for a more specific `MarshalMode` is also specified, it takes precedence over `MarshalMode.Default`. +Use to indicate that the marshaller implementation applies to any supported mode, based on the methods it implements. If you specify a marshaller for a more specific `MarshalMode`, that marshaller takes precedence over one marked as `Default`. ## Basic usage