diff --git a/docs/design/libraries/LibraryImportGenerator/SpanMarshallers.md b/docs/design/libraries/LibraryImportGenerator/SpanMarshallers.md index eccefc62e8748..b787c172e6c38 100644 --- a/docs/design/libraries/LibraryImportGenerator/SpanMarshallers.md +++ b/docs/design/libraries/LibraryImportGenerator/SpanMarshallers.md @@ -24,11 +24,11 @@ We have decided to match the managed semantics of `(ReadOnly)Span` to provide As part of this design, we would also want to include some in-box marshallers that follow the design laid out in the [Struct Marshalling design doc](./StructMarshalling.md) to support some additional scenarios: -- A marshaler that marshals an empty span as a non-null pointer. +- A marshaller that marshals an empty span as a non-null pointer. - This marshaller would only support empty spans as it cannot correctly represent non-empty spans of non-blittable types. -- A marshaler that marshals out a pointer to the native memory as a Span instead of copying the data into a managed array. +- A marshaller that marshals out a pointer to the native memory as a Span instead of copying the data into a managed array. - This marshaller would only support blittable spans by design. - - This marshaler will require the user to manually release the memory. Since this will be an opt-in marshaler, this scenario is already advanced and that additional requirement should be understandable to users who use this marshaler. + - This marshaller will require the user to manually release the memory. Since this will be an opt-in marshaller, this scenario is already advanced and that additional requirement should be understandable to users who use this marshaller. - Since there is no mechansim to provide a collection length, the question of how to provide the span's length in this case is still unresolved. One option would be to always provide a length 1 span and require the user to create a new span with the correct size, but that feels like a bad design. ### Pros/Cons of Design 1 @@ -40,7 +40,7 @@ Pros: Cons: -- Defining custom marshalers for non-empty spans of non-blittable types generically is impossible since the marshalling rules of the element's type cannot be known. +- Defining custom marshallers for non-empty spans of non-blittable types generically is impossible since the marshalling rules of the element's type cannot be known. - Custom non-default marshalling of the span element types is impossible for non-built-in types. - Inlining the span marshalling fully into the stub increases on-disk IL size. - This design does not enable developers to easily define custom marshalling support for their own collection types, which may be desireable. @@ -55,43 +55,86 @@ Span marshalling would still be implemented with similar semantics as mentioned ### Proposed extension to the custom type marshalling design -Introduce a new attribute named `GenericContiguousCollectionMarshallerAttribute`. This attribute would have the following shape: +Introduce a marshaller kind named `LinearCollection`. -```csharp +```diff namespace System.Runtime.InteropServices -{ - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class)] - public sealed class GenericContiguousCollectionMarshallerAttribute : Attribute - { - public GenericContiguousCollectionMarshallerAttribute(); - } +{ + [AttributeUsage(AttributeTargets.Struct)] + public sealed class CustomTypeMarshallerAttribute : Attribute + { ++ /// ++ /// This type is used as a placeholder for the first generic parameter when generic parameters cannot be used ++ /// to identify the managed type (i.e. when the marshaller type is generic over T and the managed type is T[]) ++ /// ++ public struct GenericPlaceholder ++ { ++ } + } + + public enum CustomTypeMarshallerKind + { + Value, ++ LinearCollection + } } ``` The attribute would be used with a collection type like `Span` as follows: ```csharp -[NativeTypeMarshalling(typeof(DefaultSpanMarshaler<>))] +[NativeTypeMarshalling(typeof(DefaultSpanMarshaller<>))] public ref struct Span { ... } -[GenericContiguousCollectionMarshaller] -public ref struct DefaultSpanMarshaler +[CustomTypeMarshaller(typeof(Span<>), CustomTypeMarshallerKind.LinearCollection)] +public ref struct DefaultSpanMarshaller { ... } ``` -The `GenericContiguousCollectionMarshallerAttribute` attribute is applied to a generic marshaler type with the "collection marshaller" shape described below. Since generic parameters cannot be used in attributes, open generic types will be permitted in the `NativeTypeMarshallingAttribute` constructor as long as they have the same arity as the type the attribute is applied to and generic parameters provided to the applied-to type can also be used to construct the type passed as a parameter. +The `CustomTypeMarshallerKind.LinearCollection` kind is applied to a generic marshaller type with the "LinearCollection marshaller shape" described below. + +#### Supporting generics + +Since generic parameters cannot be used in attributes, open generic types will be permitted in the `NativeTypeMarshallingAttribute` and the `CustomTypeMarshallerAttribute` as long as they have the same arity as the type with the attribute and generic parameters provided to the type with the attribute can also be used to construct the type passed as a parameter. + +If a `CustomTypeMarshaller`-attributed type is a marshaller for a type for a pointer, an array, or a combination of pointers and arrays, the `CustomTypeMarshallerAttribute.GenericPlaceholder` type can be used in the place of the first generic parameter of the marshaller type. + +For example: + +```csharp +[CustomTypeMarshaller(typeof(CustomTypeMarshallerAttribute.GenericPlaceholder), Direction = CustomTypeMarshallerDirection.In)] +struct Marshaller +{ + public Marshaller(T managed); +} +[CustomTypeMarshaller(typeof(CustomTypeMarshallerAttribute.GenericPlaceholder[]), Direction = CustomTypeMarshallerDirection.In)] +struct Marshaller +{ + public Marshaller(T[] managed); +} +[CustomTypeMarshaller(typeof(CustomTypeMarshallerAttribute.GenericPlaceholder*), Direction = CustomTypeMarshallerDirection.In)] +struct Marshaller where T : unmanaged +{ + public Marshaller(T* managed); +} +[CustomTypeMarshaller(typeof(CustomTypeMarshallerAttribute.GenericPlaceholder*[]), Direction = CustomTypeMarshallerDirection.In)] +struct Marshaller where T : unmanaged +{ + public Marshaller(T*[] managed); +} +``` -#### Generic collection marshaller shape +#### LinearCollection marshaller shape -A generic collection marshaller would be required to have the following shape, in addition to the requirements for marshaler types used with the `NativeTypeMarshallingAttribute`, excluding the constructors. +A generic collection marshaller would be required to have the following shape, in addition to the requirements for marshaller types used with the `CustomTypeMarshallerKind.Value` shape, excluding the constructors. ```csharp -[GenericContiguousCollectionMarshaller] +[CustomTypeMarshaller(typeof(GenericCollection<, , ,...>), CustomTypeMarshallerKind.LinearCollection)] public struct GenericContiguousCollectionMarshallerImpl { // this constructor is required if marshalling from native to managed is supported. @@ -100,29 +143,33 @@ public struct GenericContiguousCollectionMarshallerImpl public GenericContiguousCollectionMarshallerImpl(GenericCollection collection, int nativeSizeOfElement); public GenericContiguousCollectionMarshallerImpl(GenericCollection collection, Span stackSpace, int nativeSizeOfElement); // optional - public const int StackBufferSize = /* */; // required if the span-based constructor is supplied. - /// - /// A span that points to the memory where the managed values of the collection are stored (in the marshalling case) or should be stored (in the unmarshalling case). + /// A span that points to the memory where the managed values of the collection are stored. /// - public Span ManagedValues { get; } - + public ReadOnlySpan GetManagedValuesSource(); /// - /// Set the expected length of the managed collection based on the parameter/return value/field marshalling information. - /// Required only when unmarshalling is supported. + /// A span that points to the memory where the unmarshalled managed values of the collection should be stored. /// - public void SetUnmarshalledCollectionLength(int length); - - public IntPtr Value { get; set; } + public Span GetManagedValuesDestination(int length); + /// + /// A span that points to the memory where the native values of the collection are stored after the native call. + /// + public ReadOnlySpan GetNativeValuesSource(int length); + /// + /// A span that points to the memory where the native values of the collection should be stored. + /// + public Span GetNativeValuesDestination(); /// /// A span that points to the memory where the native values of the collection should be stored. /// public unsafe Span NativeValueStorage { get; } - // The requirements on the Value property are the same as when used with `NativeTypeMarshallingAttribute`. + // The requirements on the TNative type are the same as when used with `NativeTypeMarshallingAttribute`. // The property is required with the generic collection marshalling. - public TNative Value { get; set; } + public TNative ToNativeValue(); + + public void FromNativeValue(TNative value); } ``` @@ -162,7 +209,7 @@ To support supplying information about collection element counts, a parameterles The `ElementIndirectionLevel` property is added to support supplying marshalling info for element types in a collection. For example, if the user is passing a `List>` from managed to native code, they could provide the following attributes to specify marshalling rules for the outer and inner lists and `Foo` separately: ```csharp -private static partial void Bar([MarshalUsing(typeof(ListAsArrayMarshaller>), CountElementName = nameof(count)), MarshalUsing(ConstantElementCount = 10, ElementIndirectionLevel = 1), MarshalUsing(typeof(FooMarshaler), ElementIndirectionLevel = 2)] List> foos, int count); +private static partial void Bar([MarshalUsing(typeof(ListAsArrayMarshaller>), CountElementName = nameof(count)), MarshalUsing(ConstantElementCount = 10, ElementIndirectionLevel = 1), MarshalUsing(typeof(FooMarshaller), ElementIndirectionLevel = 2)] List> foos, int count); ``` Multiple `MarshalUsing` attributes can only be supplied on the same parameter or return value if the `ElementIndirectionLevel` property is set to distinct values. One `MarshalUsing` attribute per parameter or return value can leave the `ElementIndirectionLevel` property unset. This attribute controls the marshalling of the collection object passed in as the parameter. The sequence of managed types for `ElementIndirectionLevel` is based on the elements of the `ManagedValues` span on the collection marshaller of the previous indirection level. For example, for the marshalling info for `ElementIndirectionLevel = 1` above, the managed type is the type of the following C# expression: `ListAsArrayMarshaller>.ManagedValues[0]`. @@ -174,14 +221,16 @@ Alternatively, the `MarshalUsingAttribute` could provide a `Type ElementNativeTy This design could be used to provide a default marshaller for spans and arrays. Below is an example simple marshaller for `Span`. This design does not include all possible optimizations, such as stack allocation, for simpilicity of the example. ```csharp -[GenericContiguousCollectionMarshaller] -public ref struct SpanMarshaler +[CustomTypeMarshaller(typeof(Span<>), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling)] +public ref struct SpanMarshaller { private Span managedCollection; private int nativeElementSize; + + private IntPtr Value { get; set; } - public SpanMarshaler(Span collection, int nativeSizeOfElement) + public SpanMarshaller(Span collection, int nativeSizeOfElement) { managedCollection = collection; Value = Marshal.AllocCoTaskMem(collection.Length * nativeSizeOfElement); @@ -189,19 +238,20 @@ public ref struct SpanMarshaler nativeElementSize = nativeSizeOfElement; } - public Span ManagedValues => managedCollection; - - public void SetUnmarshalledCollectionLength(int length) - { - managedCollection = new T[value]; - } + public ReadOnlySpan GetManagedValuesSource() => managedCollection; + + public Span GetManagedValuesDestination(int length) => managedCollection = new T[length]; - public IntPtr Value { get; set; } + public unsafe Span GetNativeValuesDestination() => MemoryMarshal.CreateSpan(ref *(byte*)(Value), managedCollection.Length); - public unsafe Span NativeValueStorage => MemoryMarshal.CreateSpan(ref *(byte*)(Value), Length); + public unsafe Span GetNativeValuesSource(int length) => MemoryMarshal.CreateSpan(ref *(byte*)(Value), length); public Span ToManaged() => managedCollection; + public IntPtr ToNativeValue() => Value; + + public void FromNativeValue(IntPtr value) => Value = value; + public void FreeNative() { if (Value != IntPtr.Zero) @@ -235,19 +285,20 @@ public static partial Span DuplicateValues([MarshalUsing(typeof(WrappedInt) public static partial unsafe Span DuplicateValues(Span values, int length) { SpanMarshaller __values_marshaller = new SpanMarshaller(values, sizeof(WrappedInt)); - for (int i = 0; i < __values_marshaller.ManagedValues.Length; ++i) { - WrappedInt native = new WrappedInt(__values_marshaller.ManagedValues[i]); - MemoryMarshal.Write(__values_marshaller.NativeValueStorage.Slice(sizeof(WrappedInt) * i), ref native); + ReadOnlySpan __values_managedSpan = __values_marshaller.GetManagedValuesSource(); + Span __values_nativeSpan = __values_marshaller.GetNativeValuesDestination(); + for (int i = 0; i < __values_managedSpan.Length; ++i) + { + WrappedInt native = new WrappedInt(__values_managedSpan[i]); + MemoryMarshal.Write(__values_nativeSpan.Slice(sizeof(WrappedInt) * i), ref native); + } } - IntPtr __retVal_native = __PInvoke__(__values_marshaller.Value, length); - SpanMarshaller __retVal_marshaller = new - { - Value = __retVal_native - }; - __retVal_marshaller.SetUnmarshalledCollectionLength(length); - MemoryMarshal.Cast(__retVal_marshaller.NativeValueStorage).CopyTo(__retVal_marshaller.ManagedValues); + IntPtr __retVal_native = __PInvoke__(__values_marshaller.ToNativeValue(), length); + SpanMarshaller __retVal_marshaller = new(); + __retVal_marshaller.FromNativeValue(__retVal_native); + MemoryMarshal.Cast(__retVal_marshaller.GetNativeValuesSource(length)).CopyTo(__retVal_marshaller.GetManagedValuesDestination(length)); return __retVal_marshaller.ToManaged(); [DllImport("Native", EntryPoint="DuplicateValues")] @@ -261,30 +312,24 @@ This design could also be applied to support the built-in array marshalling if i If a managed or native representation of a collection has a non-contiguous element layout, then developers currently will need to convert to or from array/span types at the interop boundary. This section proposes an API that would enable developers to convert directly between a managed and native non-contiguous collection layout as part of marshalling. -A new attribute named `GenericCollectionMarshaller` attribute could be added that would specify that the collection is noncontiguous in either managed or native representations. Then additional methods should be added to the generic collection model, and some methods would be removed: +A new marshaller kind named `GenericCollection` could be added that would specify that the collection is noncontiguous in either managed or native representations. Then additional methods should be added to the generic collection model, and some methods would be removed: ```diff -- [GenericContiguousCollectionMarshaller] -+ [GenericCollectionMarshaller] +- [CustomTypeMarshaller(typeof(Span<>), CustomTypeMarshallerKind.LinearCollection)] ++ [CustomTypeMarshaller(typeof(Span<>), CustomTypeMarshallerKind.GenericCollection)] public struct GenericContiguousCollectionMarshallerImpl { // these constructors are required if marshalling from managed to native is supported. public GenericContiguousCollectionMarshallerImpl(GenericCollection collection, int nativeSizeOfElements); public GenericContiguousCollectionMarshallerImpl(GenericCollection collection, Span stackSpace, int nativeSizeOfElements); // optional - public const int StackBufferSize = /* */; // required if the span-based constructor is supplied. + public TNative ToNativeValue(); + public void FromNativeValue(TNative value); -- public Span ManagedValues { get; } - -- public void SetUnmarshalledCollectionLength(int length); - - public IntPtr Value { get; set; } - -- public unsafe Span NativeValueStorage { get; } - - // The requirements on the Value property are the same as when used with `NativeTypeMarshallingAttribute`. - // The property is required with the generic collection marshalling. - public TNative Value { get; set; } +- public ReadOnlySpan GetManagedValuesSource(); +- public Span GetManagedValuesDestination(int length); +- public ReadOnlySpan GetNativeValuesSource(int length); +- public Span GetNativeValuesDestination(); + public ref byte GetOffsetForNativeValueAtIndex(int index); + public TCollectionElement GetManagedValueAtIndex(int index); @@ -293,9 +338,9 @@ public struct GenericContiguousCollectionMarshallerImpl } ``` -The `GetManagedValueAtIndex` method and `Count` getter are used in the process of marshalling from managed to native. The generated code will iterate through `Count` elements (retrieved through `GetManagedValueAtIndex`) and assign their marshalled result to the address represented by `GetOffsetForNativeValueAtIndex` called with the same index. Then either the `Value` property getter will be called or the marshaller's `GetPinnableReference` method will be called, depending on if pinning is supported in the current scenario. +The `GetManagedValueAtIndex` method and `Count` getter are used in the process of marshalling from managed to native. The generated code will iterate through `Count` elements (retrieved through `GetManagedValueAtIndex`) and assign their marshalled result to the address represented by `GetOffsetForNativeValueAtIndex` called with the same index. Then the `ToNativeValue` method will be called to get the value to pass to native code. -The `SetManagedValueAtIndex` method and the `Count` setter are used in the process of marshalling from native to managed. The `Count` property will be set to the number of elements that the native collection contains, and the `Value` property will be assigned the result value from native code. Then the stub will iterate through the native collection `Count` times, calling `GetOffsetForNativeValueAtIndex` to get the offset of the native value and calling `SetManagedValueAtIndex` to set the unmarshalled managed value at that index. +The `SetManagedValueAtIndex` method and the `Count` setter are used in the process of marshalling from native to managed. The `Count` property will be set to the number of elements that the native collection contains, and the `FromNativeValue` property will be called with the result value from native code. Then the stub will iterate through the native collection `Count` times, calling `GetOffsetForNativeValueAtIndex` to get the offset of the native value and calling `SetManagedValueAtIndex` to set the unmarshalled managed value at that index. ### Pros/Cons of Design 2 @@ -312,6 +357,6 @@ Cons: - Introduces more attribute types into the BCL. - Introduces more complexity in the marshalling type model. - It may be worth describing the required members (other than constructors) in interfaces just to simplify the mental load of which members are required for which scenarios. - - A set of interfaces (one for managed-to-native members, one for native-to-managed members, and one for the sequential-specific members) could replace the `GenericContiguousCollectionMarshaller` attribute. + - A set of interfaces (one for managed-to-native members, one for native-to-managed members, and one for the sequential-specific members) could replace the new marshaller kind. - The base proposal only supports contiguous collections. - The feeling at time of writing is that we are okay asking developers to convert to/from arrays or spans at the interop boundary. diff --git a/docs/design/libraries/LibraryImportGenerator/StructMarshalling.md b/docs/design/libraries/LibraryImportGenerator/StructMarshalling.md index 63781d6d77dbb..7028227fedd2e 100644 --- a/docs/design/libraries/LibraryImportGenerator/StructMarshalling.md +++ b/docs/design/libraries/LibraryImportGenerator/StructMarshalling.md @@ -25,89 +25,119 @@ All design options would use these attributes: ```csharp [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class)] -public class GeneratedMarshallingAttribute : Attribute {} +public sealed class GeneratedMarshallingAttribute : Attribute {} [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class)] -public class NativeMarshallingAttribute : Attribute +public sealed class NativeMarshallingAttribute : Attribute { public NativeMarshallingAttribute(Type nativeType) {} } [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.Field)] -public class MarshalUsingAttribute : Attribute +public sealed class MarshalUsingAttribute : Attribute { public MarshalUsingAttribute(Type nativeType) {} } -``` - -The `NativeMarshallingAttribute` and `MarshalUsingAttribute` attributes would require that the provided native type `TNative` is a `struct` that does not require any marshalling and has a subset of three methods with the following names and shapes (with the managed type named TManaged): -```csharp -partial struct TNative +[AttributeUsage(AttributeTargets.Struct)] +public sealed class CustomTypeMarshallerAttribute : Attribute { - public TNative(TManaged managed) {} - public TManaged ToManaged() {} + public CustomTypeMarshallerAttribute(Type managedType, CustomTypeMarshallerKind marshallerKind = CustomTypeMarshallerKind.Value) + { + ManagedType = managedType; + MarshallerKind = marshallerKind; + } - public void FreeNative() {} + public Type ManagedType { get; } + public CustomTypeMarshallerKind MarshallerKind { get; } + public int BufferSize { get; set; } + public CustomTypeMarshallerDirection Direction { get; set; } = CustomTypeMarshallerDirection.Ref; + public CustomTypeMarshallerFeatures Features { get; set; } } -``` - -The analyzer will report an error if neither the constructor nor the `ToManaged` method is defined. When one of those two methods is missing, the direction of marshalling (managed to native/native to managed) that relies on the missing method is considered unsupported for the corresponding managed type. The `FreeNative` method is only required when there are resources that need to be released. - - -> :question: Does this API surface and shape work for all marshalling scenarios we plan on supporting? It may have issues with the current "layout class" by-value `[Out]` parameter marshalling where the runtime updates a `class` typed object in place. We already recommend against using classes for interop for performance reasons and a struct value passed via `ref` or `out` with the same members would cover this scenario. - -If the native type `TNative` also has a public `Value` property, then the value of the `Value` property will be passed to native code instead of the `TNative` value itself. As a result, the type `TNative` will be allowed to require marshalling and the type of the `Value` property will be required be passable to native code without any additional marshalling. If the `Value` property is settable, then when marshalling in the native-to-managed direction, a default value of `TNative` will have its `Value` property set to the native value. If `Value` does not have a setter, then marshalling from native to managed is not supported. -If a `Value` property is provided, the developer may also provide a ref-returning or readonly-ref-returning `GetPinnableReference` method. The `GetPinnableReference` method will be called before the `Value` property getter is called. The ref returned by `GetPinnableReference` will be pinned with a `fixed` statement, but the pinned value will not be used (it acts exclusively as a side-effect). - -A `ref` or `ref readonly` typed `Value` property is unsupported. If a ref-return is required, the type author can supply a `GetPinnableReference` method on the native type to pin the desired `ref` to return and then use `System.Runtime.CompilerServices.Unsafe.AsPointer` to get a pointer from the `ref` that will have already been pinned by the time the `Value` getter is called. +public enum CustomTypeMarshallerKind +{ + Value +} -```csharp -[NativeMarshalling(typeof(TMarshaler))] -public struct TManaged +[Flags] +public enum CustomTypeMarshallerFeatures { - // ... + None = 0, + /// + /// The marshaller owns unmanaged resources that must be freed + /// + UnmanagedResources = 0x1, + /// + /// The marshaller can use a caller-allocated buffer instead of allocating in some scenarios + /// + CallerAllocatedBuffer = 0x2, + /// + /// The marshaller uses the two-stage marshalling design for its instead of the one-stage design. + /// + TwoStageMarshalling = 0x4 } +[Flags] +public enum CustomTypeMarshallerDirection +{ + /// + /// No marshalling direction + /// + [EditorBrowsable(EditorBrowsableState.Never)] + None = 0, + /// + /// Marshalling from a managed environment to an unmanaged environment + /// + In = 0x1, + /// + /// Marshalling from an unmanaged environment to a managed environment + /// + Out = 0x2, + /// + /// Marshalling to and from managed and unmanaged environments + /// + Ref = In | Out, +} +``` + +The `NativeMarshallingAttribute` and `MarshalUsingAttribute` attributes would require that the provided native type `TNative` is a `struct` that does not require any marshalling and has the `CustomTypeMarshallerAttribute` with the first parameter being a `typeof()` of the managed type (with the managed type named TManaged in this example), an optional `CustomTypeMarshallerKind`, `CustomTypeMarshallerDirection`, and optional `CustomTypeMarshallerFeatures`: -public struct TMarshaler +```csharp +[CustomTypeMarshaller(typeof(TManaged), CustomTypeMarshallerKind.Value, Direction = CustomTypeMarshallerDirection.Ref, Features = CustomTypeMarshallerFeatures.None)] +partial struct TNative { - public TMarshaler(TManaged managed) {} + public TNative(TManaged managed) {} public TManaged ToManaged() {} +} +``` - public void FreeNative() {} - - public ref TNative GetPinnableReference() {} +If the attribute specifies the `Direction` is either `In` or `Ref`, then the example constructor above must be provided. If the attribute specifies the `Direction` is `Out` or `Ref`, then the `ToManaged` method must be provided. If the `Direction` property is unspecified, then it will be treated as if the user provided `CustomTypeMarshallerDirection.Ref`. The analyzer will report an error if the attribute provides `CustomTypeMarshallerDirection.None`, as a marshaller that supports no direction is unusable. - public TNative* Value { get; set; } -} +If the attribute provides the `CustomTypeMarshallerFeatures.UnmanagedResources` flag to the `Features` property then a `void`-returning parameterless instance method name `FreeNative` must be provided. This method can be used to release any non-managed resources used during marshalling. -``` +> :question: Does this API surface and shape work for all marshalling scenarios we plan on supporting? It may have issues with the current "layout class" by-value `[Out]` parameter marshalling where the runtime updates a `class` typed object in place. We already recommend against using classes for interop for performance reasons and a struct value passed via `ref` or `out` with the same members would cover this scenario. ### Performance features #### Pinning -Since C# 7.3 added a feature to enable custom pinning logic for user types, we should also add support for custom pinning logic. If the user provides a `GetPinnableReference` method on the managed type that matches the requirements to be used in a `fixed` statement and the pointed-to type would not require any additional marshalling, then we will support using pinning to marshal the managed value when possible. The analyzer should issue a warning when the pointed-to type would not match the final native type, accounting for the `Value` property on the native type. Since `MarshalUsingAttribute` is applied at usage time instead of at type authoring time, we will not enable the pinning feature since the implementation of `GetPinnableReference` is likely designed to match the default marshalling rules provided by the type author, not the rules provided by the marshaller provided by the `MarshalUsingAttribute`. +Since C# 7.3 added a feature to enable custom pinning logic for user types, we should also add support for custom pinning logic. If the user provides a `GetPinnableReference` method on the managed type that matches the requirements to be used in a `fixed` statement and the pointed-to type would not require any additional marshalling, then we will support using pinning to marshal the managed value when possible. The analyzer should issue a warning when the pointed-to type would not match the final native type, accounting for any optional features used by the custom marshaller type. Since `MarshalUsingAttribute` is applied at usage time instead of at type authoring time, we will not enable the pinning feature since the implementation of `GetPinnableReference` is likely designed to match the default marshalling rules provided by the type author, not the rules provided by the marshaller provided by the `MarshalUsingAttribute`. #### Caller-allocated memory -Custom marshalers of collection-like types or custom string encodings (such as UTF-32) may want to use stack space for extra storage for additional performance when possible. If the `TNative` type provides additional members with the following signatures, then it will opt in to using a caller-allocated buffer: +Custom marshalers of collection-like types or custom string encodings (such as UTF-32) may want to use stack space for extra storage for additional performance when possible. If the `[CustomTypeMarshaller]` attribute sets the `Features` property to value with the `CustomTypeMarshallerFeatures.CallerAllocatedBuffer` flag, then `TNative` type must provide additional constructor with the following signature and set the `BufferSize` field on the `CustomTypeMarshallerAttribute`. It will then be opted-in to using a caller-allocated buffer: ```csharp +[CustomTypeMarshaller(typeof(TManaged), BufferSize = /* */, Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer)] partial struct TNative { public TNative(TManaged managed, Span buffer) {} - - public const int BufferSize = /* */; - - public const bool RequiresStackBuffer = /* */; } ``` -When these members are present, the source generator will call the two-parameter constructor with a possibly stack-allocated buffer of `BufferSize` bytes when a stack-allocated buffer is usable. If a stack-allocated buffer is a requirement, the `RequiresStackBuffer` field should be set to `true` and the `buffer` will be guaranteed to be allocated on the stack. Setting the `RequiresStackBuffer` field to `false` is the same as omitting the field definition. Since a dynamically allocated buffer is not usable in all scenarios, for example Reverse P/Invoke and struct marshalling, a one-parameter constructor must also be provided for usage in those scenarios. This may also be provided by providing a two-parameter constructor with a default value for the second parameter. +When these `CallerAllocatedBuffer` feature flag is present, the source generator will call the two-parameter constructor with a possibly stack-allocated buffer of `BufferSize` bytes when a stack-allocated buffer is usable. Since a dynamically allocated buffer is not usable in all scenarios, for example Reverse P/Invoke and struct marshalling, a one-parameter constructor must also be provided for usage in those scenarios. -Type authors can pass down the `buffer` pointer to native code by defining a `Value` property that returns a pointer to the first element, generally through code using `MemoryMarshal.GetReference()` and `Unsafe.AsPointer`. If `RequiresStackBuffer` is not provided or set to `false`, the `buffer` span must be pinned to be used safely. The `buffer` span can be pinned by defining a `GetPinnableReference()` method on the native type that returns a reference to the first element of the span. +Type authors can pass down the `buffer` pointer to native code by using the `TwoStageMarshalling` feature to provide a `ToNativeValue` method that returns a pointer to the first element, generally through code using `MemoryMarshal.GetReference()` and `Unsafe.AsPointer`. The `buffer` span must be pinned to be used safely. The `buffer` span can be pinned by defining a `GetPinnableReference()` method on the native type that returns a reference to the first element of the span. ### Determining if a type is doesn't need marshalling @@ -238,7 +268,7 @@ All generated stubs will be marked with [`SkipLocalsInitAttribute`](https://docs ### Special case: Transparent Structures -There has been discussion about Transparent Structures, structure types that are treated as their underlying types when passed to native code. The support for a `Value` property on a generated marshalling type supports the transparent struct support. For example, we could support strongly typed `HRESULT` returns with this model as shown below: +There has been discussion about Transparent Structures, structure types that are treated as their underlying types when passed to native code. The source-generated model supports this design through the `TwoStageMarshalling` feature flag on the `CustomTypeMarshaller` attribute. ```csharp [NativeMarshalling(typeof(HRESULT))] @@ -251,46 +281,78 @@ struct HResult public readonly int Result; } +[CustomTypeMarshaller(typeof(HResult), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] struct HRESULT { + private HResult managed; public HRESULT(HResult hr) { - Value = hr; + managed = hr; } - public HResult ToManaged() => new HResult(Value); - public int Value { get; set; } + public HResult ToManaged() => managed; + public int ToNativeValue() => managed.Result; } ``` +For the more detailed specification, we will use the example below: + +```csharp +[NativeMarshalling(typeof(TMarshaller))] +public struct TManaged +{ + // ... +} + +[CustomTypeMarshaller(typeof(TManaged))] +public struct TMarshaller +{ + public TMarshaller(TManaged managed) {} + public TManaged ToManaged() {} + + public ref T GetPinnableReference() {} + + public unsafe TNative* ToNativeValue(); + public unsafe void FromNativeValue(TNative*); +} + +``` + In this case, the underlying native type would actually be an `int`, but the user could use the strongly-typed `HResult` type as the public surface area. -> :question: Should we support transparent structures on manually annotated types that wouldn't need marshalling otherwise? If we do, we should do so in an opt-in manner to make it possible to have a `Value` property on the type without assuming that it is for interop in all cases. +If a type `TMarshaller` with the `CustomTypeMarshaller` attribute specifies the `TwoStageMarshalling` feature, then it must provide the `ToNativeValue` feature if it supports the `In` direction, and the `FromNativeValue` method if it supports the `Out` direction. The return value of the `ToNativeValue` method will be passed to native code instead of the `TMarshaller` value itself. As a result, the type `TMarshaller` will be allowed to require marshalling and the return type of the `ToNativeValue` method, will be required be passable to native code without any additional marshalling. When marshalling in the native-to-managed direction, a default value of `TMarshaller` will have the `FromNativeValu` method called with the native value. If we are marshalling a scenario where a single frame covers the whole native call and we are marshalling in and out, then the same instance of the marshller will be reused. + +If the `TwoStageMarshalling` feature is specified, the developer may also provide a ref-returning or readonly-ref-returning `GetPinnableReference` method. The `GetPinnableReference` method will be called before the `ToNativeValue` method is called. The ref returned by `GetPinnableReference` will be pinned with a `fixed` statement, but the pinned value will not be used (it acts exclusively as a side-effect). As a result, `GetPinnableReference` can return a `ref` to any `T` that can be used in a fixed statement (a C# `unmanaged` type). + +A `ref` or `ref readonly` typed `ToNativeValue` method is unsupported. If a ref-return is required, the type author can supply a `GetPinnableReference` method on the native type to pin the desired `ref` to return and then use `System.Runtime.CompilerServices.Unsafe.AsPointer` to get a pointer from the `ref` that will have already been pinned by the time the `ToNativeValue` method is called. + +> :question: Should we support transparent structures on manually annotated types that wouldn't need marshalling otherwise? If we do, we should do so in an opt-in manner to make it possible to have a `ToNativeValue` method on the type without assuming that it is for interop in all cases. #### Example: ComWrappers marshalling with Transparent Structures Building on this Transparent Structures support, we can also support ComWrappers marshalling with this proposal via the manually-decorated types approach: ```csharp -[NativeMarshalling(typeof(ComWrappersMarshaler))] +[NativeMarshalling(typeof(ComWrappersMarshaler), Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling)] class Foo {} -struct ComWrappersMarshaler - where TComWrappers : ComWrappers, new() +struct FooComWrappersMarshaler { - private static readonly TComWrappers ComWrappers = new TComWrappers(); + private static readonly FooComWrappers ComWrappers = new FooComWrappers(); private IntPtr nativeObj; - public ComWrappersMarshaler(TClass obj) + public ComWrappersMarshaler(Foo obj) { nativeObj = ComWrappers.GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.None); } - public IntPtr Value { get => nativeObj; set => nativeObj = value; } + public IntPtr ToNativeValue() => nativeObj; + + public void FromNativeValue(IntPtr value) => nativeObj = value; - public TClass ToManaged() => (TClass)ComWrappers.GetOrCreateObjectForComInstance(nativeObj, CreateObjectFlags.None); + public Foo ToManaged() => (Foo)ComWrappers.GetOrCreateObjectForComInstance(nativeObj, CreateObjectFlags.None); public unsafe void FreeNative() { diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md index 4e2b859114d98..edd42ecefb5c0 100644 --- a/docs/project/list-of-diagnostics.md +++ b/docs/project/list-of-diagnostics.md @@ -158,18 +158,15 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL | __`SYSLIB1052`__ | Specified configuration is not supported by source-generated P/Invokes | | __`SYSLIB1053`__ | Current target framework is not supported by source-generated P/Invokes | | __`SYSLIB1054`__ | Specified LibraryImportAttribute arguments cannot be forwarded to DllImportAttribute | -| __`SYSLIB1055`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1056`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1057`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1058`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1059`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1060`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1061`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1062`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1063`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1064`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1065`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1066`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1067`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1068`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | -| __`SYSLIB1069`__ | *_`SYSLIB1055`-`SYSLIB1069` reserved for Microsoft.Interop.LibraryImportGenerator._* | +| __`SYSLIB1055`__ | *_`SYSLIB1055`-`SYSLIB1066` reserved for Microsoft.Interop.LibraryImportGenerator._* | +| __`SYSLIB1056`__ | *_`SYSLIB1055`-`SYSLIB1066` reserved for Microsoft.Interop.LibraryImportGenerator._* | +| __`SYSLIB1057`__ | *_`SYSLIB1055`-`SYSLIB1066` reserved for Microsoft.Interop.LibraryImportGenerator._* | +| __`SYSLIB1058`__ | *_`SYSLIB1055`-`SYSLIB1066` reserved for Microsoft.Interop.LibraryImportGenerator._* | +| __`SYSLIB1059`__ | *_`SYSLIB1055`-`SYSLIB1066` reserved for Microsoft.Interop.LibraryImportGenerator._* | +| __`SYSLIB1060`__ | *_`SYSLIB1055`-`SYSLIB1066` reserved for Microsoft.Interop.LibraryImportGenerator._* | +| __`SYSLIB1061`__ | *_`SYSLIB1055`-`SYSLIB1066` reserved for Microsoft.Interop.LibraryImportGenerator._* | +| __`SYSLIB1062`__ | *_`SYSLIB1055`-`SYSLIB1066` reserved for Microsoft.Interop.LibraryImportGenerator._* | +| __`SYSLIB1063`__ | *_`SYSLIB1055`-`SYSLIB1066` reserved for Microsoft.Interop.LibraryImportGenerator._* | +| __`SYSLIB1064`__ | *_`SYSLIB1055`-`SYSLIB1066` reserved for Microsoft.Interop.LibraryImportGenerator._* | +| __`SYSLIB1065`__ | *_`SYSLIB1055`-`SYSLIB1066` reserved for Microsoft.Interop.LibraryImportGenerator._* | +| __`SYSLIB1066`__ | *_`SYSLIB1055`-`SYSLIB1066` reserved for Microsoft.Interop.LibraryImportGenerator._* | diff --git a/eng/generators.targets b/eng/generators.targets index 24c5c2db8d8ac..4b55dfbaafe2a 100644 --- a/eng/generators.targets +++ b/eng/generators.targets @@ -52,7 +52,14 @@ Include="$(CoreLibSharedDir)System\Runtime\InteropServices\StringMarshalling.cs" /> - + + + + RhNewArray(pEEType.ToPointer(), length); - [LibraryImport(RuntimeLibrary)] - internal static unsafe partial void RhAllocateNewObject(IntPtr pEEType, uint flags, void* pResult); + [DllImport(RuntimeLibrary)] + internal static unsafe extern void RhAllocateNewObject(IntPtr pEEType, uint flags, void* pResult); [MethodImpl(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhpFallbackFailFast")] diff --git a/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj b/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj index 0cba6dfc1f5a2..9d53870e996a1 100644 --- a/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj +++ b/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj @@ -3,11 +3,6 @@ false false netstandard2.0 - true - - $(NoWarn);SYSLIB1053 FEATURE_GC_STRESS;$(DefineConstants) @@ -33,6 +28,9 @@ $(ArtifactsObjDir)\coreclr\$(TargetOS).$(TargetArchitecture).$(CoreCLRConfiguration) $(IntermediatesDir)\ide + + $([MSBuild]::NormalizeDirectory('$(LibrariesProjectRoot)', 'Common', 'src')) + Runtime.Base\src\System\Runtime\CachedInterfaceDispatch.cs @@ -67,9 +65,6 @@ Runtime.Base\src\System\Runtime\TypeCast.cs - - Runtime.Base\src\System\Runtime\InteropServices\MarshalAsAttribute.cs - Runtime.Base\src\System\Runtime\InteropServices\UnsafeGCHandle.cs @@ -82,6 +77,9 @@ Common\TransitionBlock.cs + + Common\Interop\Windows\Interop.BOOL.cs + diff --git a/src/libraries/Common/src/Interop/Interop.Ldap.cs b/src/libraries/Common/src/Interop/Interop.Ldap.cs index a48640d1e4619..423973b759d45 100644 --- a/src/libraries/Common/src/Interop/Interop.Ldap.cs +++ b/src/libraries/Common/src/Interop/Interop.Ldap.cs @@ -44,6 +44,9 @@ internal struct SEC_WINNT_AUTH_IDENTITY_EX public string packageList; public int packageListLength; +#if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(SEC_WINNT_AUTH_IDENTITY_EX), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.UnmanagedResources)] +#endif [StructLayout(LayoutKind.Sequential)] internal struct Native { @@ -173,6 +176,7 @@ internal sealed class BerVal public IntPtr bv_val = IntPtr.Zero; #if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(BerVal), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] internal unsafe struct PinningMarshaller { private readonly BerVal _managed; @@ -183,7 +187,7 @@ public PinningMarshaller(BerVal managed) public ref int GetPinnableReference() => ref (_managed is null ? ref Unsafe.NullRef() : ref _managed.bv_len); - public void* Value => Unsafe.AsPointer(ref GetPinnableReference()); + public void* ToNativeValue() => Unsafe.AsPointer(ref GetPinnableReference()); } #endif } @@ -211,6 +215,7 @@ internal struct LdapReferralCallback #if NET7_0_OR_GREATER public static readonly unsafe int Size = sizeof(Marshaller.Native); + [CustomTypeMarshaller(typeof(LdapReferralCallback), Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling)] public unsafe struct Marshaller { public unsafe struct Native @@ -234,11 +239,9 @@ public Marshaller(LdapReferralCallback managed) _native.dereference = managed.dereference is not null ? Marshal.GetFunctionPointerForDelegate(managed.dereference) : IntPtr.Zero; } - public Native Value - { - get => _native; - set => _native = value; - } + public Native ToNativeValue() => _native; + + public void FromNativeValue(Native value) => _native = value; public LdapReferralCallback ToManaged() { diff --git a/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.LsaLookupNames2.cs b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.LsaLookupNames2.cs index 50be738d91876..94dd8bc43608b 100644 --- a/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.LsaLookupNames2.cs +++ b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.LsaLookupNames2.cs @@ -26,6 +26,7 @@ internal struct MARSHALLED_UNICODE_STRING internal ushort MaximumLength; internal string Buffer; + [CustomTypeMarshaller(typeof(MARSHALLED_UNICODE_STRING), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.UnmanagedResources)] public struct Native { internal ushort Length; diff --git a/src/libraries/Common/src/Interop/Windows/CryptUI/Interop.CryptUIDlgCertificate.cs b/src/libraries/Common/src/Interop/Windows/CryptUI/Interop.CryptUIDlgCertificate.cs index 4bdd0880f5248..ce07eea1841ec 100644 --- a/src/libraries/Common/src/Interop/Windows/CryptUI/Interop.CryptUIDlgCertificate.cs +++ b/src/libraries/Common/src/Interop/Windows/CryptUI/Interop.CryptUIDlgCertificate.cs @@ -37,6 +37,7 @@ internal struct CRYPTUI_VIEWCERTIFICATE_STRUCTW internal uint nStartPage; #if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(CRYPTUI_VIEWCERTIFICATE_STRUCTW), Features = CustomTypeMarshallerFeatures.UnmanagedResources)] internal unsafe struct Native { private uint dwSize; @@ -139,6 +140,7 @@ internal struct CRYPTUI_SELECTCERTIFICATE_STRUCTW internal IntPtr hSelectedCertStore; #if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(CRYPTUI_SELECTCERTIFICATE_STRUCTW), Features = CustomTypeMarshallerFeatures.UnmanagedResources)] internal unsafe struct Native { private uint dwSize; diff --git a/src/libraries/Common/src/Interop/Windows/WebSocket/Interop.Structs.cs b/src/libraries/Common/src/Interop/Windows/WebSocket/Interop.Structs.cs index 26c0dd93fd3bc..a4219658a3a45 100644 --- a/src/libraries/Common/src/Interop/Windows/WebSocket/Interop.Structs.cs +++ b/src/libraries/Common/src/Interop/Windows/WebSocket/Interop.Structs.cs @@ -49,6 +49,7 @@ internal struct HttpHeader internal string Value; internal uint ValueLength; + [CustomTypeMarshaller(typeof(HttpHeader), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.UnmanagedResources)] internal struct Native { private IntPtr Name; diff --git a/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp.cs b/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp.cs index 7074ad6cdc01d..b7e40e77b996a 100644 --- a/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp.cs +++ b/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp.cs @@ -53,6 +53,7 @@ public static partial bool WinHttpAddRequestHeaders( uint modifiers); #if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(StringBuilder), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling)] private unsafe struct SimpleStringBufferMarshaller { public SimpleStringBufferMarshaller(StringBuilder builder) @@ -64,7 +65,9 @@ public SimpleStringBufferMarshaller(StringBuilder builder) builder.CopyTo(0, buffer, length - 1); } - public void* Value { get; } + private void* Value { get; } + + public void* ToNativeValue() => Value; public void FreeNative() { diff --git a/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp_types.cs b/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp_types.cs index 47f105b6c3fc1..0a0c24292ca9c 100644 --- a/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp_types.cs +++ b/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp_types.cs @@ -259,6 +259,7 @@ public struct WINHTTP_AUTOPROXY_OPTIONS [MarshalAs(UnmanagedType.Bool)] public bool AutoLoginIfChallenged; #if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(WINHTTP_AUTOPROXY_OPTIONS), Features = CustomTypeMarshallerFeatures.UnmanagedResources)] public struct Native { private uint Flags; diff --git a/src/libraries/Common/src/Interop/Windows/WinSock/Interop.WinsockBSD.cs b/src/libraries/Common/src/Interop/Windows/WinSock/Interop.WinsockBSD.cs index 28478031eb38f..b19cd8dba064d 100644 --- a/src/libraries/Common/src/Interop/Windows/WinSock/Interop.WinsockBSD.cs +++ b/src/libraries/Common/src/Interop/Windows/WinSock/Interop.WinsockBSD.cs @@ -74,6 +74,7 @@ internal struct IPv6MulticastRequest internal byte[] MulticastAddress; // IP address of group. internal int InterfaceIndex; // Local interface index. + [CustomTypeMarshaller(typeof(IPv6MulticastRequest))] public unsafe struct Native { private const int MulticastAddressLength = 16; diff --git a/src/libraries/Common/src/System/Runtime/InteropServices/ArrayMarshaller.cs b/src/libraries/Common/src/System/Runtime/InteropServices/ArrayMarshaller.cs index d28dba25b395d..f5c5277a7d373 100644 --- a/src/libraries/Common/src/System/Runtime/InteropServices/ArrayMarshaller.cs +++ b/src/libraries/Common/src/System/Runtime/InteropServices/ArrayMarshaller.cs @@ -12,6 +12,10 @@ namespace System.Runtime.InteropServices.GeneratedMarshalling { + // Stack-alloc threshold set to 256 bytes to enable small arrays to be passed on the stack. + // Number kept small to ensure that P/Invokes with a lot of array parameters doesn't + // blow the stack since this is a new optimization in the code-generated interop. + [CustomTypeMarshaller(typeof(CustomTypeMarshallerAttribute.GenericPlaceholder[]), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 0x200)] #if LIBRARYIMPORT_GENERATOR_TEST public #else @@ -58,44 +62,24 @@ public ArrayMarshaller(T[]? managed, Span stackSpace, int sizeOfNativeElem } } - /// - /// Stack-alloc threshold set to 256 bytes to enable small arrays to be passed on the stack. - /// Number kept small to ensure that P/Invokes with a lot of array parameters doesn't - /// blow the stack since this is a new optimization in the code-generated interop. - /// - public const int BufferSize = 0x200; - public const bool RequiresStackBuffer = true; + public ReadOnlySpan GetManagedValuesSource() => _managedArray; + public Span GetManagedValuesDestination(int length) => _allocatedMemory == IntPtr.Zero ? null : _managedArray = new T[length]; + public Span GetNativeValuesDestination() => NativeValueStorage; - public Span ManagedValues => _managedArray; - - public Span NativeValueStorage { get; private set; } + public ReadOnlySpan GetNativeValuesSource(int length) + { + return _allocatedMemory == IntPtr.Zero ? default : NativeValueStorage = new Span((void*)_allocatedMemory, length * _sizeOfNativeElement); + } + private Span NativeValueStorage { get; set; } public ref byte GetPinnableReference() => ref MemoryMarshal.GetReference(NativeValueStorage); - public void SetUnmarshalledCollectionLength(int length) - { - _managedArray = new T[length]; - } - public byte* Value + public byte* ToNativeValue() => (byte*)Unsafe.AsPointer(ref GetPinnableReference()); + + public void FromNativeValue(byte* value) { - get - { - return (byte*)Unsafe.AsPointer(ref GetPinnableReference()); - } - set - { - if (value == null) - { - _managedArray = null; - NativeValueStorage = default; - } - else - { - _allocatedMemory = (IntPtr)value; - NativeValueStorage = new Span(value, (_managedArray?.Length ?? 0) * _sizeOfNativeElement); - } - } + _allocatedMemory = (IntPtr)value; } public T[]? ToManaged() => _managedArray; @@ -106,6 +90,10 @@ public void FreeNative() } } + // Stack-alloc threshold set to 256 bytes to enable small arrays to be passed on the stack. + // Number kept small to ensure that P/Invokes with a lot of array parameters doesn't + // blow the stack since this is a new optimization in the code-generated interop. + [CustomTypeMarshaller(typeof(CustomTypeMarshallerAttribute.GenericPlaceholder*[]), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 0x200)] #if LIBRARYIMPORT_GENERATOR_TEST public #else @@ -152,45 +140,22 @@ public PtrArrayMarshaller(T*[]? managed, Span stackSpace, int sizeOfNative } } - /// - /// Stack-alloc threshold set to 256 bytes to enable small arrays to be passed on the stack. - /// Number kept small to ensure that P/Invokes with a lot of array parameters doesn't - /// blow the stack since this is a new optimization in the code-generated interop. - /// - public const int BufferSize = 0x200; - public const bool RequiresStackBuffer = true; - - public Span ManagedValues => Unsafe.As(_managedArray); + public ReadOnlySpan GetManagedValuesSource() => Unsafe.As(_managedArray); + public Span GetManagedValuesDestination(int length) => _allocatedMemory == IntPtr.Zero ? null : Unsafe.As(_managedArray = new T*[length]); + public Span GetNativeValuesDestination() => NativeValueStorage; - public Span NativeValueStorage { get; private set; } - - public ref byte GetPinnableReference() => ref MemoryMarshal.GetReference(NativeValueStorage); - - public void SetUnmarshalledCollectionLength(int length) + public ReadOnlySpan GetNativeValuesSource(int length) { - _managedArray = new T*[length]; + return _allocatedMemory == IntPtr.Zero ? default : NativeValueStorage = new Span((void*)_allocatedMemory, length * _sizeOfNativeElement); } + private Span NativeValueStorage { get; set; } - public byte* Value - { - get - { - return (byte*)Unsafe.AsPointer(ref GetPinnableReference()); - } - set - { - if (value == null) - { - _managedArray = null; - NativeValueStorage = default; - } - else - { - _allocatedMemory = (IntPtr)value; - NativeValueStorage = new Span(value, (_managedArray?.Length ?? 0) * _sizeOfNativeElement); - } + public ref byte GetPinnableReference() => ref MemoryMarshal.GetReference(NativeValueStorage); + public byte* ToNativeValue() => (byte*)Unsafe.AsPointer(ref GetPinnableReference()); - } + public void FromNativeValue(byte* value) + { + _allocatedMemory = (IntPtr)value; } public T*[]? ToManaged() => _managedArray; diff --git a/src/libraries/Common/src/System/Runtime/InteropServices/CustomTypeMarshallerDirection.cs b/src/libraries/Common/src/System/Runtime/InteropServices/CustomTypeMarshallerDirection.cs new file mode 100644 index 0000000000000..0ee347d72b96e --- /dev/null +++ b/src/libraries/Common/src/System/Runtime/InteropServices/CustomTypeMarshallerDirection.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; + +namespace System.Runtime.InteropServices +{ + /// + /// A direction of marshalling data into or out of the managed environment + /// + [Flags] +#if LIBRARYIMPORT_GENERATOR_TEST + public +#else + internal +#endif + enum CustomTypeMarshallerDirection + { + /// + /// No marshalling direction + /// + [EditorBrowsable(EditorBrowsableState.Never)] + None = 0, + /// + /// Marshalling from a managed environment to an unmanaged environment + /// + In = 0x1, + /// + /// Marshalling from an unmanaged environment to a managed environment + /// + Out = 0x2, + /// + /// Marshalling to and from managed and unmanaged environments + /// + Ref = In | Out, + } +} diff --git a/src/libraries/Common/src/System/Runtime/InteropServices/CustomTypeMarshallerFeatures.cs b/src/libraries/Common/src/System/Runtime/InteropServices/CustomTypeMarshallerFeatures.cs new file mode 100644 index 0000000000000..fe58f386f72f3 --- /dev/null +++ b/src/libraries/Common/src/System/Runtime/InteropServices/CustomTypeMarshallerFeatures.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.InteropServices +{ + /// + /// Optional features supported by custom type marshallers. + /// + [Flags] +#if LIBRARYIMPORT_GENERATOR_TEST + public +#else + internal +#endif + enum CustomTypeMarshallerFeatures + { + /// + /// No optional features supported + /// + None = 0, + /// + /// The marshaller owns unmanaged resources that must be freed + /// + UnmanagedResources = 0x1, + /// + /// The marshaller can use a caller-allocated buffer instead of allocating in some scenarios + /// + CallerAllocatedBuffer = 0x2, + /// + /// The marshaller uses the two-stage marshalling design for its instead of the one-stage design. + /// + TwoStageMarshalling = 0x4 + } +} diff --git a/src/libraries/Common/src/System/Runtime/InteropServices/CustomTypeMarshallerKind.cs b/src/libraries/Common/src/System/Runtime/InteropServices/CustomTypeMarshallerKind.cs new file mode 100644 index 0000000000000..06c73cdf7b608 --- /dev/null +++ b/src/libraries/Common/src/System/Runtime/InteropServices/CustomTypeMarshallerKind.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.InteropServices +{ + /// + /// The shape of a custom type marshaller for usage in source-generated interop scenarios. + /// + /// + /// + /// +#if LIBRARYIMPORT_GENERATOR_TEST + public +#else + internal +#endif + enum CustomTypeMarshallerKind + { + /// + /// This custom type marshaller represents a single value. + /// + Value, + /// + /// This custom type marshaller represents a container of values that are placed sequentially in memory. + /// + LinearCollection + } +} diff --git a/src/libraries/Common/src/System/Runtime/InteropServices/GeneratedMarshallingAttribute.cs b/src/libraries/Common/src/System/Runtime/InteropServices/GeneratedMarshallingAttribute.cs index 1ea2799beaaab..4a0aaf99e23e2 100644 --- a/src/libraries/Common/src/System/Runtime/InteropServices/GeneratedMarshallingAttribute.cs +++ b/src/libraries/Common/src/System/Runtime/InteropServices/GeneratedMarshallingAttribute.cs @@ -8,17 +8,7 @@ // namespace System.Runtime.InteropServices { - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] -#if LIBRARYIMPORT_GENERATOR_TEST - public -#else - internal -#endif - sealed class GeneratedMarshallingAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class)] + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Delegate)] #if LIBRARYIMPORT_GENERATOR_TEST public #else @@ -34,7 +24,7 @@ public NativeMarshallingAttribute(Type nativeType) public Type NativeType { get; } } - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.Field, AllowMultiple = true)] + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true)] #if LIBRARYIMPORT_GENERATOR_TEST public #else @@ -59,20 +49,64 @@ public MarshalUsingAttribute(Type nativeType) public int ConstantElementCount { get; set; } - public int ElementIndirectionLevel { get; set; } + public int ElementIndirectionDepth { get; set; } public const string ReturnsCountValue = "return-value"; } - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class)] + /// + /// Attribute used to indicate that the type can be used to convert a value of the provided to a native representation. + /// + /// + /// This attribute is recognized by the runtime-provided source generators for source-generated interop scenarios. + /// It is not used by the interop marshalling system at runtime. + /// + /// + [AttributeUsage(AttributeTargets.Struct)] #if LIBRARYIMPORT_GENERATOR_TEST public #else internal #endif - sealed class GenericContiguousCollectionMarshallerAttribute : Attribute + sealed class CustomTypeMarshallerAttribute : Attribute { - public GenericContiguousCollectionMarshallerAttribute() + public CustomTypeMarshallerAttribute(Type managedType, CustomTypeMarshallerKind marshallerKind = CustomTypeMarshallerKind.Value) + { + ManagedType = managedType; + MarshallerKind = marshallerKind; + } + + /// + /// The managed type for which the attributed type is a marshaller + /// + public Type ManagedType { get; } + + /// + /// The required shape of the attributed type + /// + public CustomTypeMarshallerKind MarshallerKind { get; } + + /// + /// When the flag is set on the size of the caller-allocated buffer in number of elements. + /// + public int BufferSize { get; set; } + + /// + /// The marshalling directions this custom type marshaller supports. + /// + /// Default is + public CustomTypeMarshallerDirection Direction { get; set; } = CustomTypeMarshallerDirection.Ref; + + /// + /// The optional features for the that the marshaller supports. + /// + public CustomTypeMarshallerFeatures Features { get; set; } + + /// + /// This type is used as a placeholder for the first generic parameter when generic parameters cannot be used + /// to identify the managed type (i.e. when the marshaller type is generic over T and the managed type is T[]) + /// + public struct GenericPlaceholder { } } diff --git a/src/libraries/Common/src/System/Runtime/InteropServices/HandleRefMarshaller.cs b/src/libraries/Common/src/System/Runtime/InteropServices/HandleRefMarshaller.cs index 5278e31ae0e2c..d5b0a308bc6da 100644 --- a/src/libraries/Common/src/System/Runtime/InteropServices/HandleRefMarshaller.cs +++ b/src/libraries/Common/src/System/Runtime/InteropServices/HandleRefMarshaller.cs @@ -6,6 +6,7 @@ namespace System.Runtime.InteropServices.GeneratedMarshalling { + [CustomTypeMarshaller(typeof(HandleRef), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling)] internal struct HandleRefMarshaller { private HandleRef _handle; @@ -15,7 +16,7 @@ public HandleRefMarshaller(HandleRef handle) _handle = handle; } - public IntPtr Value => _handle.Handle; + public IntPtr ToNativeValue() => _handle.Handle; public void FreeNative() => GC.KeepAlive(_handle.Wrapper); } diff --git a/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/UnsafeNativeMethods.cs b/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/UnsafeNativeMethods.cs index a9b92a3bca47d..a4d40ab0cc44d 100644 --- a/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/UnsafeNativeMethods.cs +++ b/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/UnsafeNativeMethods.cs @@ -353,6 +353,7 @@ internal struct EvtRpcLogin public CoTaskMemUnicodeSafeHandle Password; public int Flags; #if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(EvtRpcLogin), Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling)] public struct Marshaller { public struct Native @@ -380,18 +381,16 @@ public Marshaller(EvtRpcLogin managed) _value.Flags = managed.Flags; } - public Native Value + public Native ToNativeValue() => _value; + + public void FromNativeValue(Native value) { - get => _value; - set + // SafeHandle fields cannot change the underlying handle value during marshalling. + if (_value.Password != value.Password) { - // SafeHandle fields cannot change the underlying handle value during marshalling. - if (_value.Password != value.Password) - { - throw new InvalidOperationException(); - } - _value = value; + throw new InvalidOperationException(); } + _value = value; } public EvtRpcLogin ToManaged() @@ -701,6 +700,7 @@ internal struct EvtStringVariant public uint Type; #if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(EvtStringVariant), Features = CustomTypeMarshallerFeatures.UnmanagedResources)] [StructLayout(LayoutKind.Explicit)] public struct Native { diff --git a/src/libraries/System.Drawing.Common/src/Interop/Windows/Interop.Gdi32.cs b/src/libraries/System.Drawing.Common/src/Interop/Windows/Interop.Gdi32.cs index 545856dad969b..0e01c1e57e29d 100644 --- a/src/libraries/System.Drawing.Common/src/Interop/Windows/Interop.Gdi32.cs +++ b/src/libraries/System.Drawing.Common/src/Interop/Windows/Interop.Gdi32.cs @@ -188,6 +188,7 @@ internal sealed class DOCINFO internal int fwType; #if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(DOCINFO), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.UnmanagedResources)] internal struct Native { internal int cbSize; diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/BitmapData.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/BitmapData.cs index c5a728914bcfe..4817da34d12e7 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/BitmapData.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/BitmapData.cs @@ -98,6 +98,7 @@ public int Reserved internal ref int GetPinnableReference() => ref _width; #if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(BitmapData), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] internal unsafe struct PinningMarshaller { private readonly BitmapData _managed; @@ -108,7 +109,7 @@ public PinningMarshaller(BitmapData managed) public ref int GetPinnableReference() => ref (_managed is null ? ref Unsafe.NullRef() : ref _managed.GetPinnableReference()); - public void* Value => Unsafe.AsPointer(ref GetPinnableReference()); + public void* ToNativeValue() => Unsafe.AsPointer(ref GetPinnableReference()); } #endif } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/ColorMatrix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/ColorMatrix.cs index 707ff68721669..336d6c68a0f93 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/ColorMatrix.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/ColorMatrix.cs @@ -396,6 +396,7 @@ internal float[][] GetMatrix() internal ref float GetPinnableReference() => ref _matrix00; #if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(ColorMatrix), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] internal unsafe struct PinningMarshaller { private readonly ColorMatrix _managed; @@ -406,7 +407,7 @@ public PinningMarshaller(ColorMatrix managed) public ref float GetPinnableReference() => ref (_managed is null ? ref Unsafe.NullRef() : ref _managed.GetPinnableReference()); - public void* Value => Unsafe.AsPointer(ref GetPinnableReference()); + public void* ToNativeValue() => Unsafe.AsPointer(ref GetPinnableReference()); } #endif } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/MetafileHeaderEmf.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/MetafileHeaderEmf.cs index dfce8751ec4d2..55575087df76e 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/MetafileHeaderEmf.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/MetafileHeaderEmf.cs @@ -34,6 +34,7 @@ internal sealed class MetafileHeaderEmf internal ref byte GetPinnableReference() => ref Unsafe.As(ref type); #if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(MetafileHeaderEmf), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] internal unsafe struct PinningMarshaller { private readonly MetafileHeaderEmf _managed; @@ -44,7 +45,7 @@ public PinningMarshaller(MetafileHeaderEmf managed) public ref byte GetPinnableReference() => ref (_managed is null ? ref Unsafe.NullRef() : ref _managed.GetPinnableReference()); - public void* Value => Unsafe.AsPointer(ref GetPinnableReference()); + public void* ToNativeValue() => Unsafe.AsPointer(ref GetPinnableReference()); } #endif } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/MetafileHeaderWmf.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/MetafileHeaderWmf.cs index 6825f2d007b1e..3295b5ca55434 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/MetafileHeaderWmf.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/MetafileHeaderWmf.cs @@ -55,6 +55,7 @@ internal sealed class MetafileHeaderWmf public int LogicalDpiY; #if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(MetafileHeaderWmf), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] internal unsafe struct InPlaceMarshaller { [StructLayout(LayoutKind.Sequential, Pack = 8)] @@ -120,11 +121,9 @@ public InPlaceMarshaller(MetafileHeaderWmf managed) _native.LogicalDpiY = managed.LogicalDpiY; } - public Native Value - { - get => _native; - set => _native = value; - } + public Native ToNativeValue() => _native; + + public void FromNativeValue(Native value) => _native = value; public MetafileHeaderWmf ToManaged() { diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/WmfPlaceableFileHeader.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/WmfPlaceableFileHeader.cs index ab26edfdb1a48..1f7d7cdfd70bb 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/WmfPlaceableFileHeader.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/WmfPlaceableFileHeader.cs @@ -106,6 +106,7 @@ public short Checksum internal ref int GetPinnableReference() => ref _key; #if NET7_0_OR_GREATER + [CustomTypeMarshaller(typeof(WmfPlaceableFileHeader), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] internal unsafe struct PinningMarshaller { private readonly WmfPlaceableFileHeader _managed; @@ -116,7 +117,7 @@ public PinningMarshaller(WmfPlaceableFileHeader managed) public ref int GetPinnableReference() => ref (_managed is null ? ref Unsafe.NullRef() : ref _managed.GetPinnableReference()); - public void* Value => Unsafe.AsPointer(ref GetPinnableReference()); + public void* ToNativeValue() => Unsafe.AsPointer(ref GetPinnableReference()); } #endif } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicNativeMethods.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicNativeMethods.cs index 7b894cb803f04..f19e664fa21e9 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicNativeMethods.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicNativeMethods.cs @@ -97,6 +97,7 @@ internal struct RegistrationConfig internal string AppName; internal QUIC_EXECUTION_PROFILE ExecutionProfile; + [CustomTypeMarshaller(typeof(RegistrationConfig), Features = CustomTypeMarshallerFeatures.UnmanagedResources)] [StructLayout(LayoutKind.Sequential)] public struct Native { @@ -250,6 +251,7 @@ internal struct CredentialConfig // TODO: define delegate for AsyncHandler and make proper use of it. internal IntPtr AsyncHandler; + [CustomTypeMarshaller(typeof(CredentialConfig), Features = CustomTypeMarshallerFeatures.UnmanagedResources)] [StructLayout(LayoutKind.Sequential)] public struct Native { diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/AnalyzerDiagnostics.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/AnalyzerDiagnostics.cs index e22c7799ce01a..e365435b742e4 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/AnalyzerDiagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/AnalyzerDiagnostics.cs @@ -14,25 +14,21 @@ public static class Ids { public const string Prefix = "DLLIMPORTGENANALYZER"; + // Migration from DllImport to LibraryImport + public const string ConvertToLibraryImport = Prefix + "001"; + // ManualTypeMarshalling - public const string BlittableTypeMustBeBlittable = Prefix + "001"; - public const string CannotHaveMultipleMarshallingAttributes = Prefix + "002"; - public const string NativeTypeMustBeNonNull = Prefix + "003"; - public const string NativeTypeMustBeBlittable = Prefix + "004"; + public const string MarshallerTypeMustSpecifyManagedType = Prefix + "002"; + public const string CustomTypeMarshallerAttributeMustBeValid = Prefix + "003"; + public const string InvalidNativeType = Prefix + "004"; public const string GetPinnableReferenceReturnTypeBlittable = Prefix + "005"; - public const string NativeTypeMustBePointerSized = Prefix + "006"; - public const string NativeTypeMustHaveRequiredShape = Prefix + "007"; - public const string ValuePropertyMustHaveSetter = Prefix + "008"; - public const string ValuePropertyMustHaveGetter = Prefix + "009"; - public const string GetPinnableReferenceShouldSupportAllocatingMarshallingFallback = Prefix + "010"; - public const string CallerAllocMarshallingShouldSupportAllocatingMarshallingFallback = Prefix + "011"; - public const string CallerAllocConstructorMustHaveStackBufferSizeConstant = Prefix + "012"; - public const string RefValuePropertyUnsupported = Prefix + "014"; - public const string NativeGenericTypeMustBeClosedOrMatchArity = Prefix + "016"; - public const string MarshallerGetPinnableReferenceRequiresValueProperty = Prefix + "018"; - - // Migration from DllImport to LibraryImport - public const string ConvertToLibraryImport = Prefix + "015"; + public const string CustomMarshallerTypeMustHaveRequiredShape = Prefix + "006"; + public const string CustomMarshallerTypeMustSupportDirection = Prefix + "007"; + public const string ProvidedMethodsNotSpecifiedInShape = Prefix + "008"; + public const string MissingAllocatingMarshallingFallback = Prefix + "009"; + public const string CallerAllocConstructorMustHaveBufferSize = Prefix + "010"; + public const string InvalidSignaturesInMarshallerShape = Prefix + "011"; + public const string MarshallerGetPinnableReferenceRequiresTwoStageMarshalling = Prefix + "012"; } internal static LocalizableResourceString GetResourceString(string resourceName) diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/CustomTypeMarshallerAnalyzer.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/CustomTypeMarshallerAnalyzer.cs new file mode 100644 index 0000000000000..d9889c941c838 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/CustomTypeMarshallerAnalyzer.cs @@ -0,0 +1,909 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +using static Microsoft.Interop.Analyzers.AnalyzerDiagnostics; + +namespace Microsoft.Interop.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public class CustomTypeMarshallerAnalyzer : DiagnosticAnalyzer + { + private const string Category = "Usage"; + + public const string MissingFeaturesKey = nameof(MissingFeaturesKey); + + public static class MissingMemberNames + { + public const string Key = nameof(MissingMemberNames); + public const char Delimiter = ' '; + + public const string ValueManagedToNativeConstructor = nameof(ValueManagedToNativeConstructor); + public const string ValueCallerAllocatedBufferConstructor = nameof(ValueCallerAllocatedBufferConstructor); + public const string CollectionManagedToNativeConstructor = nameof(CollectionManagedToNativeConstructor); + public const string CollectionCallerAllocatedBufferConstructor = nameof(CollectionCallerAllocatedBufferConstructor); + public const string CollectionNativeElementSizeConstructor = nameof(CollectionNativeElementSizeConstructor); + } + + public static readonly DiagnosticDescriptor MarshallerTypeMustSpecifyManagedTypeRule = + new DiagnosticDescriptor( + Ids.MarshallerTypeMustSpecifyManagedType, + "MarshallerTypeMustSpecifyManagedType", + GetResourceString(nameof(Resources.MarshallerTypeMustSpecifyManagedTypeMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.MarshallerTypeMustSpecifyManagedTypeDescription))); + + public static readonly DiagnosticDescriptor CustomTypeMarshallerAttributeMustBeValidRule = + new DiagnosticDescriptor( + Ids.CustomTypeMarshallerAttributeMustBeValid, + "CustomTypeMarshallerAttributeMustBeValid", + GetResourceString(nameof(Resources.CustomTypeMarshallerAttributeMustBeValidMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.CustomTypeMarshallerAttributeMustBeValidDescription))); + + public static readonly DiagnosticDescriptor MarshallerKindMustBeValidRule = + new DiagnosticDescriptor( + Ids.CustomTypeMarshallerAttributeMustBeValid, + "CustomTypeMarshallerAttributeMustBeValid", + GetResourceString(nameof(Resources.MarshallerKindMustBeValidMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.MarshallerKindMustBeValidDescription))); + + public static readonly DiagnosticDescriptor MarshallerDirectionMustBeValidRule = + new DiagnosticDescriptor( + Ids.CustomTypeMarshallerAttributeMustBeValid, + "CustomTypeMarshallerAttributeMustBeValid", + GetResourceString(nameof(Resources.MarshallerDirectionMustBeValidMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.MarshallerDirectionMustBeValidDescription))); + + public static readonly DiagnosticDescriptor NativeTypeMustHaveCustomTypeMarshallerAttributeRule = + new DiagnosticDescriptor( + Ids.InvalidNativeType, + "InvalidNativeType", + GetResourceString(nameof(Resources.NativeTypeMustHaveCustomTypeMarshallerAttributeMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.NativeTypeMustHaveCustomTypeMarshallerAttributeDescription))); + + public static readonly DiagnosticDescriptor NativeTypeMustBeBlittableRule = + new DiagnosticDescriptor( + Ids.InvalidNativeType, + "InvalidNativeType", + GetResourceString(nameof(Resources.NativeTypeMustBeBlittableMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.NativeTypeMustBeBlittableDescription))); + + public static readonly DiagnosticDescriptor GetPinnableReferenceReturnTypeBlittableRule = + new DiagnosticDescriptor( + Ids.GetPinnableReferenceReturnTypeBlittable, + "GetPinnableReferenceReturnTypeBlittable", + GetResourceString(nameof(Resources.GetPinnableReferenceReturnTypeBlittableMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.GetPinnableReferenceReturnTypeBlittableDescription))); + + public static readonly DiagnosticDescriptor NativeTypeMustBePointerSizedRule = + new DiagnosticDescriptor( + Ids.InvalidNativeType, + "InvalidNativeType", + GetResourceString(nameof(Resources.NativeTypeMustBePointerSizedMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.NativeTypeMustBePointerSizedDescription))); + + public static readonly DiagnosticDescriptor CustomMarshallerTypeMustSupportDirectionRule = + new DiagnosticDescriptor( + Ids.CustomMarshallerTypeMustSupportDirection, + "CustomMarshallerTypeMustSupportDirection", + GetResourceString(nameof(Resources.CustomMarshallerTypeMustSupportDirectionMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.CustomMarshallerTypeMustSupportDirectionDescription))); + + public static readonly DiagnosticDescriptor ValueInRequiresOneParameterConstructorRule = + new DiagnosticDescriptor( + Ids.CustomMarshallerTypeMustHaveRequiredShape, + "CustomMarshallerTypeMustHaveRequiredShape", + GetResourceString(nameof(Resources.ValueInRequiresOneParameterConstructorMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.ValueInRequiresOneParameterConstructorDescription))); + + public static readonly DiagnosticDescriptor LinearCollectionInRequiresTwoParameterConstructorRule = + new DiagnosticDescriptor( + Ids.CustomMarshallerTypeMustHaveRequiredShape, + "CustomMarshallerTypeMustHaveRequiredShape", + GetResourceString(nameof(Resources.LinearCollectionInRequiresTwoParameterConstructorMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.LinearCollectionInRequiresTwoParameterConstructorDescription))); + + public static readonly DiagnosticDescriptor ValueInCallerAllocatedBufferRequiresSpanConstructorRule = + new DiagnosticDescriptor( + Ids.CustomMarshallerTypeMustHaveRequiredShape, + "CustomMarshallerTypeMustHaveRequiredShape", + GetResourceString(nameof(Resources.ValueInCallerAllocatedBufferRequiresSpanConstructorMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.ValueInCallerAllocatedBufferRequiresSpanConstructorDescription))); + + public static readonly DiagnosticDescriptor LinearCollectionInCallerAllocatedBufferRequiresSpanConstructorRule = + new DiagnosticDescriptor( + Ids.CustomMarshallerTypeMustHaveRequiredShape, + "CustomMarshallerTypeMustHaveRequiredShape", + GetResourceString(nameof(Resources.LinearCollectionInCallerAllocatedBufferRequiresSpanConstructorMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.LinearCollectionInCallerAllocatedBufferRequiresSpanConstructorDescription))); + + public static readonly DiagnosticDescriptor OutRequiresToManagedRule = + new DiagnosticDescriptor( + Ids.CustomMarshallerTypeMustHaveRequiredShape, + "CustomMarshallerTypeMustHaveRequiredShape", + GetResourceString(nameof(Resources.OutRequiresToManagedMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.OutRequiresToManagedDescription))); + + public static readonly DiagnosticDescriptor LinearCollectionInRequiresCollectionMethodsRule = + new DiagnosticDescriptor( + Ids.CustomMarshallerTypeMustHaveRequiredShape, + "CustomMarshallerTypeMustHaveRequiredShape", + GetResourceString(nameof(Resources.LinearCollectionInRequiresCollectionMethodsMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.LinearCollectionInRequiresCollectionMethodsDescription))); + + public static readonly DiagnosticDescriptor LinearCollectionOutRequiresCollectionMethodsRule = + new DiagnosticDescriptor( + Ids.CustomMarshallerTypeMustHaveRequiredShape, + "CustomMarshallerTypeMustHaveRequiredShape", + GetResourceString(nameof(Resources.LinearCollectionOutRequiresCollectionMethodsMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.LinearCollectionOutRequiresCollectionMethodsDescription))); + + public static readonly DiagnosticDescriptor LinearCollectionOutRequiresIntConstructorRule = + new DiagnosticDescriptor( + Ids.CustomMarshallerTypeMustHaveRequiredShape, + "CustomMarshallerTypeMustHaveRequiredShape", + GetResourceString(nameof(Resources.LinearCollectionOutRequiresIntConstructorMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.LinearCollectionOutRequiresIntConstructorDescription))); + + public static readonly DiagnosticDescriptor UnmanagedResourcesRequiresFreeNativeRule = + new DiagnosticDescriptor( + Ids.CustomMarshallerTypeMustHaveRequiredShape, + "CustomMarshallerTypeMustHaveRequiredShape", + GetResourceString(nameof(Resources.UnmanagedResourcesRequiresFreeNativeMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.UnmanagedResourcesRequiresFreeNativeDescription))); + + public static readonly DiagnosticDescriptor OutTwoStageMarshallingRequiresFromNativeValueRule = + new DiagnosticDescriptor( + Ids.CustomMarshallerTypeMustHaveRequiredShape, + "CustomMarshallerTypeMustHaveRequiredShape", + GetResourceString(nameof(Resources.OutTwoStageMarshallingRequiresFromNativeValueMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.OutTwoStageMarshallingRequiresFromNativeValueDescription))); + + public static readonly DiagnosticDescriptor InTwoStageMarshallingRequiresToNativeValueRule = + new DiagnosticDescriptor( + Ids.CustomMarshallerTypeMustHaveRequiredShape, + "CustomMarshallerTypeMustHaveRequiredShape", + GetResourceString(nameof(Resources.InTwoStageMarshallingRequiresToNativeValueMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.InTwoStageMarshallingRequiresToNativeValueDescription))); + + public static readonly DiagnosticDescriptor GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule = + new DiagnosticDescriptor( + Ids.MissingAllocatingMarshallingFallback, + "GetPinnableReferenceShouldSupportAllocatingMarshallingFallback", + GetResourceString(nameof(Resources.GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription))); + + public static readonly DiagnosticDescriptor CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackRule = + new DiagnosticDescriptor( + Ids.MissingAllocatingMarshallingFallback, + "CallerAllocMarshallingShouldSupportAllocatingMarshallingFallback", + GetResourceString(nameof(Resources.CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackMessage)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackDescription))); + + public static readonly DiagnosticDescriptor CallerAllocConstructorMustHaveBufferSizeRule = + new DiagnosticDescriptor( + Ids.CallerAllocConstructorMustHaveBufferSize, + "CallerAllocConstructorMustHaveBufferSize", + GetResourceString(nameof(Resources.CallerAllocConstructorMustHaveBufferSizeMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.CallerAllocConstructorMustHaveBufferSizeDescription))); + + public static readonly DiagnosticDescriptor RefNativeValueUnsupportedRule = + new DiagnosticDescriptor( + Ids.InvalidSignaturesInMarshallerShape, + "InvalidSignaturesInMarshallerShape", + GetResourceString(nameof(Resources.RefNativeValueUnsupportedMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.RefNativeValueUnsupportedDescription))); + + public static readonly DiagnosticDescriptor NativeGenericTypeMustBeClosedOrMatchArityRule = + new DiagnosticDescriptor( + Ids.InvalidNativeType, + "NativeGenericTypeMustBeClosedOrMatchArity", + GetResourceString(nameof(Resources.NativeGenericTypeMustBeClosedOrMatchArityMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.NativeGenericTypeMustBeClosedOrMatchArityDescription))); + + public static readonly DiagnosticDescriptor MarshallerGetPinnableReferenceRequiresTwoStageMarshallingRule = + new DiagnosticDescriptor( + Ids.MarshallerGetPinnableReferenceRequiresTwoStageMarshalling, + "MarshallerGetPinnableReferenceRequiresTwoStageMarshalling", + GetResourceString(nameof(Resources.MarshallerGetPinnableReferenceRequiresTwoStageMarshallingMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.MarshallerGetPinnableReferenceRequiresTwoStageMarshallingDescription))); + + public static readonly DiagnosticDescriptor FreeNativeMethodProvidedShouldSpecifyUnmanagedResourcesFeatureRule = + new DiagnosticDescriptor( + Ids.ProvidedMethodsNotSpecifiedInShape, + "ProvidedMethodsNotSpecifiedInShape", + GetResourceString(nameof(Resources.FreeNativeMethodProvidedShouldSpecifyUnmanagedResourcesFeatureMessage)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.FreeNativeMethodProvidedShouldSpecifyUnmanagedResourcesFeatureDescription))); + + public static readonly DiagnosticDescriptor ToNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureRule = + new DiagnosticDescriptor( + Ids.ProvidedMethodsNotSpecifiedInShape, + "ProvidedMethodsNotSpecifiedInShape", + GetResourceString(nameof(Resources.ToNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureMessage)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.ToNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureDescription))); + + public static readonly DiagnosticDescriptor FromNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureRule = + new DiagnosticDescriptor( + Ids.ProvidedMethodsNotSpecifiedInShape, + "ProvidedMethodsNotSpecifiedInShape", + GetResourceString(nameof(Resources.FromNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureMessage)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.FromNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureDescription))); + + public static readonly DiagnosticDescriptor CallerAllocatedBufferConstructorProvidedShouldSpecifyFeatureRule = + new DiagnosticDescriptor( + Ids.ProvidedMethodsNotSpecifiedInShape, + "ProvidedMethodsNotSpecifiedInShape", + GetResourceString(nameof(Resources.CallerAllocatedBufferConstructorProvidedShouldSpecifyFeatureMessage)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.CallerAllocatedBufferConstructorProvidedShouldSpecifyFeatureDescription))); + + public static readonly DiagnosticDescriptor TwoStageMarshallingNativeTypesMustMatchRule = + new DiagnosticDescriptor( + Ids.InvalidSignaturesInMarshallerShape, + "InvalidSignaturesInMarshallerShape", + GetResourceString(nameof(Resources.TwoStageMarshallingNativeTypesMustMatchMessage)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.TwoStageMarshallingNativeTypesMustMatchDescription))); + + public static readonly DiagnosticDescriptor LinearCollectionElementTypesMustMatchRule = + new DiagnosticDescriptor( + Ids.InvalidSignaturesInMarshallerShape, + "InvalidSignaturesInMarshallerShape", + GetResourceString(nameof(Resources.LinearCollectionElementTypesMustMatchMessage)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.LinearCollectionElementTypesMustMatchDescription))); + + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create( + MarshallerTypeMustSpecifyManagedTypeRule, + CustomTypeMarshallerAttributeMustBeValidRule, + MarshallerKindMustBeValidRule, + MarshallerDirectionMustBeValidRule, + NativeTypeMustHaveCustomTypeMarshallerAttributeRule, + NativeTypeMustBeBlittableRule, + GetPinnableReferenceReturnTypeBlittableRule, + NativeTypeMustBePointerSizedRule, + ValueInRequiresOneParameterConstructorRule, + LinearCollectionInRequiresTwoParameterConstructorRule, + OutRequiresToManagedRule, + LinearCollectionInRequiresCollectionMethodsRule, + LinearCollectionOutRequiresCollectionMethodsRule, + LinearCollectionOutRequiresIntConstructorRule, + CustomMarshallerTypeMustSupportDirectionRule, + OutTwoStageMarshallingRequiresFromNativeValueRule, + InTwoStageMarshallingRequiresToNativeValueRule, + GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule, + CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackRule, + CallerAllocConstructorMustHaveBufferSizeRule, + RefNativeValueUnsupportedRule, + NativeGenericTypeMustBeClosedOrMatchArityRule, + MarshallerGetPinnableReferenceRequiresTwoStageMarshallingRule, + FreeNativeMethodProvidedShouldSpecifyUnmanagedResourcesFeatureRule, + ToNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureRule, + FromNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureRule, + CallerAllocatedBufferConstructorProvidedShouldSpecifyFeatureRule, + TwoStageMarshallingNativeTypesMustMatchRule, + LinearCollectionElementTypesMustMatchRule); + + public override void Initialize(AnalysisContext context) + { + // Don't analyze generated code + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(PrepareForAnalysis); + } + + private void PrepareForAnalysis(CompilationStartAnalysisContext context) + { + INamedTypeSymbol? spanOfT = context.Compilation.GetTypeByMetadataName(TypeNames.System_Span_Metadata); + INamedTypeSymbol? spanOfByte = spanOfT?.Construct(context.Compilation.GetSpecialType(SpecialType.System_Byte)); + INamedTypeSymbol? readOnlySpanOfT = context.Compilation.GetTypeByMetadataName(TypeNames.System_ReadOnlySpan_Metadata); + INamedTypeSymbol? readOnlySpanOfByte = readOnlySpanOfT?.Construct(context.Compilation.GetSpecialType(SpecialType.System_Byte)); + + if (spanOfT is not null && readOnlySpanOfT is not null) + { + var perCompilationAnalyzer = new PerCompilationAnalyzer(spanOfT, spanOfByte, readOnlySpanOfT, readOnlySpanOfByte); + + // Analyze NativeMarshalling/MarshalUsing for correctness + context.RegisterSymbolAction(perCompilationAnalyzer.AnalyzeTypeDefinition, SymbolKind.NamedType); + context.RegisterSymbolAction(perCompilationAnalyzer.AnalyzeElement, SymbolKind.Parameter, SymbolKind.Field); + context.RegisterSymbolAction(perCompilationAnalyzer.AnalyzeReturnType, SymbolKind.Method); + + // Analyze marshaller type to validate shape. + context.RegisterSymbolAction(perCompilationAnalyzer.AnalyzeMarshallerType, SymbolKind.NamedType); + } + } + + private class PerCompilationAnalyzer + { + private readonly INamedTypeSymbol _spanOfT; + private readonly INamedTypeSymbol _readOnlySpanOfT; + private readonly INamedTypeSymbol _spanOfByte; + private readonly INamedTypeSymbol _readOnlySpanOfByte; + + public PerCompilationAnalyzer(INamedTypeSymbol spanOfT, INamedTypeSymbol? spanOfByte, INamedTypeSymbol readOnlySpanOfT, INamedTypeSymbol? readOnlySpanOfByte) + { + _spanOfT = spanOfT; + _spanOfByte = spanOfByte; + _readOnlySpanOfT = readOnlySpanOfT; + _readOnlySpanOfByte = readOnlySpanOfByte; + } + + public void AnalyzeTypeDefinition(SymbolAnalysisContext context) + { + INamedTypeSymbol type = (INamedTypeSymbol)context.Symbol; + + (AttributeData? attributeData, INamedTypeSymbol? marshallerType) = ManualTypeMarshallingHelper.GetDefaultMarshallerInfo(type); + + if (attributeData is null) + { + return; + } + + AnalyzeManagedTypeMarshallingInfo(context, type, attributeData, marshallerType); + } + + public void AnalyzeElement(SymbolAnalysisContext context) + { + ITypeSymbol managedType = context.Symbol switch + { + IParameterSymbol param => param.Type, + IFieldSymbol field => field.Type, + _ => throw new InvalidOperationException() + }; + AttributeData? attributeData = context.Symbol.GetAttributes().FirstOrDefault(attr => attr.AttributeClass.ToDisplayString() == TypeNames.MarshalUsingAttribute); + if (attributeData is null || attributeData.ConstructorArguments.Length == 0) + { + return; + } + AnalyzeManagedTypeMarshallingInfo(context, managedType, attributeData, attributeData.ConstructorArguments[0].Value as INamedTypeSymbol); + } + + public void AnalyzeReturnType(SymbolAnalysisContext context) + { + IMethodSymbol method = (IMethodSymbol)context.Symbol; + ITypeSymbol managedType = method.ReturnType; + AttributeData? attributeData = method.GetReturnTypeAttributes().FirstOrDefault(attr => attr.AttributeClass.ToDisplayString() == TypeNames.MarshalUsingAttribute); + if (attributeData is null || attributeData.ConstructorArguments.Length == 0) + { + return; + } + AnalyzeManagedTypeMarshallingInfo(context, managedType, attributeData, attributeData.ConstructorArguments[0].Value as INamedTypeSymbol); + } + + private static void AnalyzeManagedTypeMarshallingInfo(SymbolAnalysisContext context, ITypeSymbol type, AttributeData attributeData, INamedTypeSymbol? marshallerType) + { + if (marshallerType is null) + { + context.ReportDiagnostic( + attributeData.CreateDiagnostic( + NativeTypeMustHaveCustomTypeMarshallerAttributeRule, + type.ToDisplayString())); + return; + } + + if (marshallerType.IsUnboundGenericType) + { + context.ReportDiagnostic( + attributeData.CreateDiagnostic( + NativeGenericTypeMustBeClosedOrMatchArityRule, + marshallerType.ToDisplayString(), + type.ToDisplayString())); + } + + (bool hasCustomTypeMarshallerAttribute, ITypeSymbol? marshallerManagedType, _) = ManualTypeMarshallingHelper.GetMarshallerShapeInfo(marshallerType); + + marshallerManagedType = ManualTypeMarshallingHelper.ResolveManagedType(marshallerManagedType, marshallerType, context.Compilation); + + if (!hasCustomTypeMarshallerAttribute) + { + context.ReportDiagnostic( + attributeData.CreateDiagnostic( + NativeTypeMustHaveCustomTypeMarshallerAttributeRule, + type.ToDisplayString())); + return; + } + + if (marshallerManagedType is null) + { + context.ReportDiagnostic( + attributeData.CreateDiagnostic( + NativeTypeMustHaveCustomTypeMarshallerAttributeRule, + type.ToDisplayString())); + return; + } + + if (!TypeSymbolsConstructedFromEqualTypes(type, marshallerManagedType)) + { + context.ReportDiagnostic( + attributeData.CreateDiagnostic( + NativeTypeMustHaveCustomTypeMarshallerAttributeRule, + type.ToDisplayString())); + return; + } + } + + private static bool TypeSymbolsConstructedFromEqualTypes(ITypeSymbol left, ITypeSymbol right) + { + return (left, right) switch + { + (INamedTypeSymbol namedLeft, INamedTypeSymbol namedRight) => SymbolEqualityComparer.Default.Equals(namedLeft.ConstructedFrom, namedRight.ConstructedFrom), + _ => SymbolEqualityComparer.Default.Equals(left, right) + }; + } + + public void AnalyzeMarshallerType(SymbolAnalysisContext context) + { + INamedTypeSymbol marshallerType = (INamedTypeSymbol)context.Symbol; + (bool hasCustomTypeMarshallerAttribute, ITypeSymbol? type, CustomTypeMarshallerData? marshallerDataMaybe) = ManualTypeMarshallingHelper.GetMarshallerShapeInfo(marshallerType); + type = ManualTypeMarshallingHelper.ResolveManagedType(type, marshallerType, context.Compilation); + + if (!hasCustomTypeMarshallerAttribute) + { + return; + } + if (type is null) + { + context.ReportDiagnostic(marshallerType.CreateDiagnostic(MarshallerTypeMustSpecifyManagedTypeRule, marshallerType.ToDisplayString())); + return; + } + + if (marshallerDataMaybe is not { } marshallerData) + { + context.ReportDiagnostic(marshallerType.CreateDiagnostic(CustomTypeMarshallerAttributeMustBeValidRule, marshallerType.ToDisplayString())); + return; + } + + if (!Enum.IsDefined(typeof(CustomTypeMarshallerKind), marshallerData.Kind)) + { + context.ReportDiagnostic(marshallerType.CreateDiagnostic(MarshallerKindMustBeValidRule, marshallerType.ToDisplayString())); + return; + } + + if (type is INamedTypeSymbol { IsUnboundGenericType: true } generic) + { + if (generic.TypeArguments.Length != marshallerType.TypeArguments.Length) + { + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + NativeGenericTypeMustBeClosedOrMatchArityRule, + marshallerType.ToDisplayString(), + type.ToDisplayString())); + return; + } + type = generic.ConstructedFrom.Construct(marshallerType.TypeArguments, marshallerType.TypeArgumentNullableAnnotations); + } + + IMethodSymbol? inConstructor = null; + IMethodSymbol? callerAllocatedSpanConstructor = null; + IMethodSymbol collectionOutConstructor = null; + foreach (IMethodSymbol ctor in marshallerType.Constructors) + { + if (ctor.IsStatic) + { + continue; + } + + if (inConstructor is null && ManualTypeMarshallingHelper.IsManagedToNativeConstructor(ctor, type, marshallerData.Kind)) + { + inConstructor = ctor; + } + + if (callerAllocatedSpanConstructor is null && ManualTypeMarshallingHelper.IsCallerAllocatedSpanConstructor(ctor, type, _spanOfByte, marshallerData.Kind)) + { + callerAllocatedSpanConstructor = ctor; + } + if (collectionOutConstructor is null && ctor.Parameters.Length == 1 && ctor.Parameters[0].Type.SpecialType == SpecialType.System_Int32) + { + collectionOutConstructor = ctor; + } + } + + if (marshallerData.Direction.HasFlag(CustomTypeMarshallerDirection.In)) + { + if (inConstructor is null) + { + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + GetInConstructorShapeRule(marshallerData.Kind), + ImmutableDictionary.Empty.Add( + MissingMemberNames.Key, + GetInConstructorMissingMemberName(marshallerData.Kind)), + marshallerType.ToDisplayString(), + type.ToDisplayString())); + } + if (marshallerData.Features.HasFlag(CustomTypeMarshallerFeatures.CallerAllocatedBuffer)) + { + if (callerAllocatedSpanConstructor is null) + { + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + GetCallerAllocatedBufferConstructorShapeRule(marshallerData.Kind), + ImmutableDictionary.Empty.Add( + MissingMemberNames.Key, + GetCallerAllocatedBufferConstructorMissingMemberName(marshallerData.Kind)), + marshallerType.ToDisplayString(), + type.ToDisplayString())); + } + if (marshallerData.BufferSize == null) + { + context.ReportDiagnostic( + (callerAllocatedSpanConstructor ?? (ISymbol)marshallerType).CreateDiagnostic( + CallerAllocConstructorMustHaveBufferSizeRule, + marshallerType.ToDisplayString())); + } + } + else if (callerAllocatedSpanConstructor is not null) + { + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + CallerAllocatedBufferConstructorProvidedShouldSpecifyFeatureRule, + ImmutableDictionary.Empty.Add( + MissingFeaturesKey, + nameof(CustomTypeMarshallerFeatures.CallerAllocatedBuffer)), + marshallerType.ToDisplayString())); + } + + // Validate that this type can support marshalling when stackalloc is not usable. + if (callerAllocatedSpanConstructor is not null && inConstructor is null) + { + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackRule, + marshallerType.ToDisplayString())); + } + } + + if (marshallerData.Direction.HasFlag(CustomTypeMarshallerDirection.Out) && !ManualTypeMarshallingHelper.HasToManagedMethod(marshallerType, type)) + { + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + OutRequiresToManagedRule, + ImmutableDictionary.Empty.Add( + MissingMemberNames.Key, + ShapeMemberNames.Value.ToManaged), + marshallerType.ToDisplayString())); + } + + if (marshallerData.Kind == CustomTypeMarshallerKind.LinearCollection) + { + IMethodSymbol? getManagedValuesSourceMethod = ManualTypeMarshallingHelper.FindGetManagedValuesSourceMethod(marshallerType, _readOnlySpanOfT); + IMethodSymbol? getManagedValuesDestinationMethod = ManualTypeMarshallingHelper.FindGetManagedValuesDestinationMethod(marshallerType, _spanOfT); + IMethodSymbol? getNativeValuesSourceMethod = ManualTypeMarshallingHelper.FindGetNativeValuesSourceMethod(marshallerType, _readOnlySpanOfByte); + IMethodSymbol? getNativeValuesDestinationMethod = ManualTypeMarshallingHelper.FindGetNativeValuesDestinationMethod(marshallerType, _spanOfByte); + if (marshallerData.Direction.HasFlag(CustomTypeMarshallerDirection.In) && (getManagedValuesSourceMethod is null || getNativeValuesDestinationMethod is null)) + { + var missingMembers = (getManagedValuesSourceMethod, getNativeValuesDestinationMethod) switch + { + (null, not null) => ShapeMemberNames.LinearCollection.GetManagedValuesSource, + (not null, null) => ShapeMemberNames.LinearCollection.GetNativeValuesDestination, + (null, null) => $"{ShapeMemberNames.LinearCollection.GetManagedValuesSource}{MissingMemberNames.Delimiter}{ShapeMemberNames.LinearCollection.GetNativeValuesDestination}", + (not null, not null) => string.Empty + }; + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + LinearCollectionInRequiresCollectionMethodsRule, + ImmutableDictionary.Empty.Add( + MissingMemberNames.Key, + missingMembers), + marshallerType.ToDisplayString())); + } + + if (marshallerData.Direction.HasFlag(CustomTypeMarshallerDirection.Out) && (getNativeValuesSourceMethod is null || getManagedValuesDestinationMethod is null)) + { + var missingMembers = (getNativeValuesSourceMethod, getManagedValuesDestinationMethod) switch + { + (not null, null) => ShapeMemberNames.LinearCollection.GetNativeValuesSource, + (null, not null) => ShapeMemberNames.LinearCollection.GetManagedValuesDestination, + (null, null) => $"{ShapeMemberNames.LinearCollection.GetNativeValuesSource}{MissingMemberNames.Delimiter}{ShapeMemberNames.LinearCollection.GetManagedValuesDestination}", + (not null, not null) => string.Empty + }; + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + LinearCollectionOutRequiresCollectionMethodsRule, + ImmutableDictionary.Empty.Add( + MissingMemberNames.Key, + missingMembers), + marshallerType.ToDisplayString())); + } + + if (getManagedValuesSourceMethod is not null + && getManagedValuesDestinationMethod is not null + && !SymbolEqualityComparer.Default.Equals( + ((INamedTypeSymbol)getManagedValuesSourceMethod.ReturnType).TypeArguments[0], + ((INamedTypeSymbol)getManagedValuesDestinationMethod.ReturnType).TypeArguments[0])) + { + context.ReportDiagnostic(getManagedValuesSourceMethod.CreateDiagnostic(LinearCollectionElementTypesMustMatchRule)); + } + if (marshallerData.Direction.HasFlag(CustomTypeMarshallerDirection.Out) && collectionOutConstructor is null) + { + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + LinearCollectionOutRequiresIntConstructorRule, + ImmutableDictionary.Empty.Add( + MissingMemberNames.Key, + MissingMemberNames.CollectionNativeElementSizeConstructor), + marshallerType.ToDisplayString())); + } + } + + + // Validate that the native type has at least one marshalling direction (either managed to native or native to managed) + if ((marshallerData.Direction & CustomTypeMarshallerDirection.Ref) == CustomTypeMarshallerDirection.None) + { + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + CustomMarshallerTypeMustSupportDirectionRule, + marshallerType.ToDisplayString())); + } + + if (marshallerData.Features.HasFlag(CustomTypeMarshallerFeatures.UnmanagedResources) && !ManualTypeMarshallingHelper.HasFreeNativeMethod(marshallerType)) + { + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + UnmanagedResourcesRequiresFreeNativeRule, + ImmutableDictionary.Empty.Add( + MissingMemberNames.Key, + ShapeMemberNames.Value.FreeNative), + marshallerType.ToDisplayString(), + type.ToDisplayString())); + } + else if (!marshallerData.Features.HasFlag(CustomTypeMarshallerFeatures.UnmanagedResources) && ManualTypeMarshallingHelper.HasFreeNativeMethod(marshallerType)) + { + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + FreeNativeMethodProvidedShouldSpecifyUnmanagedResourcesFeatureRule, + ImmutableDictionary.Empty.Add( + MissingFeaturesKey, + nameof(CustomTypeMarshallerFeatures.UnmanagedResources)), + marshallerType.ToDisplayString())); + } + + IMethodSymbol? toNativeValueMethod = ManualTypeMarshallingHelper.FindToNativeValueMethod(marshallerType); + IMethodSymbol? fromNativeValueMethod = ManualTypeMarshallingHelper.FindFromNativeValueMethod(marshallerType); + bool toNativeValueMethodIsRefReturn = toNativeValueMethod is { ReturnsByRef: true } or { ReturnsByRefReadonly: true }; + ITypeSymbol nativeType = marshallerType; + + if (marshallerData.Features.HasFlag(CustomTypeMarshallerFeatures.TwoStageMarshalling)) + { + if (marshallerData.Direction.HasFlag(CustomTypeMarshallerDirection.In) && toNativeValueMethod is null) + { + context.ReportDiagnostic(marshallerType.CreateDiagnostic( + InTwoStageMarshallingRequiresToNativeValueRule, + ImmutableDictionary.Empty.Add( + MissingMemberNames.Key, + ShapeMemberNames.Value.ToNativeValue), + marshallerType.ToDisplayString())); + } + if (marshallerData.Direction.HasFlag(CustomTypeMarshallerDirection.Out) && fromNativeValueMethod is null) + { + context.ReportDiagnostic(marshallerType.CreateDiagnostic( + OutTwoStageMarshallingRequiresFromNativeValueRule, + ImmutableDictionary.Empty.Add( + MissingMemberNames.Key, + ShapeMemberNames.Value.FromNativeValue), + marshallerType.ToDisplayString())); + } + + // ToNativeValue and FromNativeValue must be provided with the same type. + if (toNativeValueMethod is not null + && fromNativeValueMethod is not null + && !SymbolEqualityComparer.Default.Equals(toNativeValueMethod.ReturnType, fromNativeValueMethod.Parameters[0].Type)) + { + context.ReportDiagnostic(toNativeValueMethod.CreateDiagnostic(TwoStageMarshallingNativeTypesMustMatchRule)); + } + } + else if (fromNativeValueMethod is not null) + { + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + FromNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureRule, + ImmutableDictionary.Empty.Add( + MissingFeaturesKey, + nameof(CustomTypeMarshallerFeatures.TwoStageMarshalling)), + marshallerType.ToDisplayString())); + } + else if (toNativeValueMethod is not null) + { + context.ReportDiagnostic( + marshallerType.CreateDiagnostic( + ToNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureRule, + ImmutableDictionary.Empty.Add( + MissingFeaturesKey, + nameof(CustomTypeMarshallerFeatures.TwoStageMarshalling)), + marshallerType.ToDisplayString())); + } + + if (toNativeValueMethod is not null) + { + if (toNativeValueMethodIsRefReturn) + { + context.ReportDiagnostic( + toNativeValueMethod.CreateDiagnostic( + RefNativeValueUnsupportedRule, + marshallerType.ToDisplayString())); + } + + nativeType = toNativeValueMethod.ReturnType; + } + else if (ManualTypeMarshallingHelper.FindGetPinnableReference(marshallerType) is IMethodSymbol marshallerGetPinnableReferenceMethod) + { + // If we don't have a ToNativeValue method, then we disallow a GetPinnableReference on the marshaler type. + // We do this since there is no valid use case that we can think of for a GetPinnableReference on a blittable type + // being a requirement to calculate the value of the fields of the same blittable instance, + // so we're pre-emptively blocking this until a use case is discovered. + context.ReportDiagnostic( + marshallerGetPinnableReferenceMethod.CreateDiagnostic( + MarshallerGetPinnableReferenceRequiresTwoStageMarshallingRule, + nativeType.ToDisplayString())); + } + + if (!nativeType.IsConsideredBlittable()) + { + context.ReportDiagnostic( + (toNativeValueMethod ?? (ISymbol)marshallerType).CreateDiagnostic( + NativeTypeMustBeBlittableRule, + nativeType.ToDisplayString(), + type.ToDisplayString())); + } + + if (SymbolEqualityComparer.Default.Equals(ManualTypeMarshallingHelper.GetDefaultMarshallerInfo(type).marshallerType, marshallerType) + && ManualTypeMarshallingHelper.FindGetPinnableReference(type) is IMethodSymbol managedGetPinnableReferenceMethod) + { + if (!managedGetPinnableReferenceMethod.ReturnType.IsConsideredBlittable()) + { + context.ReportDiagnostic(managedGetPinnableReferenceMethod.CreateDiagnostic(GetPinnableReferenceReturnTypeBlittableRule)); + } + // Validate that our marshaler supports scenarios where GetPinnableReference cannot be used. + if (!marshallerData.Direction.HasFlag(CustomTypeMarshallerDirection.In)) + { + context.ReportDiagnostic( + type.CreateDiagnostic( + GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule, + type.ToDisplayString())); + } + + // If the managed type has a GetPinnableReference method, make sure that the Value getter is also a pointer-sized primitive. + // This ensures that marshalling via pinning the managed value and marshalling via the default marshaller will have the same ABI. + if (toNativeValueMethod is not null + && !toNativeValueMethodIsRefReturn // Ref returns are already reported above as invalid, so don't issue another warning here about them + && nativeType is not ( + IPointerTypeSymbol or + { SpecialType: SpecialType.System_IntPtr } or + { SpecialType: SpecialType.System_UIntPtr })) + { + context.ReportDiagnostic( + toNativeValueMethod.CreateDiagnostic( + NativeTypeMustBePointerSizedRule, + nativeType.ToDisplayString(), + managedGetPinnableReferenceMethod.ContainingType.ToDisplayString())); + } + } + } + + private DiagnosticDescriptor GetInConstructorShapeRule(CustomTypeMarshallerKind kind) => kind switch + { + CustomTypeMarshallerKind.Value => ValueInRequiresOneParameterConstructorRule, + CustomTypeMarshallerKind.LinearCollection => LinearCollectionInRequiresTwoParameterConstructorRule, + _ => throw new UnreachableException() + }; + private string GetInConstructorMissingMemberName(CustomTypeMarshallerKind kind) => kind switch + { + CustomTypeMarshallerKind.Value => MissingMemberNames.ValueManagedToNativeConstructor, + CustomTypeMarshallerKind.LinearCollection => MissingMemberNames.CollectionManagedToNativeConstructor, + _ => throw new UnreachableException() + }; + private DiagnosticDescriptor GetCallerAllocatedBufferConstructorShapeRule(CustomTypeMarshallerKind kind) => kind switch + { + CustomTypeMarshallerKind.Value => ValueInCallerAllocatedBufferRequiresSpanConstructorRule, + CustomTypeMarshallerKind.LinearCollection => LinearCollectionInCallerAllocatedBufferRequiresSpanConstructorRule, + _ => throw new UnreachableException() + }; + private string GetCallerAllocatedBufferConstructorMissingMemberName(CustomTypeMarshallerKind kind) => kind switch + { + CustomTypeMarshallerKind.Value => MissingMemberNames.ValueCallerAllocatedBufferConstructor, + CustomTypeMarshallerKind.LinearCollection => MissingMemberNames.CollectionCallerAllocatedBufferConstructor, + _ => throw new UnreachableException() + }; + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/CustomTypeMarshallerFixer.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/CustomTypeMarshallerFixer.cs new file mode 100644 index 0000000000000..08cc2550ed4e2 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/CustomTypeMarshallerFixer.cs @@ -0,0 +1,404 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.Interop.Analyzers +{ + [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] + public class CustomTypeMarshallerFixer : CodeFixProvider + { + private const string AddMissingCustomTypeMarshallerMembersKey = nameof(AddMissingCustomTypeMarshallerMembersKey); + private const string AddMissingCustomTypeMarshallerFeaturesKey = nameof(AddMissingCustomTypeMarshallerFeaturesKey); + + private class CustomFixAllProvider : DocumentBasedFixAllProvider + { + protected override async Task FixAllAsync(FixAllContext fixAllContext, Document document, ImmutableArray diagnostics) + { + SyntaxNode? root = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + if (root == null) + return document; + + DocumentEditor editor = await DocumentEditor.CreateAsync(document, fixAllContext.CancellationToken).ConfigureAwait(false); + + switch (fixAllContext.CodeActionEquivalenceKey) + { + case AddMissingCustomTypeMarshallerMembersKey: + foreach (IGrouping diagnosticsBySpan in diagnostics.GroupBy(d => d.Location.SourceSpan)) + { + SyntaxNode node = root.FindNode(diagnosticsBySpan.Key); + + AddMissingMembers(fixAllContext, editor, diagnosticsBySpan, node); + } + break; + case AddMissingCustomTypeMarshallerFeaturesKey: + foreach (IGrouping diagnosticsBySpan in diagnostics.GroupBy(d => d.Location.SourceSpan)) + { + SyntaxNode node = root.FindNode(diagnosticsBySpan.Key); + + await AddMissingFeatures(fixAllContext, editor, diagnosticsBySpan, node).ConfigureAwait(false); + } + break; + default: + break; + } + + return editor.GetChangedDocument(); + } + + private static void AddMissingMembers(FixAllContext fixAllContext, DocumentEditor editor, IEnumerable diagnostics, SyntaxNode node) + { + var (missingMemberNames, _) = GetRequiredShapeMissingMemberNames(diagnostics); + ITypeSymbol marshallerType = (ITypeSymbol)editor.SemanticModel.GetDeclaredSymbol(node); + editor.ReplaceNode( + node, + (node, gen) => + CustomTypeMarshallerFixer.AddMissingMembers( + node, + marshallerType, + missingMemberNames, + editor.SemanticModel.Compilation, + gen)); + } + + private static async Task AddMissingFeatures(FixAllContext fixAllContext, DocumentEditor editor, IEnumerable diagnostics, SyntaxNode node) + { + var (featuresToAdd, _) = GetFeaturesToAdd(diagnostics); + ITypeSymbol marshallerType = (ITypeSymbol)editor.SemanticModel.GetDeclaredSymbol(node); + AttributeData customTypeMarshallerAttribute = marshallerType.GetAttributes().FirstOrDefault(attr => attr.AttributeClass.ToDisplayString() == TypeNames.CustomTypeMarshallerAttribute); + + SyntaxNode attributeSyntax = await customTypeMarshallerAttribute.ApplicationSyntaxReference!.GetSyntaxAsync(fixAllContext.CancellationToken).ConfigureAwait(false); + + editor.ReplaceNode( + attributeSyntax, + (node, gen) => + CustomTypeMarshallerFixer.AddMissingFeatures( + gen.GetName(node), + customTypeMarshallerAttribute, + featuresToAdd, + gen)); + } + } + + public override FixAllProvider? GetFixAllProvider() => new CustomFixAllProvider(); + + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create( + AnalyzerDiagnostics.Ids.CustomMarshallerTypeMustHaveRequiredShape, + AnalyzerDiagnostics.Ids.MissingAllocatingMarshallingFallback, + AnalyzerDiagnostics.Ids.ProvidedMethodsNotSpecifiedInShape); + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + Document doc = context.Document; + SyntaxNode? root = await doc.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + if (root == null) + return; + + SyntaxNode node = root.FindNode(context.Span); + var (missingMemberNames, missingMembersDiagnostics) = GetRequiredShapeMissingMemberNames(context.Diagnostics); + + if (missingMembersDiagnostics.Count > 0) + { + context.RegisterCodeFix( + CodeAction.Create( + Resources.AddMissingCustomTypeMarshallerMembers, + ct => AddMissingMembers(doc, node, missingMemberNames, ct), + AddMissingCustomTypeMarshallerMembersKey), + missingMembersDiagnostics); + } + + var (featuresToAdd, featuresToAddDiagnostics) = GetFeaturesToAdd(context.Diagnostics); + + if (featuresToAddDiagnostics.Count > 0 && featuresToAdd != CustomTypeMarshallerFeatures.None) + { + context.RegisterCodeFix( + CodeAction.Create( + Resources.AddMissingFeaturesToCustomTypeMarshaller, + ct => AddMissingFeatures(doc, node, featuresToAdd, ct), + AddMissingCustomTypeMarshallerFeaturesKey), + featuresToAddDiagnostics); + } + } + + private static (List missingMembers, List fixedDiagnostics) GetRequiredShapeMissingMemberNames(IEnumerable diagnostics) + { + List missingMemberNames = new(); + List requiredShapeDiagnostics = new(); + foreach (var diagnostic in diagnostics) + { + if (diagnostic.Id == AnalyzerDiagnostics.Ids.CustomMarshallerTypeMustHaveRequiredShape) + { + requiredShapeDiagnostics.Add(diagnostic); + if (diagnostic.Properties.TryGetValue(CustomTypeMarshallerAnalyzer.MissingMemberNames.Key, out string missingMembers)) + { + missingMemberNames.AddRange(missingMembers.Split(CustomTypeMarshallerAnalyzer.MissingMemberNames.Delimiter)); + } + } + } + + return (missingMemberNames, requiredShapeDiagnostics); + } + private static (CustomTypeMarshallerFeatures featuresToAdd, List fixedDiagnostics) GetFeaturesToAdd(IEnumerable diagnostics) + { + List featuresToAddDiagnostics = new(); + CustomTypeMarshallerFeatures featuresToAdd = CustomTypeMarshallerFeatures.None; + foreach (var diagnostic in diagnostics) + { + if (diagnostic.Id == AnalyzerDiagnostics.Ids.ProvidedMethodsNotSpecifiedInShape) + { + featuresToAddDiagnostics.Add(diagnostic); + if (diagnostic.Properties.TryGetValue(CustomTypeMarshallerAnalyzer.MissingFeaturesKey, out string missingFeatures) + && Enum.TryParse(missingFeatures, out CustomTypeMarshallerFeatures featuresValue)) + { + featuresToAdd |= featuresValue; + } + } + } + + return (featuresToAdd, featuresToAddDiagnostics); + } + private static async Task AddMissingFeatures(Document doc, SyntaxNode node, CustomTypeMarshallerFeatures featuresToAdd, CancellationToken ct) + { + var editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false); + var gen = editor.Generator; + + ISymbol marshallerType = editor.SemanticModel.GetDeclaredSymbol(node, ct); + + AttributeData customTypeMarshallerAttribute = marshallerType.GetAttributes().FirstOrDefault(attr => attr.AttributeClass.ToDisplayString() == TypeNames.CustomTypeMarshallerAttribute); + + SyntaxNode attributeSyntax = await customTypeMarshallerAttribute.ApplicationSyntaxReference!.GetSyntaxAsync(ct).ConfigureAwait(false); + + SyntaxNode updatedDeclaration = AddMissingFeatures(gen.GetName(attributeSyntax), customTypeMarshallerAttribute, featuresToAdd, gen); + + editor.ReplaceNode(attributeSyntax, updatedDeclaration); + + return editor.GetChangedDocument(); + } + + private static SyntaxNode AddMissingFeatures(string attributeName, AttributeData? customTypeMarshallerAttribute, CustomTypeMarshallerFeatures featuresToAdd, SyntaxGenerator gen) + { + SyntaxNode newAttributeSyntax = gen.Attribute(attributeName); + + newAttributeSyntax = gen.AddAttributeArguments(newAttributeSyntax, customTypeMarshallerAttribute.ConstructorArguments.Select(a => gen.AttributeArgument(gen.TypedConstantExpression(a)))); + + CustomTypeMarshallerFeatures newFeaturesValue = featuresToAdd; + int? featuresArgLocation = null; + + newAttributeSyntax = gen.AddAttributeArguments(newAttributeSyntax, customTypeMarshallerAttribute.NamedArguments + .Where((a, i) => + { + if (a.Key == "Features") + { + // Capture the original location of the 'Features' named argument so we can update it "in place". + featuresArgLocation = customTypeMarshallerAttribute.ConstructorArguments.Length + i; + newFeaturesValue |= (CustomTypeMarshallerFeatures)a.Value.Value; + return false; + } + return true; + }) + .Select(a => gen.AttributeArgument(a.Key, gen.TypedConstantExpression(a.Value)))); + + SyntaxNode featureAttributeArgument = gen.AttributeArgument("Features", + gen.GetEnumValueAsFlagsExpression( + customTypeMarshallerAttribute.AttributeClass.GetMembers(ManualTypeMarshallingHelper.CustomMarshallerAttributeFields.Features).OfType().First().Type, + (int)newFeaturesValue, + includeZeroValueFlags: false)); + + newAttributeSyntax = featuresArgLocation is null + ? gen.AddAttributeArguments(newAttributeSyntax, new[] { featureAttributeArgument }) + : gen.InsertAttributeArguments(newAttributeSyntax, featuresArgLocation.Value, new[] { featureAttributeArgument }); + + return newAttributeSyntax; + } + + private static async Task AddMissingMembers(Document doc, SyntaxNode node, List missingMemberNames, CancellationToken ct) + { + var editor = await DocumentEditor.CreateAsync(doc, ct).ConfigureAwait(false); + var gen = editor.Generator; + + SyntaxNode updatedDeclaration = AddMissingMembers(node, (ITypeSymbol)editor.SemanticModel.GetDeclaredSymbol(node, ct), missingMemberNames, editor.SemanticModel.Compilation, gen); + + editor.ReplaceNode(node, updatedDeclaration); + + return editor.GetChangedDocument(); + } + + private static SyntaxNode AddMissingMembers(SyntaxNode node, ITypeSymbol + marshallerType, List missingMemberNames, Compilation compilation, SyntaxGenerator gen) + { + INamedTypeSymbol @byte = compilation.GetSpecialType(SpecialType.System_Byte); + INamedTypeSymbol @object = compilation.GetSpecialType(SpecialType.System_Object); + INamedTypeSymbol spanOfT = compilation.GetTypeByMetadataName(TypeNames.System_Span_Metadata)!; + INamedTypeSymbol spanOfByte = spanOfT.Construct(@byte)!; + INamedTypeSymbol readOnlySpanOfT = compilation.GetTypeByMetadataName(TypeNames.System_ReadOnlySpan_Metadata)!; + INamedTypeSymbol readOnlySpanOfByte = readOnlySpanOfT.Construct(@byte)!; + INamedTypeSymbol int32 = compilation.GetSpecialType(SpecialType.System_Int32); + + SyntaxNode updatedDeclaration = node; + + + (_, ITypeSymbol managedType, _) = ManualTypeMarshallingHelper.GetMarshallerShapeInfo(marshallerType); + + IMethodSymbol? fromNativeValueMethod = ManualTypeMarshallingHelper.FindFromNativeValueMethod(marshallerType); + IMethodSymbol? toNativeValueMethod = ManualTypeMarshallingHelper.FindToNativeValueMethod(marshallerType); + IMethodSymbol? getManagedValuesSourceMethod = ManualTypeMarshallingHelper.FindGetManagedValuesSourceMethod(marshallerType, readOnlySpanOfT); + IMethodSymbol? getManagedValuesDestinationMethod = ManualTypeMarshallingHelper.FindGetManagedValuesDestinationMethod(marshallerType, spanOfT); + + SyntaxNode[] throwNotImplementedStatements = new[] + { + gen.ThrowStatement(gen.ObjectCreationExpression(gen.DottedName("System.NotImplementedException"))) + }; + + foreach (string missingMemberName in missingMemberNames) + { + switch (missingMemberName) + { + case CustomTypeMarshallerAnalyzer.MissingMemberNames.ValueManagedToNativeConstructor: + updatedDeclaration = gen.AddMembers(updatedDeclaration, gen.ConstructorDeclaration( + gen.GetName(node), + new[] + { + gen.ParameterDeclaration("managed", type: gen.TypeExpression(managedType)) + }, + accessibility: Accessibility.Public, + statements: throwNotImplementedStatements)); + break; + case CustomTypeMarshallerAnalyzer.MissingMemberNames.ValueCallerAllocatedBufferConstructor: + updatedDeclaration = gen.AddMembers(updatedDeclaration, gen.ConstructorDeclaration( + gen.GetName(node), + new[] + { + gen.ParameterDeclaration("managed", type: gen.TypeExpression(managedType)), + gen.ParameterDeclaration("buffer", type: gen.TypeExpression(spanOfByte)) + }, + accessibility: Accessibility.Public, + statements: throwNotImplementedStatements)); + break; + case CustomTypeMarshallerAnalyzer.MissingMemberNames.CollectionManagedToNativeConstructor: + updatedDeclaration = gen.AddMembers(updatedDeclaration, gen.ConstructorDeclaration( + gen.GetName(node), + new[] + { + gen.ParameterDeclaration("managed", type: gen.TypeExpression(managedType)), + gen.ParameterDeclaration("nativeElementSize", type: gen.TypeExpression(int32)) + }, + accessibility: Accessibility.Public, + statements: throwNotImplementedStatements)); + break; + case CustomTypeMarshallerAnalyzer.MissingMemberNames.CollectionCallerAllocatedBufferConstructor: + updatedDeclaration = gen.AddMembers(updatedDeclaration, gen.ConstructorDeclaration( + gen.GetName(node), + new[] + { + gen.ParameterDeclaration("managed", type: gen.TypeExpression(managedType)), + gen.ParameterDeclaration("buffer", type: gen.TypeExpression(spanOfByte)), + gen.ParameterDeclaration("nativeElementSize", type: gen.TypeExpression(int32)) + }, + accessibility: Accessibility.Public, + statements: throwNotImplementedStatements)); + break; + case CustomTypeMarshallerAnalyzer.MissingMemberNames.CollectionNativeElementSizeConstructor: + updatedDeclaration = gen.AddMembers(updatedDeclaration, gen.ConstructorDeclaration( + gen.GetName(node), + new[] + { + gen.ParameterDeclaration("nativeElementSize", type: gen.TypeExpression(int32)) + }, + accessibility: Accessibility.Public, + statements: throwNotImplementedStatements)); + break; + case ShapeMemberNames.Value.ToManaged: + updatedDeclaration = gen.AddMembers(updatedDeclaration, gen.MethodDeclaration( + ShapeMemberNames.Value.ToManaged, + returnType: gen.TypeExpression(managedType), + accessibility: Accessibility.Public, + statements: throwNotImplementedStatements)); + break; + case ShapeMemberNames.Value.FreeNative: + updatedDeclaration = gen.AddMembers(updatedDeclaration, gen.MethodDeclaration(ShapeMemberNames.Value.FreeNative, + accessibility: Accessibility.Public, + statements: throwNotImplementedStatements)); + break; + case ShapeMemberNames.Value.FromNativeValue: + updatedDeclaration = gen.AddMembers(updatedDeclaration, gen.MethodDeclaration( + ShapeMemberNames.Value.FromNativeValue, + parameters: new[] + { + gen.ParameterDeclaration("value", + type: gen.TypeExpression(toNativeValueMethod?.ReturnType ?? @byte)) + }, + accessibility: Accessibility.Public, + statements: throwNotImplementedStatements)); + break; + case ShapeMemberNames.Value.ToNativeValue: + updatedDeclaration = gen.AddMembers(updatedDeclaration, gen.MethodDeclaration( + ShapeMemberNames.Value.ToNativeValue, + returnType: gen.TypeExpression(fromNativeValueMethod?.Parameters[0].Type ?? @byte), + accessibility: Accessibility.Public, + statements: throwNotImplementedStatements)); + break; + case ShapeMemberNames.LinearCollection.GetManagedValuesSource: + INamedTypeSymbol? getManagedValuesDestinationReturnType = (INamedTypeSymbol?)getManagedValuesDestinationMethod?.ReturnType; + updatedDeclaration = gen.AddMembers(updatedDeclaration, gen.MethodDeclaration( + ShapeMemberNames.LinearCollection.GetManagedValuesSource, + returnType: gen.TypeExpression( + readOnlySpanOfT.Construct( + getManagedValuesDestinationReturnType?.TypeArguments[0] ?? @object)), + accessibility: Accessibility.Public, + statements: throwNotImplementedStatements)); + break; + case ShapeMemberNames.LinearCollection.GetNativeValuesDestination: + updatedDeclaration = gen.AddMembers(updatedDeclaration, gen.MethodDeclaration( + ShapeMemberNames.LinearCollection.GetNativeValuesDestination, + returnType: gen.TypeExpression(spanOfByte), + accessibility: Accessibility.Public, + statements: throwNotImplementedStatements)); + break; + case ShapeMemberNames.LinearCollection.GetNativeValuesSource: + updatedDeclaration = gen.AddMembers(updatedDeclaration, gen.MethodDeclaration( + ShapeMemberNames.LinearCollection.GetNativeValuesSource, + parameters: new[] + { + gen.ParameterDeclaration("numElements", type: gen.TypeExpression(int32)) + }, + returnType: gen.TypeExpression(readOnlySpanOfByte), + accessibility: Accessibility.Public, + statements: throwNotImplementedStatements)); + break; + case ShapeMemberNames.LinearCollection.GetManagedValuesDestination: + INamedTypeSymbol? getManagedValuesSourceReturnType = (INamedTypeSymbol?)getManagedValuesSourceMethod?.ReturnType; + updatedDeclaration = gen.AddMembers(updatedDeclaration, gen.MethodDeclaration( + ShapeMemberNames.LinearCollection.GetNativeValuesDestination, + parameters: new[] + { + gen.ParameterDeclaration("numElements", type: gen.TypeExpression(int32)) + }, + returnType: gen.TypeExpression( + spanOfT.Construct( + getManagedValuesSourceReturnType?.TypeArguments[0] ?? @object)), + accessibility: Accessibility.Public, + statements: throwNotImplementedStatements)); + break; + default: + break; + } + } + + return updatedDeclaration; + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ManualTypeMarshallingAnalyzer.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ManualTypeMarshallingAnalyzer.cs deleted file mode 100644 index 00abe866653b7..0000000000000 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ManualTypeMarshallingAnalyzer.cs +++ /dev/null @@ -1,535 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Immutable; -using System.Linq; - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -using static Microsoft.Interop.Analyzers.AnalyzerDiagnostics; - -namespace Microsoft.Interop.Analyzers -{ - [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] - public class ManualTypeMarshallingAnalyzer : DiagnosticAnalyzer - { - private const string Category = "Usage"; - - public static readonly DiagnosticDescriptor BlittableTypeMustBeBlittableRule = - new DiagnosticDescriptor( - Ids.BlittableTypeMustBeBlittable, - "BlittableTypeMustBeBlittable", - GetResourceString(nameof(Resources.BlittableTypeMustBeBlittableMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.BlittableTypeMustBeBlittableDescription))); - - public static readonly DiagnosticDescriptor CannotHaveMultipleMarshallingAttributesRule = - new DiagnosticDescriptor( - Ids.CannotHaveMultipleMarshallingAttributes, - "CannotHaveMultipleMarshallingAttributes", - GetResourceString(nameof(Resources.CannotHaveMultipleMarshallingAttributesMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.CannotHaveMultipleMarshallingAttributesDescription))); - - public static readonly DiagnosticDescriptor NativeTypeMustBeNonNullRule = - new DiagnosticDescriptor( - Ids.NativeTypeMustBeNonNull, - "NativeTypeMustBeNonNull", - GetResourceString(nameof(Resources.NativeTypeMustBeNonNullMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.NativeTypeMustBeNonNullDescription))); - - public static readonly DiagnosticDescriptor NativeTypeMustBeBlittableRule = - new DiagnosticDescriptor( - Ids.NativeTypeMustBeBlittable, - "NativeTypeMustBeBlittable", - GetResourceString(nameof(Resources.NativeTypeMustBeBlittableMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.NativeTypeMustBeBlittableDescription))); - - public static readonly DiagnosticDescriptor GetPinnableReferenceReturnTypeBlittableRule = - new DiagnosticDescriptor( - Ids.GetPinnableReferenceReturnTypeBlittable, - "GetPinnableReferenceReturnTypeBlittable", - GetResourceString(nameof(Resources.GetPinnableReferenceReturnTypeBlittableMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.GetPinnableReferenceReturnTypeBlittableDescription))); - - public static readonly DiagnosticDescriptor NativeTypeMustBePointerSizedRule = - new DiagnosticDescriptor( - Ids.NativeTypeMustBePointerSized, - "NativeTypeMustBePointerSized", - GetResourceString(nameof(Resources.NativeTypeMustBePointerSizedMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.NativeTypeMustBePointerSizedDescription))); - - public static readonly DiagnosticDescriptor NativeTypeMustHaveRequiredShapeRule = - new DiagnosticDescriptor( - Ids.NativeTypeMustHaveRequiredShape, - "NativeTypeMustHaveRequiredShape", - GetResourceString(nameof(Resources.NativeTypeMustHaveRequiredShapeMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.NativeTypeMustHaveRequiredShapeDescription))); - - public static readonly DiagnosticDescriptor CollectionNativeTypeMustHaveRequiredShapeRule = - new DiagnosticDescriptor( - Ids.NativeTypeMustHaveRequiredShape, - "NativeTypeMustHaveRequiredShape", - GetResourceString(nameof(Resources.CollectionNativeTypeMustHaveRequiredShapeMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.CollectionNativeTypeMustHaveRequiredShapeDescription))); - - public static readonly DiagnosticDescriptor ValuePropertyMustHaveSetterRule = - new DiagnosticDescriptor( - Ids.ValuePropertyMustHaveSetter, - "ValuePropertyMustHaveSetter", - GetResourceString(nameof(Resources.ValuePropertyMustHaveSetterMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.ValuePropertyMustHaveSetterDescription))); - - public static readonly DiagnosticDescriptor ValuePropertyMustHaveGetterRule = - new DiagnosticDescriptor( - Ids.ValuePropertyMustHaveGetter, - "ValuePropertyMustHaveGetter", - GetResourceString(nameof(Resources.ValuePropertyMustHaveGetterMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.ValuePropertyMustHaveGetterDescription))); - - public static readonly DiagnosticDescriptor GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule = - new DiagnosticDescriptor( - Ids.GetPinnableReferenceShouldSupportAllocatingMarshallingFallback, - "GetPinnableReferenceShouldSupportAllocatingMarshallingFallback", - GetResourceString(nameof(Resources.GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage)), - Category, - DiagnosticSeverity.Warning, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription))); - - public static readonly DiagnosticDescriptor CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackRule = - new DiagnosticDescriptor( - Ids.CallerAllocMarshallingShouldSupportAllocatingMarshallingFallback, - "CallerAllocMarshallingShouldSupportAllocatingMarshallingFallback", - GetResourceString(nameof(Resources.CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackMessage)), - Category, - DiagnosticSeverity.Warning, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackDescription))); - - public static readonly DiagnosticDescriptor CallerAllocConstructorMustHaveBufferSizeConstantRule = - new DiagnosticDescriptor( - Ids.CallerAllocConstructorMustHaveStackBufferSizeConstant, - "CallerAllocConstructorMustHaveBufferSizeConstant", - GetResourceString(nameof(Resources.CallerAllocConstructorMustHaveBufferSizeConstantMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.CallerAllocConstructorMustHaveBufferSizeConstantDescription))); - - public static readonly DiagnosticDescriptor RefValuePropertyUnsupportedRule = - new DiagnosticDescriptor( - Ids.RefValuePropertyUnsupported, - "RefValuePropertyUnsupported", - GetResourceString(nameof(Resources.RefValuePropertyUnsupportedMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.RefValuePropertyUnsupportedDescription))); - - public static readonly DiagnosticDescriptor NativeGenericTypeMustBeClosedOrMatchArityRule = - new DiagnosticDescriptor( - Ids.NativeGenericTypeMustBeClosedOrMatchArity, - "NativeGenericTypeMustBeClosedOrMatchArity", - GetResourceString(nameof(Resources.NativeGenericTypeMustBeClosedOrMatchArityMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.NativeGenericTypeMustBeClosedOrMatchArityDescription))); - - public static readonly DiagnosticDescriptor MarshallerGetPinnableReferenceRequiresValuePropertyRule = - new DiagnosticDescriptor( - Ids.MarshallerGetPinnableReferenceRequiresValueProperty, - "MarshallerGetPinnableReferenceRequiresValueProperty", - GetResourceString(nameof(Resources.MarshallerGetPinnableReferenceRequiresValuePropertyMessage)), - Category, - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.MarshallerGetPinnableReferenceRequiresValuePropertyDescription))); - - public override ImmutableArray SupportedDiagnostics => - ImmutableArray.Create( - BlittableTypeMustBeBlittableRule, - CannotHaveMultipleMarshallingAttributesRule, - NativeTypeMustBeNonNullRule, - NativeTypeMustBeBlittableRule, - GetPinnableReferenceReturnTypeBlittableRule, - NativeTypeMustBePointerSizedRule, - NativeTypeMustHaveRequiredShapeRule, - CollectionNativeTypeMustHaveRequiredShapeRule, - ValuePropertyMustHaveSetterRule, - ValuePropertyMustHaveGetterRule, - GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule, - CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackRule, - CallerAllocConstructorMustHaveBufferSizeConstantRule, - RefValuePropertyUnsupportedRule, - NativeGenericTypeMustBeClosedOrMatchArityRule, - MarshallerGetPinnableReferenceRequiresValuePropertyRule); - - public override void Initialize(AnalysisContext context) - { - // Don't analyze generated code - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.EnableConcurrentExecution(); - context.RegisterCompilationStartAction(PrepareForAnalysis); - } - - private void PrepareForAnalysis(CompilationStartAnalysisContext context) - { - INamedTypeSymbol? generatedMarshallingAttribute = context.Compilation.GetTypeByMetadataName(TypeNames.GeneratedMarshallingAttribute); - INamedTypeSymbol? nativeMarshallingAttribute = context.Compilation.GetTypeByMetadataName(TypeNames.NativeMarshallingAttribute); - INamedTypeSymbol? marshalUsingAttribute = context.Compilation.GetTypeByMetadataName(TypeNames.MarshalUsingAttribute); - INamedTypeSymbol? genericContiguousCollectionMarshallerAttribute = context.Compilation.GetTypeByMetadataName(TypeNames.GenericContiguousCollectionMarshallerAttribute); - INamedTypeSymbol? spanOfByte = context.Compilation.GetTypeByMetadataName(TypeNames.System_Span_Metadata)!.Construct(context.Compilation.GetSpecialType(SpecialType.System_Byte)); - - if (generatedMarshallingAttribute is not null - && nativeMarshallingAttribute is not null - && marshalUsingAttribute is not null - && genericContiguousCollectionMarshallerAttribute is not null - && spanOfByte is not null) - { - var perCompilationAnalyzer = new PerCompilationAnalyzer( - generatedMarshallingAttribute, - nativeMarshallingAttribute, - marshalUsingAttribute, - genericContiguousCollectionMarshallerAttribute, - spanOfByte, - context.Compilation.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_StructLayoutAttribute)!); - context.RegisterSymbolAction(context => perCompilationAnalyzer.AnalyzeTypeDefinition(context), SymbolKind.NamedType); - context.RegisterSymbolAction(context => perCompilationAnalyzer.AnalyzeElement(context), SymbolKind.Parameter, SymbolKind.Field); - context.RegisterSymbolAction(context => perCompilationAnalyzer.AnalyzeReturnType(context), SymbolKind.Method); - } - } - - private class PerCompilationAnalyzer - { - private readonly INamedTypeSymbol _generatedMarshallingAttribute; - private readonly INamedTypeSymbol _nativeMarshallingAttribute; - private readonly INamedTypeSymbol _marshalUsingAttribute; - private readonly INamedTypeSymbol _genericContiguousCollectionMarshallerAttribute; - private readonly INamedTypeSymbol _spanOfByte; - private readonly INamedTypeSymbol _structLayoutAttribute; - - public PerCompilationAnalyzer(INamedTypeSymbol generatedMarshallingAttribute, - INamedTypeSymbol nativeMarshallingAttribute, - INamedTypeSymbol marshalUsingAttribute, - INamedTypeSymbol genericContiguousCollectionMarshallerAttribute, - INamedTypeSymbol spanOfByte, - INamedTypeSymbol structLayoutAttribute) - { - _generatedMarshallingAttribute = generatedMarshallingAttribute; - _nativeMarshallingAttribute = nativeMarshallingAttribute; - _marshalUsingAttribute = marshalUsingAttribute; - _genericContiguousCollectionMarshallerAttribute = genericContiguousCollectionMarshallerAttribute; - _spanOfByte = spanOfByte; - _structLayoutAttribute = structLayoutAttribute; - } - - public void AnalyzeTypeDefinition(SymbolAnalysisContext context) - { - INamedTypeSymbol type = (INamedTypeSymbol)context.Symbol; - - foreach (AttributeData attr in type.GetAttributes()) - { - if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, _generatedMarshallingAttribute)) - { - // If the type has the GeneratedMarshallingAttribute, - // we let the source generator handle error checking. - return; - } - else if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, _nativeMarshallingAttribute)) - { - AnalyzeNativeMarshalerType(context, type, attr, isNativeMarshallingAttribute: true); - return; - } - } - } - - public void AnalyzeElement(SymbolAnalysisContext context) - { - AttributeData? attrData = context.Symbol.GetAttributes().FirstOrDefault(attr => SymbolEqualityComparer.Default.Equals(_marshalUsingAttribute, attr.AttributeClass)); - if (attrData is not null) - { - if (context.Symbol is IParameterSymbol param) - { - AnalyzeNativeMarshalerType(context, param.Type, attrData, isNativeMarshallingAttribute: false); - } - else if (context.Symbol is IFieldSymbol field) - { - AnalyzeNativeMarshalerType(context, field.Type, attrData, isNativeMarshallingAttribute: false); - } - } - } - - public void AnalyzeReturnType(SymbolAnalysisContext context) - { - var method = (IMethodSymbol)context.Symbol; - AttributeData? attrData = method.GetReturnTypeAttributes().FirstOrDefault(attr => SymbolEqualityComparer.Default.Equals(_marshalUsingAttribute, attr.AttributeClass)); - if (attrData is not null) - { - AnalyzeNativeMarshalerType(context, method.ReturnType, attrData, isNativeMarshallingAttribute: false); - } - } - - private void AnalyzeNativeMarshalerType(SymbolAnalysisContext context, ITypeSymbol type, AttributeData nativeMarshalerAttributeData, bool isNativeMarshallingAttribute) - { - if (nativeMarshalerAttributeData.ConstructorArguments.Length == 0) - { - // This is a MarshalUsing with just count information. - return; - } - - if (nativeMarshalerAttributeData.ConstructorArguments[0].IsNull) - { - context.ReportDiagnostic( - nativeMarshalerAttributeData.CreateDiagnostic( - NativeTypeMustBeNonNullRule, - type.ToDisplayString())); - return; - } - - ITypeSymbol nativeType = (ITypeSymbol)nativeMarshalerAttributeData.ConstructorArguments[0].Value!; - ISymbol nativeTypeDiagnosticsTargetSymbol = nativeType; - - if (nativeType is not INamedTypeSymbol marshalerType) - { - context.ReportDiagnostic( - GetDiagnosticLocations(context, nativeType, nativeMarshalerAttributeData).CreateDiagnostic( - NativeTypeMustHaveRequiredShapeRule, - nativeType.ToDisplayString(), - type.ToDisplayString())); - return; - } - - DiagnosticDescriptor requiredShapeRule = NativeTypeMustHaveRequiredShapeRule; - - ManualTypeMarshallingHelper.NativeTypeMarshallingVariant variant = ManualTypeMarshallingHelper.NativeTypeMarshallingVariant.Standard; - if (marshalerType.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(_genericContiguousCollectionMarshallerAttribute, a.AttributeClass))) - { - variant = ManualTypeMarshallingHelper.NativeTypeMarshallingVariant.ContiguousCollection; - requiredShapeRule = CollectionNativeTypeMustHaveRequiredShapeRule; - if (!ManualTypeMarshallingHelper.TryGetManagedValuesProperty(marshalerType, out _) - || !ManualTypeMarshallingHelper.HasNativeValueStorageProperty(marshalerType, _spanOfByte)) - { - context.ReportDiagnostic( - GetDiagnosticLocations(context, marshalerType, nativeMarshalerAttributeData).CreateDiagnostic( - requiredShapeRule, - nativeType.ToDisplayString(), - type.ToDisplayString())); - return; - } - } - - if (!nativeType.IsValueType) - { - context.ReportDiagnostic( - GetDiagnosticLocations(context, nativeType, nativeMarshalerAttributeData).CreateDiagnostic( - requiredShapeRule, - nativeType.ToDisplayString(), - type.ToDisplayString())); - return; - } - - if (marshalerType.IsUnboundGenericType) - { - if (!isNativeMarshallingAttribute) - { - context.ReportDiagnostic( - nativeMarshalerAttributeData.CreateDiagnostic( - NativeGenericTypeMustBeClosedOrMatchArityRule, - nativeType.ToDisplayString(), - type.ToDisplayString())); - return; - } - if (type is not INamedTypeSymbol namedType || marshalerType.TypeArguments.Length != namedType.TypeArguments.Length) - { - context.ReportDiagnostic( - nativeMarshalerAttributeData.CreateDiagnostic( - NativeGenericTypeMustBeClosedOrMatchArityRule, - nativeType.ToDisplayString(), - type.ToDisplayString())); - return; - } - // Construct the marshaler type around the same type arguments as the managed type. - nativeType = marshalerType = marshalerType.ConstructedFrom.Construct(namedType.TypeArguments, namedType.TypeArgumentNullableAnnotations); - } - - bool hasConstructor = false; - bool hasCallerAllocSpanConstructor = false; - foreach (IMethodSymbol ctor in marshalerType.Constructors) - { - if (ctor.IsStatic) - { - continue; - } - - hasConstructor = hasConstructor || ManualTypeMarshallingHelper.IsManagedToNativeConstructor(ctor, type, variant); - - if (!hasCallerAllocSpanConstructor && ManualTypeMarshallingHelper.IsCallerAllocatedSpanConstructor(ctor, type, _spanOfByte, variant)) - { - hasCallerAllocSpanConstructor = true; - IFieldSymbol bufferSizeField = nativeType.GetMembers(ManualTypeMarshallingHelper.BufferSizeFieldName).OfType().FirstOrDefault(); - if (bufferSizeField is null or { DeclaredAccessibility: not Accessibility.Public } or { IsConst: false } or { Type: not { SpecialType: SpecialType.System_Int32 } }) - { - context.ReportDiagnostic( - GetDiagnosticLocations(context, ctor, nativeMarshalerAttributeData).CreateDiagnostic( - CallerAllocConstructorMustHaveBufferSizeConstantRule, - nativeType.ToDisplayString())); - } - } - } - - bool hasToManaged = ManualTypeMarshallingHelper.HasToManagedMethod(marshalerType, type); - - // Validate that the native type has at least one marshalling method (either managed to native or native to managed) - if (!hasConstructor && !hasCallerAllocSpanConstructor && !hasToManaged) - { - context.ReportDiagnostic( - GetDiagnosticLocations(context, marshalerType, nativeMarshalerAttributeData).CreateDiagnostic( - requiredShapeRule, - marshalerType.ToDisplayString(), - type.ToDisplayString())); - } - - // Validate that this type can support marshalling when stackalloc is not usable. - if (isNativeMarshallingAttribute && hasCallerAllocSpanConstructor && !hasConstructor) - { - context.ReportDiagnostic( - GetDiagnosticLocations(context, marshalerType, nativeMarshalerAttributeData).CreateDiagnostic( - CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackRule, - marshalerType.ToDisplayString())); - } - - IPropertySymbol? valueProperty = ManualTypeMarshallingHelper.FindValueProperty(nativeType); - bool valuePropertyIsRefReturn = valueProperty is { ReturnsByRef: true } or { ReturnsByRefReadonly: true }; - - if (valueProperty is not null) - { - if (valuePropertyIsRefReturn) - { - context.ReportDiagnostic( - GetDiagnosticLocations(context, valueProperty, nativeMarshalerAttributeData).CreateDiagnostic( - RefValuePropertyUnsupportedRule, - marshalerType.ToDisplayString())); - } - - nativeType = valueProperty.Type; - nativeTypeDiagnosticsTargetSymbol = valueProperty; - - // Validate that we don't have partial implementations. - // We error if either of the conditions below are partially met but not fully met: - // - a constructor and a Value property getter - // - a ToManaged method and a Value property setter - if ((hasConstructor || hasCallerAllocSpanConstructor) && valueProperty.GetMethod is null) - { - context.ReportDiagnostic( - GetDiagnosticLocations(context, valueProperty, nativeMarshalerAttributeData).CreateDiagnostic( - ValuePropertyMustHaveGetterRule, - marshalerType.ToDisplayString())); - } - if (hasToManaged && valueProperty.SetMethod is null) - { - context.ReportDiagnostic( - GetDiagnosticLocations(context, valueProperty, nativeMarshalerAttributeData).CreateDiagnostic( - ValuePropertyMustHaveSetterRule, - marshalerType.ToDisplayString())); - } - } - else if (ManualTypeMarshallingHelper.FindGetPinnableReference(marshalerType) is IMethodSymbol marshallerGetPinnableReferenceMethod) - { - // If we don't have a Value property, then we disallow a GetPinnableReference on the marshaler type. - // We do this since there is no valid use case that we can think of for a GetPinnableReference on a blittable type - // being a requirement to calculate the value of the fields of the same blittable instance, - // so we're pre-emptively blocking this until a use case is discovered. - context.ReportDiagnostic( - marshallerGetPinnableReferenceMethod.CreateDiagnostic( - MarshallerGetPinnableReferenceRequiresValuePropertyRule, - nativeType.ToDisplayString())); - } - - if (!nativeType.IsConsideredBlittable()) - { - context.ReportDiagnostic( - GetDiagnosticLocations(context, nativeTypeDiagnosticsTargetSymbol, nativeMarshalerAttributeData).CreateDiagnostic( - NativeTypeMustBeBlittableRule, - nativeType.ToDisplayString(), - type.ToDisplayString())); - } - - if (isNativeMarshallingAttribute && ManualTypeMarshallingHelper.FindGetPinnableReference(type) is IMethodSymbol managedGetPinnableReferenceMethod) - { - if (!managedGetPinnableReferenceMethod.ReturnType.IsConsideredBlittable()) - { - context.ReportDiagnostic(managedGetPinnableReferenceMethod.CreateDiagnostic(GetPinnableReferenceReturnTypeBlittableRule)); - } - // Validate that our marshaler supports scenarios where GetPinnableReference cannot be used. - if (isNativeMarshallingAttribute && (!hasConstructor || valueProperty is { GetMethod: null })) - { - context.ReportDiagnostic( - nativeMarshalerAttributeData.CreateDiagnostic( - GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule, - type.ToDisplayString())); - } - - // If the managed type has a GetPinnableReference method, make sure that the Value getter is also a pointer-sized primitive. - // This ensures that marshalling via pinning the managed value and marshalling via the default marshaller will have the same ABI. - if (!valuePropertyIsRefReturn // Ref returns are already reported above as invalid, so don't issue another warning here about them - && nativeType is not ( - IPointerTypeSymbol or - { SpecialType: SpecialType.System_IntPtr } or - { SpecialType: SpecialType.System_UIntPtr })) - { - IMethodSymbol getPinnableReferenceMethodToMention = managedGetPinnableReferenceMethod; - - context.ReportDiagnostic( - GetDiagnosticLocations(context, nativeTypeDiagnosticsTargetSymbol, nativeMarshalerAttributeData).CreateDiagnostic( - NativeTypeMustBePointerSizedRule, - nativeType.ToDisplayString(), - getPinnableReferenceMethodToMention.ContainingType.ToDisplayString())); - } - } - } - - private ImmutableArray GetDiagnosticLocations(SymbolAnalysisContext context, ISymbol targetSymbol, AttributeData marshallingAttribute) - { - // If we're using a compilation that references another compilation, the symbol locations can be in source in the wrong compilation, - // which can cause exceptions when reporting diagnostics. Make sure the symbol is defined in the current Compilation's source module before using its locations. - // If the symbol is not defined in the current Compilation's source module, report the diagnostic at the marshalling attribute's location. - if (SymbolEqualityComparer.Default.Equals(context.Compilation.SourceModule, targetSymbol.ContainingModule)) - { - return targetSymbol.Locations; - } - return ImmutableArray.Create(marshallingAttribute.ApplicationSyntaxReference?.GetSyntax().GetLocation() ?? Location.None); - } - } - } -} diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/SyntaxGeneratorExtensions.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/SyntaxGeneratorExtensions.cs new file mode 100644 index 0000000000000..1bda8e4cc7765 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/SyntaxGeneratorExtensions.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editing; + +namespace Microsoft.Interop.Analyzers +{ + internal static class SyntaxGeneratorExtensions + { + public static SyntaxNode GetEnumValueAsFlagsExpression(this SyntaxGenerator gen, ITypeSymbol enumType, object value, bool includeZeroValueFlags) + { + if (enumType.TypeKind != TypeKind.Enum) + { + throw new ArgumentException(nameof(enumType)); + } + + SpecialType underlyingType = ((INamedTypeSymbol)enumType).EnumUnderlyingType.SpecialType; + + if (!underlyingType.IsIntegralType()) + { + return gen.CastExpression(gen.TypeExpression(underlyingType), gen.LiteralExpression(value)); + } + + ulong valueToMatch = GetNumericValue(value); + + ulong currentlyMatchedFlags = 0; + SyntaxNode? enumValueSyntax = null; + foreach (ISymbol member in enumType.GetMembers()) + { + if (member is IFieldSymbol { HasConstantValue: true } enumValue) + { + ulong fieldNumericValue = GetNumericValue(enumValue.ConstantValue); + if (fieldNumericValue == 0 && !includeZeroValueFlags) + { + continue; + } + if ((fieldNumericValue & valueToMatch) == fieldNumericValue) + { + currentlyMatchedFlags |= fieldNumericValue; + enumValueSyntax = enumValueSyntax is null + ? gen.MemberAccessExpression(gen.TypeExpression(enumType), enumValue.Name) + : gen.BitwiseOrExpression(enumValueSyntax, gen.MemberAccessExpression(gen.TypeExpression(enumType), enumValue.Name)); + } + } + } + + // Unable to represent the value as the enum flags. Just use the literal value cast as the enum type. + if (currentlyMatchedFlags != valueToMatch) + { + return gen.CastExpression(gen.TypeExpression(underlyingType), gen.LiteralExpression(value)); + } + + return enumValueSyntax; + + static ulong GetNumericValue(object value) => value switch + { + byte or ushort or uint or ulong => Convert.ToUInt64(value), + sbyte or short or int or long => (ulong)Convert.ToInt64(value), + _ => throw new UnreachableException() + }; + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/PInvokeStubCodeGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/PInvokeStubCodeGenerator.cs index fc0baa4b2d0dd..ec7a770472ac2 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/PInvokeStubCodeGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/PInvokeStubCodeGenerator.cs @@ -145,7 +145,7 @@ public PInvokeStubCodeGenerator( // // [LibraryImport(NativeExportsNE_Binary, EntryPoint = "transpose_matrix")] // [return: MarshalUsing(CountElementName = "numColumns")] - // [return: MarshalUsing(CountElementName = "numRows", ElementIndirectionLevel = 1)] + // [return: MarshalUsing(CountElementName = "numRows", ElementIndirectionDepth = 1)] // public static partial int[][] TransposeMatrix( // int[][] matrix, // [MarshalUsing(CountElementName="numColumns")] ref int[] numRows, diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources.Designer.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources.Designer.cs index fcad506c63500..5e4628b58dde0 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources.Designer.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources.Designer.cs @@ -10,8 +10,8 @@ namespace Microsoft.Interop { using System; - - + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -23,15 +23,15 @@ namespace Microsoft.Interop { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { - + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// @@ -45,7 +45,7 @@ internal Resources() { return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. @@ -59,43 +59,61 @@ internal Resources() { resourceCulture = value; } } - + /// - /// Looks up a localized string similar to A type marked with 'BlittableTypeAttribute' must be blittable.. + /// Looks up a localized string similar to Add missing custom type marshaller members. /// - internal static string BlittableTypeMustBeBlittableDescription { + internal static string AddMissingCustomTypeMarshallerMembers { get { - return ResourceManager.GetString("BlittableTypeMustBeBlittableDescription", resourceCulture); + return ResourceManager.GetString("AddMissingCustomTypeMarshallerMembers", resourceCulture); } } - + /// - /// Looks up a localized string similar to Type '{0}' is marked with 'BlittableTypeAttribute' but is not blittable. + /// Looks up a localized string similar to Add missing features to the 'CustomTypeMarshallerAttribute' attribute. /// - internal static string BlittableTypeMustBeBlittableMessage { + internal static string AddMissingFeaturesToCustomTypeMarshaller { get { - return ResourceManager.GetString("BlittableTypeMustBeBlittableMessage", resourceCulture); + return ResourceManager.GetString("AddMissingFeaturesToCustomTypeMarshaller", resourceCulture); } } - + /// - /// Looks up a localized string similar to When a constructor taking a Span<byte> is specified on the native type, the type must also have a public integer constant named BufferSize to provide the size of the caller-allocated buffer.. + /// Looks up a localized string similar to A marshaller type that provides a constructor taking a caller-allocated 'Span<byte>' should specify that it supports the 'CallerAllocatedBuffer' feature.. /// - internal static string CallerAllocConstructorMustHaveBufferSizeConstantDescription { + internal static string CallerAllocatedBufferConstructorProvidedShouldSpecifyFeatureDescription { get { - return ResourceManager.GetString("CallerAllocConstructorMustHaveBufferSizeConstantDescription", resourceCulture); + return ResourceManager.GetString("CallerAllocatedBufferConstructorProvidedShouldSpecifyFeatureDescription", resourceCulture); } } - + /// - /// Looks up a localized string similar to The native type '{0}' must have a 'public const int BufferSize' field that specifies the size of the stack buffer because it has a constructor that takes a caller-allocated Span<byte>. + /// Looks up a localized string similar to The type '{0}' provides a constructor taking a caller-allocated 'Span<byte>' but does not specify that it supports the 'CallerAllocatedBuffer' feature. The constructor will not be used unless the feature is specified.. /// - internal static string CallerAllocConstructorMustHaveBufferSizeConstantMessage { + internal static string CallerAllocatedBufferConstructorProvidedShouldSpecifyFeatureMessage { get { - return ResourceManager.GetString("CallerAllocConstructorMustHaveBufferSizeConstantMessage", resourceCulture); + return ResourceManager.GetString("CallerAllocatedBufferConstructorProvidedShouldSpecifyFeatureMessage", resourceCulture); } } - + + /// + /// Looks up a localized string similar to When a constructor taking a 'Span<byte>' is specified on the native type, the type must set the BufferSize field on the 'System.Runtime.InteropServices.CustomTypeMarshallerAttribute' to provide the size of the caller-allocated buffer.. + /// + internal static string CallerAllocConstructorMustHaveBufferSizeDescription { + get { + return ResourceManager.GetString("CallerAllocConstructorMustHaveBufferSizeDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The native type '{0}' must set the 'BufferSize' field on the applied 'System.Runtime.InteropServices.CustomTypeMarshallerAttribute' to specify the size of the caller-allocated buffer because it has a constructor that takes a caller-allocated 'Span<byte>'. + /// + internal static string CallerAllocConstructorMustHaveBufferSizeMessage { + get { + return ResourceManager.GetString("CallerAllocConstructorMustHaveBufferSizeMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to A type that supports marshalling from managed to native using a caller-allocated buffer should also support marshalling from managed to native where using a caller-allocated buffer is impossible.. /// @@ -104,7 +122,7 @@ internal static string CallerAllocMarshallingShouldSupportAllocatingMarshallingF return ResourceManager.GetString("CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Native type '{0}' has a constructor taking a caller-allocated buffer, but does not support marshalling in scenarios where using a caller-allocated buffer is impossible. /// @@ -113,7 +131,7 @@ internal static string CallerAllocMarshallingShouldSupportAllocatingMarshallingF return ResourceManager.GetString("CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to The generated 'DllImportAttribute' will not have a value corresponding to '{0}'.. /// @@ -122,7 +140,7 @@ internal static string CannotForwardToDllImportDescription { return ResourceManager.GetString("CannotForwardToDllImportDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to '{0}' has no equivalent in 'DllImportAtttribute' and will not be forwarded. /// @@ -131,7 +149,7 @@ internal static string CannotForwardToDllImportMessage { return ResourceManager.GetString("CannotForwardToDllImportMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to Specified 'LibraryImportAttribute' arguments cannot be forwarded to 'DllImportAttribute'. /// @@ -140,43 +158,7 @@ internal static string CannotForwardToDllImportTitle { return ResourceManager.GetString("CannotForwardToDllImportTitle", resourceCulture); } } - - /// - /// Looks up a localized string similar to The 'BlittableTypeAttribute' and 'NativeMarshallingAttribute' attributes are mutually exclusive.. - /// - internal static string CannotHaveMultipleMarshallingAttributesDescription { - get { - return ResourceManager.GetString("CannotHaveMultipleMarshallingAttributesDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type '{0}' is marked with 'BlittableTypeAttribute' and 'NativeMarshallingAttribute'. A type can only have one of these two attributes.. - /// - internal static string CannotHaveMultipleMarshallingAttributesMessage { - get { - return ResourceManager.GetString("CannotHaveMultipleMarshallingAttributesMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A native type with the 'GenericContiguousCollectionMarshallerAttribute' must have at least one of the two marshalling methods as well as a 'ManagedValues' property of type 'Span<T>' for some 'T' and a 'NativeValueStorage' property of type 'Span<byte>' to enable marshalling the managed type.. - /// - internal static string CollectionNativeTypeMustHaveRequiredShapeDescription { - get { - return ResourceManager.GetString("CollectionNativeTypeMustHaveRequiredShapeDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The native type '{0}' must be a value type and have a constructor that takes two parameters, one of type '{1}' and an 'int', or have a parameterless instance method named 'ToManaged' that returns '{1}' as well as a 'ManagedValues' property of type 'Span<T>' for some 'T' and a 'NativeValueStorage' property of type 'Span<byte>'. - /// - internal static string CollectionNativeTypeMustHaveRequiredShapeMessage { - get { - return ResourceManager.GetString("CollectionNativeTypeMustHaveRequiredShapeMessage", resourceCulture); - } - } - + /// /// Looks up a localized string similar to Source-generated P/Invokes will ignore any configuration that is not supported.. /// @@ -185,7 +167,7 @@ internal static string ConfigurationNotSupportedDescription { return ResourceManager.GetString("ConfigurationNotSupportedDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to The '{0}' configuration is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead.. /// @@ -194,7 +176,7 @@ internal static string ConfigurationNotSupportedMessage { return ResourceManager.GetString("ConfigurationNotSupportedMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to The specified marshalling configuration is not supported by source-generated P/Invokes. {0}.. /// @@ -203,7 +185,7 @@ internal static string ConfigurationNotSupportedMessageMarshallingInfo { return ResourceManager.GetString("ConfigurationNotSupportedMessageMarshallingInfo", resourceCulture); } } - + /// /// Looks up a localized string similar to The specified '{0}' configuration for parameter '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead.. /// @@ -212,7 +194,7 @@ internal static string ConfigurationNotSupportedMessageParameter { return ResourceManager.GetString("ConfigurationNotSupportedMessageParameter", resourceCulture); } } - + /// /// Looks up a localized string similar to The specified '{0}' configuration for the return value of method '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead.. /// @@ -221,7 +203,7 @@ internal static string ConfigurationNotSupportedMessageReturn { return ResourceManager.GetString("ConfigurationNotSupportedMessageReturn", resourceCulture); } } - + /// /// Looks up a localized string similar to The specified value '{0}' for '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead.. /// @@ -230,7 +212,7 @@ internal static string ConfigurationNotSupportedMessageValue { return ResourceManager.GetString("ConfigurationNotSupportedMessageValue", resourceCulture); } } - + /// /// Looks up a localized string similar to Specified configuration is not supported by source-generated P/Invokes.. /// @@ -239,16 +221,16 @@ internal static string ConfigurationNotSupportedTitle { return ResourceManager.GetString("ConfigurationNotSupportedTitle", resourceCulture); } } - + /// - /// Looks up a localized string similar to Only one of 'ConstantElementCount' or 'ElementCountInfo' may be used in a 'MarshalUsingAttribute' for a given 'ElementIndirectionLevel'. + /// Looks up a localized string similar to Only one of 'ConstantElementCount' or 'ElementCountInfo' may be used in a 'MarshalUsingAttribute' for a given 'ElementIndirectionDepth'. /// internal static string ConstantAndElementCountInfoDisallowed { get { return ResourceManager.GetString("ConstantAndElementCountInfoDisallowed", resourceCulture); } } - + /// /// Looks up a localized string similar to Automatically converting a P/Invoke with 'PreserveSig' set to 'false' to a source-generated P/Invoke may produce invalid code. /// @@ -257,7 +239,7 @@ internal static string ConvertNoPreserveSigDllImportToGeneratedMayProduceInvalid return ResourceManager.GetString("ConvertNoPreserveSigDllImportToGeneratedMayProduceInvalidCode", resourceCulture); } } - + /// /// Looks up a localized string similar to Convert to 'LibraryImport'. /// @@ -266,7 +248,7 @@ internal static string ConvertToLibraryImport { return ResourceManager.GetString("ConvertToLibraryImport", resourceCulture); } } - + /// /// Looks up a localized string similar to Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time. /// @@ -275,7 +257,7 @@ internal static string ConvertToLibraryImportDescription { return ResourceManager.GetString("ConvertToLibraryImportDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time. /// @@ -284,7 +266,7 @@ internal static string ConvertToLibraryImportMessage { return ResourceManager.GetString("ConvertToLibraryImportMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time. /// @@ -293,7 +275,7 @@ internal static string ConvertToLibraryImportTitle { return ResourceManager.GetString("ConvertToLibraryImportTitle", resourceCulture); } } - + /// /// Looks up a localized string similar to Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.. /// @@ -302,7 +284,7 @@ internal static string ConvertToLibraryImportWarning { return ResourceManager.GetString("ConvertToLibraryImportWarning", resourceCulture); } } - + /// /// Looks up a localized string similar to Convert to 'LibraryImport' with '{0}' suffix. /// @@ -311,7 +293,43 @@ internal static string ConvertToLibraryImportWithSuffix { return ResourceManager.GetString("ConvertToLibraryImportWithSuffix", resourceCulture); } } - + + /// + /// Looks up a localized string similar to A native must set the 'Direction' property on the 'CustomTypeMarshallerAttribute' to a value that sets at least one known flag value on the 'CustomTypeMarshallerDirection' enum. + /// + internal static string CustomMarshallerTypeMustSupportDirectionDescription { + get { + return ResourceManager.GetString("CustomMarshallerTypeMustSupportDirectionDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The native type '{0}' must set the 'Direction' property on the 'CustomTypeMarshallerAttribute' to a value that sets at least one known flag value on the 'CustomTypeMarshallerDirection' enum. + /// + internal static string CustomMarshallerTypeMustSupportDirectionMessage { + get { + return ResourceManager.GetString("CustomMarshallerTypeMustSupportDirectionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The 'CustomTypeMarshallerAttribute' attribute must be semantically valid. + /// + internal static string CustomTypeMarshallerAttributeMustBeValidDescription { + get { + return ResourceManager.GetString("CustomTypeMarshallerAttributeMustBeValidDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The 'CustomTypeMarshallerAttribute' on '{0}' is semantically invalid. + /// + internal static string CustomTypeMarshallerAttributeMustBeValidMessage { + get { + return ResourceManager.GetString("CustomTypeMarshallerAttributeMustBeValidMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to The specified parameter needs to be marshalled from managed to native, but the native type '{0}' does not support it.. /// @@ -320,7 +338,7 @@ internal static string CustomTypeMarshallingManagedToNativeUnsupported { return ResourceManager.GetString("CustomTypeMarshallingManagedToNativeUnsupported", resourceCulture); } } - + /// /// Looks up a localized string similar to The specified parameter needs to be marshalled from native to managed, but the native type '{0}' does not support it.. /// @@ -329,7 +347,43 @@ internal static string CustomTypeMarshallingNativeToManagedUnsupported { return ResourceManager.GetString("CustomTypeMarshallingNativeToManagedUnsupported", resourceCulture); } } - + + /// + /// Looks up a localized string similar to A marshaller type that provides a 'FreeNative' method should specify that it supports the 'UnmanagedResources' feature.. + /// + internal static string FreeNativeMethodProvidedShouldSpecifyUnmanagedResourcesFeatureDescription { + get { + return ResourceManager.GetString("FreeNativeMethodProvidedShouldSpecifyUnmanagedResourcesFeatureDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' provides a 'FreeNative' method but does not specify that it supports the 'UnmanagedResources' feature. The method will not be used unless the feature is specified.. + /// + internal static string FreeNativeMethodProvidedShouldSpecifyUnmanagedResourcesFeatureMessage { + get { + return ResourceManager.GetString("FreeNativeMethodProvidedShouldSpecifyUnmanagedResourcesFeatureMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A marshaller type that provides a 'FromNativeValue' method should specify that it supports the 'TwoStageMarshalling' feature.. + /// + internal static string FromNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureDescription { + get { + return ResourceManager.GetString("FromNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' provides a 'FromNativeValue' method but does not specify that it supports the 'TwoStageMarshalling' feature. The method will not be used unless the feature is specified.. + /// + internal static string FromNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureMessage { + get { + return ResourceManager.GetString("FromNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to The return type of 'GetPinnableReference' (after accounting for 'ref') must be blittable.. /// @@ -338,7 +392,7 @@ internal static string GetPinnableReferenceReturnTypeBlittableDescription { return ResourceManager.GetString("GetPinnableReferenceReturnTypeBlittableDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to The dereferenced type of the return type of the 'GetPinnableReference' method must be blittable. /// @@ -347,7 +401,7 @@ internal static string GetPinnableReferenceReturnTypeBlittableMessage { return ResourceManager.GetString("GetPinnableReferenceReturnTypeBlittableMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to A type that supports marshalling from managed to native by pinning should also support marshalling from managed to native where pinning is impossible.. /// @@ -356,7 +410,7 @@ internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFal return ResourceManager.GetString("GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Type '{0}' has a 'GetPinnableReference' method but its native type does not support marshalling in scenarios where pinning is impossible. /// @@ -365,7 +419,25 @@ internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFal return ResourceManager.GetString("GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage", resourceCulture); } } - + + /// + /// Looks up a localized string similar to The 'TwoStageMarshalling' feature requires a 'TNativeType ToNativeValue()' method for the 'In' direction.. + /// + internal static string InTwoStageMarshallingRequiresToNativeValueDescription { + get { + return ResourceManager.GetString("InTwoStageMarshallingRequiresToNativeValueDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The marshaller type '{0}' supports marshalling in the 'In' direction with the 'TwoStageMarshalling' feature must provide a 'ToNativeValue' instance method that returns the native value. + /// + internal static string InTwoStageMarshallingRequiresToNativeValueMessage { + get { + return ResourceManager.GetString("InTwoStageMarshallingRequiresToNativeValueMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'.. /// @@ -374,7 +446,7 @@ internal static string InvalidAttributedMethodContainingTypeMissingModifiersMess return ResourceManager.GetString("InvalidAttributedMethodContainingTypeMissingModifiersMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to Methods marked with 'LibraryImportAttribute' should be 'static', 'partial', and non-generic. P/Invoke source generation will ignore methods that are non-'static', non-'partial', or generic.. /// @@ -383,7 +455,7 @@ internal static string InvalidAttributedMethodDescription { return ResourceManager.GetString("InvalidAttributedMethodDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to Method '{0}' should be 'static', 'partial', and non-generic when marked with 'LibraryImportAttribute'. P/Invoke source generation will ignore method '{0}'.. /// @@ -392,7 +464,7 @@ internal static string InvalidAttributedMethodSignatureMessage { return ResourceManager.GetString("InvalidAttributedMethodSignatureMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to Invalid 'LibraryImportAttribute' usage. /// @@ -401,7 +473,7 @@ internal static string InvalidLibraryImportAttributeUsageTitle { return ResourceManager.GetString("InvalidLibraryImportAttributeUsageTitle", resourceCulture); } } - + /// /// Looks up a localized string similar to The configuration of 'StringMarshalling' and 'StringMarshallingCustomType' is invalid.. /// @@ -410,7 +482,7 @@ internal static string InvalidStringMarshallingConfigurationDescription { return ResourceManager.GetString("InvalidStringMarshallingConfigurationDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to The configuration of 'StringMarshalling' and 'StringMarshallingCustomType' on method '{0}' is invalid. {1}. /// @@ -419,7 +491,7 @@ internal static string InvalidStringMarshallingConfigurationMessage { return ResourceManager.GetString("InvalidStringMarshallingConfigurationMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to 'StringMarshallingCustomType' must be specified when 'StringMarshalling' is set to 'StringMarshalling.Custom'.. /// @@ -428,7 +500,7 @@ internal static string InvalidStringMarshallingConfigurationMissingCustomType { return ResourceManager.GetString("InvalidStringMarshallingConfigurationMissingCustomType", resourceCulture); } } - + /// /// Looks up a localized string similar to 'StringMarshalling' should be set to 'StringMarshalling.Custom' when 'StringMarshallingCustomType' is specified.. /// @@ -437,34 +509,187 @@ internal static string InvalidStringMarshallingConfigurationNotCustom { return ResourceManager.GetString("InvalidStringMarshallingConfigurationNotCustom", resourceCulture); } } - + /// - /// Looks up a localized string similar to The use cases for 'GetPinnableReference' are not applicable in any scenarios where a 'Value' property is not also required.. + /// Looks up a localized string similar to The element type of the 'ReadOnlySpan' returned by 'GetManagedValuesSource' must be the same as the element type returned by 'GetManagedValuesDestination'.. /// - internal static string MarshallerGetPinnableReferenceRequiresValuePropertyDescription { + internal static string LinearCollectionElementTypesMustMatchDescription { get { - return ResourceManager.GetString("MarshallerGetPinnableReferenceRequiresValuePropertyDescription", resourceCulture); + return ResourceManager.GetString("LinearCollectionElementTypesMustMatchDescription", resourceCulture); } } - + /// - /// Looks up a localized string similar to The 'GetPinnableReference' method cannot be provided on the native type '{0}' unless a 'Value' property is also provided. + /// Looks up a localized string similar to The element type of the 'ReadOnlySpan' returned by 'GetManagedValuesSource' must be the same as the element type returned by 'GetManagedValuesDestination'. /// - internal static string MarshallerGetPinnableReferenceRequiresValuePropertyMessage { + internal static string LinearCollectionElementTypesMustMatchMessage { get { - return ResourceManager.GetString("MarshallerGetPinnableReferenceRequiresValuePropertyMessage", resourceCulture); + return ResourceManager.GetString("LinearCollectionElementTypesMustMatchMessage", resourceCulture); } } - + /// - /// Looks up a localized string similar to The native type '{0}' must be a closed generic so the emitted code can use a specific instantiation.. + /// Looks up a localized string similar to A 'LinearCollection'-kind native type that supports the 'CallerAllocatedBuffer' feature must provide a three-parameter constructor taking the managed type as the first parameter, a 'Span<byte>' as the second parameter, and the native size of the element as the third parameter. /// - internal static string NativeGenericTypeMustBeClosedDescription { + internal static string LinearCollectionInCallerAllocatedBufferRequiresSpanConstructorDescription { get { - return ResourceManager.GetString("NativeGenericTypeMustBeClosedDescription", resourceCulture); + return ResourceManager.GetString("LinearCollectionInCallerAllocatedBufferRequiresSpanConstructorDescription", resourceCulture); } } - + + /// + /// Looks up a localized string similar to The type '{0}' specifies that it supports 'In' marshalling with the 'CallerAllocatedBuffer' feature for '{1}' but does not provide a three-parameter constructor that takes a '{1}' , a 'Span<byte>', and an 'int'. + /// + internal static string LinearCollectionInCallerAllocatedBufferRequiresSpanConstructorMessage { + get { + return ResourceManager.GetString("LinearCollectionInCallerAllocatedBufferRequiresSpanConstructorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A 'LinearCollection'-kind native type that supports marshalling in the 'In' direction must provide a 'GetManagedValuesSource' that returns a 'ReadOnlySpan<>' and a 'GetNativeValuesDestination' method that returns a 'Span<byte>'.. + /// + internal static string LinearCollectionInRequiresCollectionMethodsDescription { + get { + return ResourceManager.GetString("LinearCollectionInRequiresCollectionMethodsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' specifies that is supports marshalling in the 'In' direction, but it does not provide a 'GetManagedValuesSource' that returns a 'ReadOnlySpan<>' and a 'GetNativeValuesDestination' method that returns a 'Span<byte>'. + /// + internal static string LinearCollectionInRequiresCollectionMethodsMessage { + get { + return ResourceManager.GetString("LinearCollectionInRequiresCollectionMethodsMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A 'LinearCollection'-kind native type must provide a two-parameter constructor taking the managed type as the first parameter and the native size of the element as the second parameter. + /// + internal static string LinearCollectionInRequiresTwoParameterConstructorDescription { + get { + return ResourceManager.GetString("LinearCollectionInRequiresTwoParameterConstructorDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' specifies that it supports 'In' marshalling of '{1}' but does not provide a two-parameter constructor that takes a '{1}' as the first parameter and an 'int' as the second parameter. + /// + internal static string LinearCollectionInRequiresTwoParameterConstructorMessage { + get { + return ResourceManager.GetString("LinearCollectionInRequiresTwoParameterConstructorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A 'LinearCollection'-kind native type that supports marshalling in the 'Out' direction must provide a 'GetManagedValuesDestination' that takes an 'int' and returns a 'Span<>' and a 'GetNativeValuesSource' method that takes an 'int' and rreturns a 'ReadOnlySpan<byte>'.. + /// + internal static string LinearCollectionOutRequiresCollectionMethodsDescription { + get { + return ResourceManager.GetString("LinearCollectionOutRequiresCollectionMethodsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' specifies that it supports marshalling in the 'Out' direction, but it does not provide a 'GetManagedValuesDestination' that takes an 'int' and returns a 'Span<>' and a 'GetNativeValuesSource' method that takes an 'int' and rreturns a 'ReadOnlySpan<byte>'. + /// + internal static string LinearCollectionOutRequiresCollectionMethodsMessage { + get { + return ResourceManager.GetString("LinearCollectionOutRequiresCollectionMethodsMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A 'LinearCollection'-kind native type that supports marshalling in the 'Out' direction must provide a constructor that takes the size of the native element as an 'int'.. + /// + internal static string LinearCollectionOutRequiresIntConstructorDescription { + get { + return ResourceManager.GetString("LinearCollectionOutRequiresIntConstructorDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' specifies that it supports marshalling in the 'Out' direction, but it does not provide a constructor that takes the size of the native element as an 'int'.. + /// + internal static string LinearCollectionOutRequiresIntConstructorMessage { + get { + return ResourceManager.GetString("LinearCollectionOutRequiresIntConstructorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified marshaller direction must be a valid value of the 'System.Runtime.InteropServices.CustomMarshallerDirection' enum.. + /// + internal static string MarshallerDirectionMustBeValidDescription { + get { + return ResourceManager.GetString("MarshallerDirectionMustBeValidDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified custom marshaller direction for '{0}' is invalid. + /// + internal static string MarshallerDirectionMustBeValidMessage { + get { + return ResourceManager.GetString("MarshallerDirectionMustBeValidMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The use cases for 'GetPinnableReference' are not applicable in any scenarios where 'TwoStageMarshalling' is not also required.. + /// + internal static string MarshallerGetPinnableReferenceRequiresTwoStageMarshallingDescription { + get { + return ResourceManager.GetString("MarshallerGetPinnableReferenceRequiresTwoStageMarshallingDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The 'GetPinnableReference' method cannot be provided on the native type '{0}' unless the 'TwoStageMarshalling' feature is also supported. + /// + internal static string MarshallerGetPinnableReferenceRequiresTwoStageMarshallingMessage { + get { + return ResourceManager.GetString("MarshallerGetPinnableReferenceRequiresTwoStageMarshallingMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified marshaller kind must be a valid value of the 'System.Runtime.InteropServices.CustomMarshallerKind' enum.. + /// + internal static string MarshallerKindMustBeValidDescription { + get { + return ResourceManager.GetString("MarshallerKindMustBeValidDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified custom marshaller kind for '{0}' is invalid. + /// + internal static string MarshallerKindMustBeValidMessage { + get { + return ResourceManager.GetString("MarshallerKindMustBeValidMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A type with a 'System.Runtime.InteropServices.CustomTypeMarshallerAttribute' must specify a managed type. + /// + internal static string MarshallerTypeMustSpecifyManagedTypeDescription { + get { + return ResourceManager.GetString("MarshallerTypeMustSpecifyManagedTypeDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' does not specify a managed type in the 'System.Runtime.InteropServices.CustomTypeMarshallerAttribute' applied to the type. + /// + internal static string MarshallerTypeMustSpecifyManagedTypeMessage { + get { + return ResourceManager.GetString("MarshallerTypeMustSpecifyManagedTypeMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to The native type '{0}' must be a closed generic or have the same number of generic parameters as the managed type so the emitted code can use a specific instantiation.. /// @@ -473,7 +698,7 @@ internal static string NativeGenericTypeMustBeClosedOrMatchArityDescription { return ResourceManager.GetString("NativeGenericTypeMustBeClosedOrMatchArityDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to The native type '{0}' for managed type '{1}' must be a closed generic type or have the same arity as the managed type.. /// @@ -482,106 +707,115 @@ internal static string NativeGenericTypeMustBeClosedOrMatchArityMessage { return ResourceManager.GetString("NativeGenericTypeMustBeClosedOrMatchArityMessage", resourceCulture); } } - + /// - /// Looks up a localized string similar to A native type for a given type must be blittable.. + /// Looks up a localized string similar to A native type must be blittable.. /// internal static string NativeTypeMustBeBlittableDescription { get { return ResourceManager.GetString("NativeTypeMustBeBlittableDescription", resourceCulture); } } - + /// - /// Looks up a localized string similar to The native type '{0}' for the type '{1}' is not blittable. + /// Looks up a localized string similar to The native type '{0}' for type '{1}' must be blittable. /// internal static string NativeTypeMustBeBlittableMessage { get { return ResourceManager.GetString("NativeTypeMustBeBlittableMessage", resourceCulture); } } - + /// - /// Looks up a localized string similar to A native type for a given type must be non-null.. + /// Looks up a localized string similar to The native type must be pointer sized so the pinned result of 'GetPinnableReference' can be cast to the native type.. /// - internal static string NativeTypeMustBeNonNullDescription { + internal static string NativeTypeMustBePointerSizedDescription { get { - return ResourceManager.GetString("NativeTypeMustBeNonNullDescription", resourceCulture); + return ResourceManager.GetString("NativeTypeMustBePointerSizedDescription", resourceCulture); } } - + /// - /// Looks up a localized string similar to The native type for the type '{0}' is null. + /// Looks up a localized string similar to The native type '{0}' must be pointer sized because the managed type '{1}' has a 'GetPinnableReference' method. /// - internal static string NativeTypeMustBeNonNullMessage { + internal static string NativeTypeMustBePointerSizedMessage { get { - return ResourceManager.GetString("NativeTypeMustBeNonNullMessage", resourceCulture); + return ResourceManager.GetString("NativeTypeMustBePointerSizedMessage", resourceCulture); } } - + /// - /// Looks up a localized string similar to The native type must be pointer sized so the pinned result of 'GetPinnableReference' can be cast to the native type.. + /// Looks up a localized string similar to A native type for a given type must have the 'System.Runtime.InteropServices.CustomTypeMarshallerAttribute' that specifies this type as the managed type.. /// - internal static string NativeTypeMustBePointerSizedDescription { + internal static string NativeTypeMustHaveCustomTypeMarshallerAttributeDescription { get { - return ResourceManager.GetString("NativeTypeMustBePointerSizedDescription", resourceCulture); + return ResourceManager.GetString("NativeTypeMustHaveCustomTypeMarshallerAttributeDescription", resourceCulture); } } - + /// - /// Looks up a localized string similar to The native type '{0}' must be pointer sized because the managed type '{1}' has a 'GetPinnableReference' method. + /// Looks up a localized string similar to The native type for the type '{0}' must be a type with the 'System.Runtime.InteropServices.CustomTypeMarshallerAttribute' that specifies this type as the managed type. /// - internal static string NativeTypeMustBePointerSizedMessage { + internal static string NativeTypeMustHaveCustomTypeMarshallerAttributeMessage { get { - return ResourceManager.GetString("NativeTypeMustBePointerSizedMessage", resourceCulture); + return ResourceManager.GetString("NativeTypeMustHaveCustomTypeMarshallerAttributeMessage", resourceCulture); } } - + /// - /// Looks up a localized string similar to The native type must have at least one of the two marshalling methods to enable marshalling the managed type.. + /// Looks up a localized string similar to A 'Value' or 'LinearCollection'-kind native type that supports marshalling in the 'Out' direction must provide a 'ToManaged' method that returns the managed type.. /// - internal static string NativeTypeMustHaveRequiredShapeDescription { + internal static string OutRequiresToManagedDescription { get { - return ResourceManager.GetString("NativeTypeMustHaveRequiredShapeDescription", resourceCulture); + return ResourceManager.GetString("OutRequiresToManagedDescription", resourceCulture); } } - + /// - /// Looks up a localized string similar to The native type '{0}' must be a value type and have a constructor that takes one parameter of type '{1}' or a parameterless instance method named 'ToManaged' that returns '{1}'. + /// Looks up a localized string similar to The type '{0}' specifies it supports marshalling in the 'Out' direction, but it does not provide a 'ToManaged' method that returns the managed type. /// - internal static string NativeTypeMustHaveRequiredShapeMessage { + internal static string OutRequiresToManagedMessage { get { - return ResourceManager.GetString("NativeTypeMustHaveRequiredShapeMessage", resourceCulture); + return ResourceManager.GetString("OutRequiresToManagedMessage", resourceCulture); } } - + /// - /// Looks up a localized string similar to The 'Value' property must not be a 'ref' or 'readonly ref' property.. + /// Looks up a localized string similar to The 'TwoStageMarshalling' feature requires a 'void FromNativeValue(TNativeType value)' method for the 'Out' direction.. /// - internal static string RefValuePropertyUnsupportedDescription { + internal static string OutTwoStageMarshallingRequiresFromNativeValueDescription { get { - return ResourceManager.GetString("RefValuePropertyUnsupportedDescription", resourceCulture); + return ResourceManager.GetString("OutTwoStageMarshallingRequiresFromNativeValueDescription", resourceCulture); } } - + /// - /// Looks up a localized string similar to The 'Value' property on the native type '{0}' must not be a 'ref' or 'readonly ref' property.. + /// Looks up a localized string similar to The marshaller type '{0}' supports marshalling in the 'Out' direction with the 'TwoStageMarshalling' feature, but it does not provide a 'FromNativeValue' instance method that returns 'void' and takes one parameter.. /// - internal static string RefValuePropertyUnsupportedMessage { + internal static string OutTwoStageMarshallingRequiresFromNativeValueMessage { get { - return ResourceManager.GetString("RefValuePropertyUnsupportedMessage", resourceCulture); + return ResourceManager.GetString("OutTwoStageMarshallingRequiresFromNativeValueMessage", resourceCulture); } } - + /// - /// Looks up a localized string similar to . + /// Looks up a localized string similar to The 'Value' property must not be a 'ref' or 'readonly ref' property.. /// - internal static string RuntimeMarshallingMustBeDisabled { + internal static string RefNativeValueUnsupportedDescription { get { - return ResourceManager.GetString("RuntimeMarshallingMustBeDisabled", resourceCulture); + return ResourceManager.GetString("RefNativeValueUnsupportedDescription", resourceCulture); } } - + + /// + /// Looks up a localized string similar to The 'Value' property on the native type '{0}' must not be a 'ref' or 'readonly ref' property.. + /// + internal static string RefNativeValueUnsupportedMessage { + get { + return ResourceManager.GetString("RefNativeValueUnsupportedMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to An abstract type derived from 'SafeHandle' cannot be marshalled by reference. The provided type must be concrete.. /// @@ -590,7 +824,7 @@ internal static string SafeHandleByRefMustBeConcrete { return ResourceManager.GetString("SafeHandleByRefMustBeConcrete", resourceCulture); } } - + /// /// Looks up a localized string similar to P/Invoke source generation is not supported on unknown target framework v{0}. The generated source will not be compatible with other frameworks.. /// @@ -599,7 +833,7 @@ internal static string TargetFrameworkNotSupportedDescription { return ResourceManager.GetString("TargetFrameworkNotSupportedDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to 'LibraryImportAttribute' cannot be used for source-generated P/Invokes on an unknown target framework v{0}.. /// @@ -608,7 +842,7 @@ internal static string TargetFrameworkNotSupportedMessage { return ResourceManager.GetString("TargetFrameworkNotSupportedMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to Current target framework is not supported by source-generated P/Invokes. /// @@ -617,7 +851,43 @@ internal static string TargetFrameworkNotSupportedTitle { return ResourceManager.GetString("TargetFrameworkNotSupportedTitle", resourceCulture); } } - + + /// + /// Looks up a localized string similar to A marshaller type that provides a 'ToNativeValue' method should specify that it supports the 'TwoStageMarshalling' feature.. + /// + internal static string ToNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureDescription { + get { + return ResourceManager.GetString("ToNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' provides a 'ToNativeValue' method but does not specify that it supports the 'TwoStageMarshalling' feature. The method will not be used unless the feature is specified.. + /// + internal static string ToNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureMessage { + get { + return ResourceManager.GetString("ToNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The return type of 'ToNativeValue' and the parameter type of 'FromNativeValue' must be the same.. + /// + internal static string TwoStageMarshallingNativeTypesMustMatchDescription { + get { + return ResourceManager.GetString("TwoStageMarshallingNativeTypesMustMatchDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The return type of 'ToNativeValue' and the parameter type of 'FromNativeValue' must be the same. + /// + internal static string TwoStageMarshallingNativeTypesMustMatchMessage { + get { + return ResourceManager.GetString("TwoStageMarshallingNativeTypesMustMatchMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type.. /// @@ -626,7 +896,7 @@ internal static string TypeNotSupportedDescription { return ResourceManager.GetString("TypeNotSupportedDescription", resourceCulture); } } - + /// /// Looks up a localized string similar to The type '{0}' is not supported by source-generated P/Invokes. The generated source will not handle marshalling of parameter '{1}'.. /// @@ -635,7 +905,7 @@ internal static string TypeNotSupportedMessageParameter { return ResourceManager.GetString("TypeNotSupportedMessageParameter", resourceCulture); } } - + /// /// Looks up a localized string similar to {0} The generated source will not handle marshalling of parameter '{1}'.. /// @@ -644,7 +914,7 @@ internal static string TypeNotSupportedMessageParameterWithDetails { return ResourceManager.GetString("TypeNotSupportedMessageParameterWithDetails", resourceCulture); } } - + /// /// Looks up a localized string similar to The type '{0}' is not supported by source-generated P/Invokes. The generated source will not handle marshalling of the return value of method '{1}'.. /// @@ -653,7 +923,7 @@ internal static string TypeNotSupportedMessageReturn { return ResourceManager.GetString("TypeNotSupportedMessageReturn", resourceCulture); } } - + /// /// Looks up a localized string similar to {0} The generated source will not handle marshalling of the return value of method '{1}'.. /// @@ -662,7 +932,7 @@ internal static string TypeNotSupportedMessageReturnWithDetails { return ResourceManager.GetString("TypeNotSupportedMessageReturnWithDetails", resourceCulture); } } - + /// /// Looks up a localized string similar to Specified type is not supported by source-generated P/Invokes. /// @@ -671,40 +941,58 @@ internal static string TypeNotSupportedTitle { return ResourceManager.GetString("TypeNotSupportedTitle", resourceCulture); } } - + /// - /// Looks up a localized string similar to The native type's 'Value' property must have a getter to support marshalling from managed to native.. + /// Looks up a localized string similar to The 'UnmanagedResources' feature requires a 'void FreeNative()' method.. /// - internal static string ValuePropertyMustHaveGetterDescription { + internal static string UnmanagedResourcesRequiresFreeNativeDescription { get { - return ResourceManager.GetString("ValuePropertyMustHaveGetterDescription", resourceCulture); + return ResourceManager.GetString("UnmanagedResourcesRequiresFreeNativeDescription", resourceCulture); } } - + /// - /// Looks up a localized string similar to The 'Value' property on the native type '{0}' must have a getter. + /// Looks up a localized string similar to The marshaller type '{0}' supports marshalling with the 'UnmanagedResources' feature, but it does not provide a parameterless 'FreeNative' instance method that returns 'void'. /// - internal static string ValuePropertyMustHaveGetterMessage { + internal static string UnmanagedResourcesRequiresFreeNativeMessage { get { - return ResourceManager.GetString("ValuePropertyMustHaveGetterMessage", resourceCulture); + return ResourceManager.GetString("UnmanagedResourcesRequiresFreeNativeMessage", resourceCulture); } } - + /// - /// Looks up a localized string similar to The native type's 'Value' property must have a setter to support marshalling from native to managed.. + /// Looks up a localized string similar to A 'Value'-kind native type that supports the 'CallerAllocatedBuffer' feature must provide a two-parameter constructor taking the managed type and a 'Span<byte>' as parameters. /// - internal static string ValuePropertyMustHaveSetterDescription { + internal static string ValueInCallerAllocatedBufferRequiresSpanConstructorDescription { get { - return ResourceManager.GetString("ValuePropertyMustHaveSetterDescription", resourceCulture); + return ResourceManager.GetString("ValueInCallerAllocatedBufferRequiresSpanConstructorDescription", resourceCulture); } } - + + /// + /// Looks up a localized string similar to The type '{0}' specifies that it supports 'In' marshalling with the 'CallerAllocatedBuffer' feature for '{1}' but does not provide a one-parameter constructor that takes a '{1}' and 'Span<byte>' as parameters. + /// + internal static string ValueInCallerAllocatedBufferRequiresSpanConstructorMessage { + get { + return ResourceManager.GetString("ValueInCallerAllocatedBufferRequiresSpanConstructorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A 'Value'-kind native type must provide a one-parameter constructor taking the managed type as a parameter. + /// + internal static string ValueInRequiresOneParameterConstructorDescription { + get { + return ResourceManager.GetString("ValueInRequiresOneParameterConstructorDescription", resourceCulture); + } + } + /// - /// Looks up a localized string similar to The 'Value' property on the native type '{0}' must have a setter. + /// Looks up a localized string similar to The type '{0}' specifies that it supports 'In' marshalling of '{1}' but does not provide a one-parameter constructor that takes a '{1}' as a parameter. /// - internal static string ValuePropertyMustHaveSetterMessage { + internal static string ValueInRequiresOneParameterConstructorMessage { get { - return ResourceManager.GetString("ValuePropertyMustHaveSetterMessage", resourceCulture); + return ResourceManager.GetString("ValueInRequiresOneParameterConstructorMessage", resourceCulture); } } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources.resx b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources.resx index 1d6467f5daae8..cb1b973807da4 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources.resx +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources.resx @@ -1,17 +1,17 @@  - @@ -117,17 +117,11 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - A type marked with 'BlittableTypeAttribute' must be blittable. - - - Type '{0}' is marked with 'BlittableTypeAttribute' but is not blittable - - - When a constructor taking a Span<byte> is specified on the native type, the type must also have a public integer constant named BufferSize to provide the size of the caller-allocated buffer. + + When a constructor taking a 'Span<byte>' is specified on the native type, the type must set the BufferSize field on the 'System.Runtime.InteropServices.CustomTypeMarshallerAttribute' to provide the size of the caller-allocated buffer. - - The native type '{0}' must have a 'public const int BufferSize' field that specifies the size of the stack buffer because it has a constructor that takes a caller-allocated Span<byte> + + The native type '{0}' must set the 'BufferSize' field on the applied 'System.Runtime.InteropServices.CustomTypeMarshallerAttribute' to specify the size of the caller-allocated buffer because it has a constructor that takes a caller-allocated 'Span<byte>' A type that supports marshalling from managed to native using a caller-allocated buffer should also support marshalling from managed to native where using a caller-allocated buffer is impossible. @@ -144,18 +138,6 @@ Specified 'LibraryImportAttribute' arguments cannot be forwarded to 'DllImportAttribute' - - The 'BlittableTypeAttribute' and 'NativeMarshallingAttribute' attributes are mutually exclusive. - - - Type '{0}' is marked with 'BlittableTypeAttribute' and 'NativeMarshallingAttribute'. A type can only have one of these two attributes. - - - A native type with the 'GenericContiguousCollectionMarshallerAttribute' must have at least one of the two marshalling methods as well as a 'ManagedValues' property of type 'Span<T>' for some 'T' and a 'NativeValueStorage' property of type 'Span<byte>' to enable marshalling the managed type. - - - The native type '{0}' must be a value type and have a constructor that takes two parameters, one of type '{1}' and an 'int', or have a parameterless instance method named 'ToManaged' that returns '{1}' as well as a 'ManagedValues' property of type 'Span<T>' for some 'T' and a 'NativeValueStorage' property of type 'Span<byte>' - Source-generated P/Invokes will ignore any configuration that is not supported. @@ -178,7 +160,7 @@ Specified configuration is not supported by source-generated P/Invokes. - Only one of 'ConstantElementCount' or 'ElementCountInfo' may be used in a 'MarshalUsingAttribute' for a given 'ElementIndirectionLevel' + Only one of 'ConstantElementCount' or 'ElementCountInfo' may be used in a 'MarshalUsingAttribute' for a given 'ElementIndirectionDepth' Automatically converting a P/Invoke with 'PreserveSig' set to 'false' to a source-generated P/Invoke may produce invalid code @@ -245,14 +227,23 @@ 'StringMarshalling' should be set to 'StringMarshalling.Custom' when 'StringMarshallingCustomType' is specified. - - The use cases for 'GetPinnableReference' are not applicable in any scenarios where a 'Value' property is not also required. + + The use cases for 'GetPinnableReference' are not applicable in any scenarios where 'TwoStageMarshalling' is not also required. + + + The 'GetPinnableReference' method cannot be provided on the native type '{0}' unless the 'TwoStageMarshalling' feature is also supported + + + The specified marshaller kind must be a valid value of the 'System.Runtime.InteropServices.CustomMarshallerKind' enum. + + + The specified custom marshaller kind for '{0}' is invalid - - The 'GetPinnableReference' method cannot be provided on the native type '{0}' unless a 'Value' property is also provided + + A type with a 'System.Runtime.InteropServices.CustomTypeMarshallerAttribute' must specify a managed type - - The native type '{0}' must be a closed generic so the emitted code can use a specific instantiation. + + The type '{0}' does not specify a managed type in the 'System.Runtime.InteropServices.CustomTypeMarshallerAttribute' applied to the type The native type '{0}' must be a closed generic or have the same number of generic parameters as the managed type so the emitted code can use a specific instantiation. @@ -261,16 +252,10 @@ The native type '{0}' for managed type '{1}' must be a closed generic type or have the same arity as the managed type. - A native type for a given type must be blittable. + A native type must be blittable. - The native type '{0}' for the type '{1}' is not blittable - - - A native type for a given type must be non-null. - - - The native type for the type '{0}' is null + The native type '{0}' for type '{1}' must be blittable The native type must be pointer sized so the pinned result of 'GetPinnableReference' can be cast to the native type. @@ -278,21 +263,24 @@ The native type '{0}' must be pointer sized because the managed type '{1}' has a 'GetPinnableReference' method - - The native type must have at least one of the two marshalling methods to enable marshalling the managed type. + + A native type for a given type must have the 'System.Runtime.InteropServices.CustomTypeMarshallerAttribute' that specifies this type as the managed type. + + + The native type for the type '{0}' must be a type with the 'System.Runtime.InteropServices.CustomTypeMarshallerAttribute' that specifies this type as the managed type + + + A native must set the 'Direction' property on the 'CustomTypeMarshallerAttribute' to a value that sets at least one known flag value on the 'CustomTypeMarshallerDirection' enum - - The native type '{0}' must be a value type and have a constructor that takes one parameter of type '{1}' or a parameterless instance method named 'ToManaged' that returns '{1}' + + The native type '{0}' must set the 'Direction' property on the 'CustomTypeMarshallerAttribute' to a value that sets at least one known flag value on the 'CustomTypeMarshallerDirection' enum - + The 'Value' property must not be a 'ref' or 'readonly ref' property. - + The 'Value' property on the native type '{0}' must not be a 'ref' or 'readonly ref' property. - - - An abstract type derived from 'SafeHandle' cannot be marshalled by reference. The provided type must be concrete. @@ -329,16 +317,124 @@ Specified type is not supported by source-generated P/Invokes - - The native type's 'Value' property must have a getter to support marshalling from managed to native. + + The 'TwoStageMarshalling' feature requires a 'TNativeType ToNativeValue()' method for the 'In' direction. + + + The marshaller type '{0}' supports marshalling in the 'In' direction with the 'TwoStageMarshalling' feature must provide a 'ToNativeValue' instance method that returns the native value + + + The 'TwoStageMarshalling' feature requires a 'void FromNativeValue(TNativeType value)' method for the 'Out' direction. + + + The marshaller type '{0}' supports marshalling in the 'Out' direction with the 'TwoStageMarshalling' feature, but it does not provide a 'FromNativeValue' instance method that returns 'void' and takes one parameter. + + + The 'CustomTypeMarshallerAttribute' attribute must be semantically valid + + + The 'CustomTypeMarshallerAttribute' on '{0}' is semantically invalid + + + A 'LinearCollection'-kind native type that supports the 'CallerAllocatedBuffer' feature must provide a three-parameter constructor taking the managed type as the first parameter, a 'Span<byte>' as the second parameter, and the native size of the element as the third parameter + + + The type '{0}' specifies that it supports 'In' marshalling with the 'CallerAllocatedBuffer' feature for '{1}' but does not provide a three-parameter constructor that takes a '{1}' , a 'Span<byte>', and an 'int' + + + A 'LinearCollection'-kind native type must provide a two-parameter constructor taking the managed type as the first parameter and the native size of the element as the second parameter + + + The type '{0}' specifies that it supports 'In' marshalling of '{1}' but does not provide a two-parameter constructor that takes a '{1}' as the first parameter and an 'int' as the second parameter + + + The specified marshaller direction must be a valid value of the 'System.Runtime.InteropServices.CustomMarshallerDirection' enum. + + + The specified custom marshaller direction for '{0}' is invalid + + + A 'Value'-kind native type that supports the 'CallerAllocatedBuffer' feature must provide a two-parameter constructor taking the managed type and a 'Span<byte>' as parameters + + + The type '{0}' specifies that it supports 'In' marshalling with the 'CallerAllocatedBuffer' feature for '{1}' but does not provide a one-parameter constructor that takes a '{1}' and 'Span<byte>' as parameters + + + A 'Value'-kind native type must provide a one-parameter constructor taking the managed type as a parameter + + + The type '{0}' specifies that it supports 'In' marshalling of '{1}' but does not provide a one-parameter constructor that takes a '{1}' as a parameter + + + A 'LinearCollection'-kind native type that supports marshalling in the 'In' direction must provide a 'GetManagedValuesSource' that returns a 'ReadOnlySpan<>' and a 'GetNativeValuesDestination' method that returns a 'Span<byte>'. + + + The type '{0}' specifies that is supports marshalling in the 'In' direction, but it does not provide a 'GetManagedValuesSource' that returns a 'ReadOnlySpan<>' and a 'GetNativeValuesDestination' method that returns a 'Span<byte>' + + + A 'Value' or 'LinearCollection'-kind native type that supports marshalling in the 'Out' direction must provide a 'ToManaged' method that returns the managed type. + + + The type '{0}' specifies it supports marshalling in the 'Out' direction, but it does not provide a 'ToManaged' method that returns the managed type + + + A 'LinearCollection'-kind native type that supports marshalling in the 'Out' direction must provide a 'GetManagedValuesDestination' that takes an 'int' and returns a 'Span<>' and a 'GetNativeValuesSource' method that takes an 'int' and rreturns a 'ReadOnlySpan<byte>'. + + + The type '{0}' specifies that it supports marshalling in the 'Out' direction, but it does not provide a 'GetManagedValuesDestination' that takes an 'int' and returns a 'Span<>' and a 'GetNativeValuesSource' method that takes an 'int' and rreturns a 'ReadOnlySpan<byte>' + + + A 'LinearCollection'-kind native type that supports marshalling in the 'Out' direction must provide a constructor that takes the size of the native element as an 'int'. + + + The type '{0}' specifies that it supports marshalling in the 'Out' direction, but it does not provide a constructor that takes the size of the native element as an 'int'. + + + The 'UnmanagedResources' feature requires a 'void FreeNative()' method. + + + The marshaller type '{0}' supports marshalling with the 'UnmanagedResources' feature, but it does not provide a parameterless 'FreeNative' instance method that returns 'void' + + + Add missing custom type marshaller members + + + Add missing features to the 'CustomTypeMarshallerAttribute' attribute + + + A marshaller type that provides a constructor taking a caller-allocated 'Span<byte>' should specify that it supports the 'CallerAllocatedBuffer' feature. + + + The type '{0}' provides a constructor taking a caller-allocated 'Span<byte>' but does not specify that it supports the 'CallerAllocatedBuffer' feature. The constructor will not be used unless the feature is specified. + + + A marshaller type that provides a 'FreeNative' method should specify that it supports the 'UnmanagedResources' feature. + + + The type '{0}' provides a 'FreeNative' method but does not specify that it supports the 'UnmanagedResources' feature. The method will not be used unless the feature is specified. + + + A marshaller type that provides a 'FromNativeValue' method should specify that it supports the 'TwoStageMarshalling' feature. + + + The type '{0}' provides a 'FromNativeValue' method but does not specify that it supports the 'TwoStageMarshalling' feature. The method will not be used unless the feature is specified. + + + The element type of the 'ReadOnlySpan' returned by 'GetManagedValuesSource' must be the same as the element type returned by 'GetManagedValuesDestination'. + + + The element type of the 'ReadOnlySpan' returned by 'GetManagedValuesSource' must be the same as the element type returned by 'GetManagedValuesDestination' + + + A marshaller type that provides a 'ToNativeValue' method should specify that it supports the 'TwoStageMarshalling' feature. - - The 'Value' property on the native type '{0}' must have a getter + + The type '{0}' provides a 'ToNativeValue' method but does not specify that it supports the 'TwoStageMarshalling' feature. The method will not be used unless the feature is specified. - - The native type's 'Value' property must have a setter to support marshalling from native to managed. + + The return type of 'ToNativeValue' and the parameter type of 'FromNativeValue' must be the same. - - The 'Value' property on the native type '{0}' must have a setter + + The return type of 'ToNativeValue' and the parameter type of 'FromNativeValue' must be the same \ No newline at end of file diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IGeneratorDiagnostics.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IGeneratorDiagnostics.cs index f1df80186c8d9..3af27467e7ff0 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IGeneratorDiagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IGeneratorDiagnostics.cs @@ -38,7 +38,7 @@ public static Diagnostic CreateDiagnostic( { SyntaxReference? syntaxReference = attributeData.ApplicationSyntaxReference; Location location = syntaxReference is not null - ? syntaxReference.GetSyntax().GetLocation() + ? syntaxReference.SyntaxTree.GetLocation(syntaxReference.Span) : Location.None; return location.CreateDiagnostic(descriptor, args); diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ContiguousCollectionElementMarshallingCodeContext.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/LinearCollectionElementMarshallingCodeContext.cs similarity index 84% rename from src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ContiguousCollectionElementMarshallingCodeContext.cs rename to src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/LinearCollectionElementMarshallingCodeContext.cs index 02d965c9f29ce..442c3ab55e372 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ContiguousCollectionElementMarshallingCodeContext.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/LinearCollectionElementMarshallingCodeContext.cs @@ -3,8 +3,9 @@ namespace Microsoft.Interop { - internal sealed class ContiguousCollectionElementMarshallingCodeContext : StubCodeContext + internal sealed class LinearCollectionElementMarshallingCodeContext : StubCodeContext { + private readonly string _managedSpanIdentifier; private readonly string _nativeSpanIdentifier; public override bool SingleFrameSpansNativeContext => false; @@ -20,13 +21,15 @@ internal sealed class ContiguousCollectionElementMarshallingCodeContext : StubCo /// The indexer in the loop to get the element to marshal from the collection. /// The identifier of the native value storage cast to the target element type. /// The parent context. - public ContiguousCollectionElementMarshallingCodeContext( + public LinearCollectionElementMarshallingCodeContext( Stage currentStage, + string managedSpanIdentifier, string nativeSpanIdentifier, StubCodeContext parentContext) { CurrentStage = currentStage; IndexerIdentifier = CalculateIndexerIdentifierBasedOnParentContext(parentContext); + _managedSpanIdentifier = managedSpanIdentifier; _nativeSpanIdentifier = nativeSpanIdentifier; ParentContext = parentContext; } @@ -38,9 +41,8 @@ public ContiguousCollectionElementMarshallingCodeContext( /// Managed and native identifiers public override (string managed, string native) GetIdentifiers(TypePositionInfo info) { - (string _, string native) = ParentContext!.GetIdentifiers(info); return ( - $"{native}.ManagedValues[{IndexerIdentifier}]", + $"{_managedSpanIdentifier}[{IndexerIdentifier}]", $"{_nativeSpanIdentifier}[{IndexerIdentifier}]" ); } @@ -55,7 +57,7 @@ private static string CalculateIndexerIdentifierBasedOnParentContext(StubCodeCon int i = 0; while (parentContext is StubCodeContext context) { - if (context is ContiguousCollectionElementMarshallingCodeContext) + if (context is LinearCollectionElementMarshallingCodeContext) { i++; } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManualTypeMarshallingHelper.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManualTypeMarshallingHelper.cs index b6343bb5d23e2..3b190b6ad0433 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManualTypeMarshallingHelper.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManualTypeMarshallingHelper.cs @@ -2,39 +2,172 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; +using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; namespace Microsoft.Interop { + public readonly record struct CustomTypeMarshallerData(CustomTypeMarshallerKind Kind, CustomTypeMarshallerDirection Direction, CustomTypeMarshallerFeatures Features, int? BufferSize); + + public static class ShapeMemberNames + { + public abstract class Value + { + public const string ToNativeValue = nameof(ToNativeValue); + public const string FromNativeValue = nameof(FromNativeValue); + public const string GetPinnableReference = nameof(GetPinnableReference); + public const string FreeNative = nameof(FreeNative); + public const string ToManaged = nameof(ToManaged); + } + + public abstract class LinearCollection : Value + { + public const string GetManagedValuesDestination = nameof(GetManagedValuesDestination); + public const string GetManagedValuesSource = nameof(GetManagedValuesSource); + public const string GetNativeValuesDestination = nameof(GetNativeValuesDestination); + public const string GetNativeValuesSource = nameof(GetNativeValuesSource); + } + } public static class ManualTypeMarshallingHelper { - public const string ValuePropertyName = "Value"; - public const string GetPinnableReferenceName = "GetPinnableReference"; - public const string BufferSizeFieldName = "BufferSize"; - public const string RequiresStackBufferFieldName = "RequiresStackBuffer"; - public const string ToManagedMethodName = "ToManaged"; - public const string FreeNativeMethodName = "FreeNative"; - public const string ManagedValuesPropertyName = "ManagedValues"; - public const string NativeValueStoragePropertyName = "NativeValueStorage"; - public const string SetUnmarshalledCollectionLengthMethodName = "SetUnmarshalledCollectionLength"; + public static class CustomMarshallerAttributeFields + { + public const string BufferSize = nameof(BufferSize); + public const string Direction = nameof(Direction); + public const string Features = nameof(Features); + } public static class MarshalUsingProperties { - public const string ElementIndirectionLevel = nameof(ElementIndirectionLevel); + public const string ElementIndirectionDepth = nameof(ElementIndirectionDepth); public const string CountElementName = nameof(CountElementName); public const string ConstantElementCount = nameof(ConstantElementCount); } - public enum NativeTypeMarshallingVariant + public static (bool hasAttribute, ITypeSymbol? managedType, CustomTypeMarshallerData? kind) GetMarshallerShapeInfo(ITypeSymbol marshallerType) { - Standard, - ContiguousCollection + var attr = marshallerType.GetAttributes().FirstOrDefault(attr => attr.AttributeClass.ToDisplayString() == TypeNames.CustomTypeMarshallerAttribute); + if (attr is null) + { + return (false, null, null); + } + if (attr.ConstructorArguments.Length == 0) + { + return (true, null, null); + } + CustomTypeMarshallerKind kind = CustomTypeMarshallerKind.Value; + ITypeSymbol? managedType = attr.ConstructorArguments[0].Value as ITypeSymbol; + if (attr.ConstructorArguments.Length > 1) + { + if (attr.ConstructorArguments[1].Value is not int i) + { + return (true, managedType, null); + } + kind = (CustomTypeMarshallerKind)i; + } + var namedArguments = attr.NamedArguments.ToImmutableDictionary(); + int? bufferSize = namedArguments.TryGetValue(CustomMarshallerAttributeFields.BufferSize, out TypedConstant bufferSizeConstant) ? bufferSizeConstant.Value as int? : null; + CustomTypeMarshallerDirection direction = namedArguments.TryGetValue(CustomMarshallerAttributeFields.Direction, out TypedConstant directionConstant) ? (CustomTypeMarshallerDirection)directionConstant.Value : CustomTypeMarshallerDirection.Ref; + CustomTypeMarshallerFeatures features = namedArguments.TryGetValue(CustomMarshallerAttributeFields.Features, out TypedConstant featuresConstant) ? (CustomTypeMarshallerFeatures)featuresConstant.Value : CustomTypeMarshallerFeatures.None; + return (true, managedType, new CustomTypeMarshallerData(kind, direction, features, bufferSize)); + } + + /// + /// Resolve a non- to the correct managed type if is generic and is using any placeholder types. + /// + /// The non-named managed type. + /// The marshaller type. + /// The compilation to use to make new type symbols. + /// The resolved managed type, or if the provided type did not have any placeholders. + public static ITypeSymbol? ResolveManagedType(ITypeSymbol? managedType, INamedTypeSymbol marshallerType, Compilation compilation) + { + if (managedType is null || !marshallerType.IsGenericType) + { + return managedType; + } + Stack typeStack = new(); + ITypeSymbol? innerType = managedType; + while (innerType.TypeKind is TypeKind.Array or TypeKind.Pointer) + { + if (innerType is IArrayTypeSymbol array) + { + typeStack.Push(innerType); + innerType = array.ElementType; + } + else if (innerType is IPointerTypeSymbol pointerType) + { + typeStack.Push(innerType); + innerType = pointerType.PointedAtType; + } + } + + if (innerType.ToDisplayString() != TypeNames.CustomTypeMarshallerAttributeGenericPlaceholder) + { + return managedType; + } + + ITypeSymbol resultType = marshallerType.TypeArguments[0]; + + while (typeStack.Count > 0) + { + ITypeSymbol wrapperType = typeStack.Pop(); + if (wrapperType.TypeKind == TypeKind.Pointer) + { + resultType = compilation.CreatePointerTypeSymbol(resultType); + } + else if (wrapperType.TypeKind == TypeKind.Array) + { + IArrayTypeSymbol arrayType = (IArrayTypeSymbol)wrapperType; + if (arrayType.IsSZArray) + { + resultType = compilation.CreateArrayTypeSymbol(resultType, arrayType.Rank); + } + } + } + return resultType; + } + + public static (AttributeData? attribute, INamedTypeSymbol? marshallerType) GetDefaultMarshallerInfo(ITypeSymbol managedType) + { + AttributeData? attr = managedType.GetAttributes().FirstOrDefault(attr => attr.AttributeClass.ToDisplayString() == TypeNames.NativeMarshallingAttribute); + if (attr is null) + { + return (attr, null); + } + INamedTypeSymbol? marshallerType = null; + if (attr.ConstructorArguments.Length == 0) + { + return (attr, null); + } + + marshallerType = attr.ConstructorArguments[0].Value as INamedTypeSymbol; + if (managedType is not INamedTypeSymbol namedType || marshallerType is null) + { + return (attr, null); + } + if (namedType.TypeArguments.Length == 0) + { + return (attr, marshallerType); + } + else if (marshallerType.TypeArguments.Length != namedType.TypeArguments.Length) + { + return (attr, null); + } + else if (marshallerType.IsGenericType) + { + // Construct the marshaler type around the same type arguments as the managed type. + return (attr, marshallerType.ConstructedFrom.Construct(namedType.TypeArguments, namedType.TypeArgumentNullableAnnotations)); + } + + return (attr, marshallerType); } public static bool HasToManagedMethod(ITypeSymbol nativeType, ITypeSymbol managedType) { - return nativeType.GetMembers(ToManagedMethodName) + return nativeType.GetMembers(ShapeMemberNames.Value.ToManaged) .OfType() .Any(m => m.Parameters.IsEmpty && !m.ReturnsByRef @@ -46,9 +179,9 @@ public static bool HasToManagedMethod(ITypeSymbol nativeType, ITypeSymbol manage public static bool IsManagedToNativeConstructor( IMethodSymbol ctor, ITypeSymbol managedType, - NativeTypeMarshallingVariant variant) + CustomTypeMarshallerKind variant) { - if (variant == NativeTypeMarshallingVariant.ContiguousCollection) + if (variant == CustomTypeMarshallerKind.LinearCollection) { return ctor.Parameters.Length == 2 && SymbolEqualityComparer.Default.Equals(managedType, ctor.Parameters[0].Type) @@ -62,9 +195,9 @@ public static bool IsCallerAllocatedSpanConstructor( IMethodSymbol ctor, ITypeSymbol managedType, ITypeSymbol spanOfByte, - NativeTypeMarshallingVariant variant) + CustomTypeMarshallerKind variant) { - if (variant == NativeTypeMarshallingVariant.ContiguousCollection) + if (variant == CustomTypeMarshallerKind.LinearCollection) { return ctor.Parameters.Length == 3 && SymbolEqualityComparer.Default.Equals(managedType, ctor.Parameters[0].Type) @@ -82,66 +215,81 @@ public static bool IsCallerAllocatedSpanConstructor( // fixed statement. We aren't supporting a GetPinnableReference extension method // (which is apparently supported in the compiler). // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-7.3/pattern-based-fixed - return type.GetMembers(GetPinnableReferenceName) + return type.GetMembers(ShapeMemberNames.Value.GetPinnableReference) .OfType() - .FirstOrDefault(m => m is { Parameters: { Length: 0 } } and + .FirstOrDefault(m => m is { Parameters.Length: 0 } and ({ ReturnsByRef: true } or { ReturnsByRefReadonly: true })); } - public static IPropertySymbol? FindValueProperty(ITypeSymbol type) + public static bool HasFreeNativeMethod(ITypeSymbol type) { - return type.GetMembers(ValuePropertyName) - .OfType() - .FirstOrDefault(p => !p.IsStatic); + return type.GetMembers(ShapeMemberNames.Value.FreeNative) + .OfType() + .Any(m => m is { IsStatic: false, Parameters.Length: 0, ReturnType.SpecialType: SpecialType.System_Void }); } - public static bool HasFreeNativeMethod(ITypeSymbol type) + public static IMethodSymbol? FindToNativeValueMethod(ITypeSymbol type) { - return type.GetMembers(FreeNativeMethodName) + return type.GetMembers(ShapeMemberNames.Value.ToNativeValue) .OfType() - .Any(m => m is { IsStatic: false, Parameters: { Length: 0 }, ReturnType: { SpecialType: SpecialType.System_Void } }); + .FirstOrDefault(m => m is { IsStatic: false, Parameters.Length: 0 }); } - public static bool TryGetManagedValuesProperty(ITypeSymbol type, out IPropertySymbol managedValuesProperty) + public static IMethodSymbol? FindFromNativeValueMethod(ITypeSymbol type) { - managedValuesProperty = type - .GetMembers(ManagedValuesPropertyName) - .OfType() - .FirstOrDefault(p => p is { IsStatic: false, GetMethod: not null, ReturnsByRef: false, ReturnsByRefReadonly: false }); - return managedValuesProperty is not null; + return type.GetMembers(ShapeMemberNames.Value.FromNativeValue) + .OfType() + .FirstOrDefault(m => m is { IsStatic: false, Parameters.Length: 1, ReturnType.SpecialType: SpecialType.System_Void }); } - public static bool TryGetElementTypeFromContiguousCollectionMarshaller(ITypeSymbol type, out ITypeSymbol elementType) + public static bool TryGetElementTypeFromLinearCollectionMarshaller(ITypeSymbol type, ITypeSymbol readOnlySpanOfT, out ITypeSymbol elementType) { - if (!TryGetManagedValuesProperty(type, out IPropertySymbol managedValuesProperty)) + if (FindGetManagedValuesSourceMethod(type, readOnlySpanOfT) is not IMethodSymbol managedValuesSourceMethod) { elementType = null!; return false; } - elementType = ((INamedTypeSymbol)managedValuesProperty.Type).TypeArguments[0]; + elementType = ((INamedTypeSymbol)managedValuesSourceMethod.ReturnType).TypeArguments[0]; return true; } - public static bool HasSetUnmarshalledCollectionLengthMethod(ITypeSymbol type) + public static IMethodSymbol? FindGetManagedValuesSourceMethod(ITypeSymbol type, ITypeSymbol readOnlySpanOfT) { - return type.GetMembers(SetUnmarshalledCollectionLengthMethodName) + return type + .GetMembers(ShapeMemberNames.LinearCollection.GetManagedValuesSource) .OfType() - .Any(m => m is - { - IsStatic: false, - Parameters: { Length: 1 }, - ReturnType: { SpecialType: SpecialType.System_Void } - } && m.Parameters[0].Type.SpecialType == SpecialType.System_Int32); + .FirstOrDefault(m => m is { IsStatic: false, ReturnsByRef: false, ReturnsByRefReadonly: false, Parameters.Length: 0, ReturnType: INamedTypeSymbol { ConstructedFrom: INamedTypeSymbol returnType } } + && SymbolEqualityComparer.Default.Equals(returnType, readOnlySpanOfT)); } - public static bool HasNativeValueStorageProperty(ITypeSymbol type, ITypeSymbol spanOfByte) + public static IMethodSymbol? FindGetManagedValuesDestinationMethod(ITypeSymbol type, ITypeSymbol spanOfT) { return type - .GetMembers(NativeValueStoragePropertyName) - .OfType() - .Any(p => p is { IsStatic: false, GetMethod: not null, ReturnsByRef: false, ReturnsByRefReadonly: false } - && SymbolEqualityComparer.Default.Equals(p.Type, spanOfByte)); + .GetMembers(ShapeMemberNames.LinearCollection.GetManagedValuesDestination) + .OfType() + .FirstOrDefault(m => m is { IsStatic: false, ReturnsByRef: false, ReturnsByRefReadonly: false, Parameters.Length: 1, ReturnType: INamedTypeSymbol { ConstructedFrom: INamedTypeSymbol returnType } } + && m.Parameters[0].Type.SpecialType == SpecialType.System_Int32 + && SymbolEqualityComparer.Default.Equals(returnType, spanOfT)); + } + + public static IMethodSymbol? FindGetNativeValuesDestinationMethod(ITypeSymbol type, ITypeSymbol spanOfByte) + { + return type + .GetMembers(ShapeMemberNames.LinearCollection.GetNativeValuesDestination) + .OfType() + .FirstOrDefault(m => m is { IsStatic: false, ReturnsByRef: false, ReturnsByRefReadonly: false, Parameters.Length: 0, ReturnType: INamedTypeSymbol returnType } + && SymbolEqualityComparer.Default.Equals(returnType, spanOfByte)); + } + + public static IMethodSymbol? FindGetNativeValuesSourceMethod(ITypeSymbol type, ITypeSymbol readOnlySpanOfByte) + { + return type + .GetMembers(ShapeMemberNames.LinearCollection.GetNativeValuesSource) + .OfType() + .FirstOrDefault(m => m is { IsStatic: false, ReturnsByRef: false, ReturnsByRefReadonly: false, Parameters.Length: 1, ReturnType: INamedTypeSymbol returnType } + && m.Parameters[0].Type.SpecialType == SpecialType.System_Int32 + && SymbolEqualityComparer.Default.Equals(returnType, readOnlySpanOfByte)); } } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/AttributedMarshallingModelGeneratorFactory.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/AttributedMarshallingModelGeneratorFactory.cs index 7b28fa14f2a9a..f3aac5de6a729 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/AttributedMarshallingModelGeneratorFactory.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/AttributedMarshallingModelGeneratorFactory.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -47,8 +48,8 @@ public IMarshallingGenerator Create(TypePositionInfo info, StubCodeContext conte return info.MarshallingAttributeInfo switch { NativeMarshallingAttributeInfo marshalInfo when Options.RuntimeMarshallingDisabled => CreateCustomNativeTypeMarshaller(info, context, marshalInfo), - NativeMarshallingAttributeInfo { ValuePropertyType: SpecialTypeInfo specialType } marshalInfo when specialType.SpecialType.IsAlwaysBlittable() => CreateCustomNativeTypeMarshaller(info, context, marshalInfo), - NativeMarshallingAttributeInfo { ValuePropertyType: PointerTypeInfo } marshalInfo => CreateCustomNativeTypeMarshaller(info, context, marshalInfo), + NativeMarshallingAttributeInfo { NativeValueType: SpecialTypeInfo specialType } marshalInfo when specialType.SpecialType.IsAlwaysBlittable() => CreateCustomNativeTypeMarshaller(info, context, marshalInfo), + NativeMarshallingAttributeInfo { NativeValueType: PointerTypeInfo } marshalInfo => CreateCustomNativeTypeMarshaller(info, context, marshalInfo), UnmanagedBlittableMarshallingInfo when Options.RuntimeMarshallingDisabled => s_blittable, UnmanagedBlittableMarshallingInfo or NativeMarshallingAttributeInfo when !Options.RuntimeMarshallingDisabled => throw new MarshallingNotSupportedException(info, context) @@ -93,7 +94,7 @@ ExpressionSyntax GetExpressionForParam(TypePositionInfo paramInfo) for (int i = 0; i < numIndirectionLevels; i++) { - if (marshallingInfo is NativeContiguousCollectionMarshallingInfo collectionInfo) + if (marshallingInfo is NativeLinearCollectionMarshallingInfo collectionInfo) { type = collectionInfo.ElementType; marshallingInfo = collectionInfo.ElementMarshallingInfo; @@ -129,7 +130,7 @@ static ExpressionSyntax GetIndexedNumElementsExpression(StubCodeContext context, while (currentContext is not null) { - if (currentContext is ContiguousCollectionElementMarshallingCodeContext collectionContext) + if (currentContext is LinearCollectionElementMarshallingCodeContext collectionContext) { indexerStack.Push(collectionContext.IndexerIdentifier); } @@ -157,30 +158,34 @@ private IMarshallingGenerator CreateCustomNativeTypeMarshaller(TypePositionInfo ICustomNativeTypeMarshallingStrategy marshallingStrategy = new SimpleCustomNativeTypeMarshalling(marshalInfo.NativeMarshallingType.Syntax); - if ((marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedToNativeStackalloc) != 0) + if ((marshalInfo.MarshallingFeatures & CustomTypeMarshallerFeatures.CallerAllocatedBuffer) != 0) { - marshallingStrategy = new StackallocOptimizationMarshalling(marshallingStrategy); + if (marshalInfo.BufferSize is null) + { + throw new MarshallingNotSupportedException(info, context); + } + marshallingStrategy = new StackallocOptimizationMarshalling(marshallingStrategy, marshalInfo.BufferSize.Value); } - if ((marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.FreeNativeResources) != 0) + if ((marshalInfo.MarshallingFeatures & CustomTypeMarshallerFeatures.UnmanagedResources) != 0) { marshallingStrategy = new FreeNativeCleanupStrategy(marshallingStrategy); } // Collections have extra configuration, so handle them here. - if (marshalInfo is NativeContiguousCollectionMarshallingInfo collectionMarshallingInfo) + if (marshalInfo is NativeLinearCollectionMarshallingInfo collectionMarshallingInfo) { return CreateNativeCollectionMarshaller(info, context, collectionMarshallingInfo, marshallingStrategy); } - if (marshalInfo.ValuePropertyType is not null) + if (marshalInfo.NativeValueType is not null) { - marshallingStrategy = DecorateWithValuePropertyStrategy(marshalInfo, marshallingStrategy); + marshallingStrategy = DecorateWithTwoStageMarshallingStrategy(marshalInfo, marshallingStrategy); } IMarshallingGenerator marshallingGenerator = new CustomNativeTypeMarshallingGenerator(marshallingStrategy, enableByValueContentsMarshalling: false); - if ((marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedTypePinning) != 0) + if (marshalInfo.PinningFeatures.HasFlag(CustomTypeMarshallerPinning.ManagedType)) { return new PinnableManagedValueMarshaller(marshallingGenerator); } @@ -193,7 +198,7 @@ private void ValidateCustomNativeTypeMarshallingSupported(TypePositionInfo info, // The marshalling method for this type doesn't support marshalling from native to managed, // but our scenario requires marshalling from native to managed. if ((info.RefKind == RefKind.Ref || info.RefKind == RefKind.Out || info.IsManagedReturnPosition) - && (marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.NativeToManaged) == 0) + && !marshalInfo.Direction.HasFlag(CustomTypeMarshallerDirection.Out)) { throw new MarshallingNotSupportedException(info, context) { @@ -203,8 +208,10 @@ private void ValidateCustomNativeTypeMarshallingSupported(TypePositionInfo info, // The marshalling method for this type doesn't support marshalling from managed to native by value, // but our scenario requires marshalling from managed to native by value. else if (!info.IsByRef - && (marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedToNative) == 0 - && (context.SingleFrameSpansNativeContext && (marshalInfo.MarshallingFeatures & (CustomMarshallingFeatures.ManagedTypePinning | CustomMarshallingFeatures.ManagedToNativeStackalloc)) == 0)) + && context.SingleFrameSpansNativeContext + && !(marshalInfo.PinningFeatures.HasFlag(CustomTypeMarshallerPinning.ManagedType) + || marshalInfo.MarshallingFeatures.HasFlag(CustomTypeMarshallerFeatures.CallerAllocatedBuffer) + || marshalInfo.Direction.HasFlag(CustomTypeMarshallerDirection.In))) { throw new MarshallingNotSupportedException(info, context) { @@ -215,8 +222,8 @@ private void ValidateCustomNativeTypeMarshallingSupported(TypePositionInfo info, // but our scenario requires marshalling from managed to native by reference. // "in" byref supports stack marshalling. else if (info.RefKind == RefKind.In - && (marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedToNative) == 0 - && !(context.SingleFrameSpansNativeContext && (marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedToNativeStackalloc) != 0)) + && !(context.SingleFrameSpansNativeContext && marshalInfo.MarshallingFeatures.HasFlag(CustomTypeMarshallerFeatures.CallerAllocatedBuffer)) + && !marshalInfo.Direction.HasFlag(CustomTypeMarshallerDirection.In)) { throw new MarshallingNotSupportedException(info, context) { @@ -226,8 +233,9 @@ private void ValidateCustomNativeTypeMarshallingSupported(TypePositionInfo info, // The marshalling method for this type doesn't support marshalling from managed to native by reference, // but our scenario requires marshalling from managed to native by reference. // "ref" byref marshalling doesn't support stack marshalling + // The "Out" direction for "ref" was checked above else if (info.RefKind == RefKind.Ref - && (marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedToNative) == 0) + && !marshalInfo.Direction.HasFlag(CustomTypeMarshallerDirection.In)) { throw new MarshallingNotSupportedException(info, context) { @@ -236,57 +244,57 @@ private void ValidateCustomNativeTypeMarshallingSupported(TypePositionInfo info, } } - private ICustomNativeTypeMarshallingStrategy DecorateWithValuePropertyStrategy(NativeMarshallingAttributeInfo marshalInfo, ICustomNativeTypeMarshallingStrategy nativeTypeMarshaller) + private ICustomNativeTypeMarshallingStrategy DecorateWithTwoStageMarshallingStrategy(NativeMarshallingAttributeInfo marshalInfo, ICustomNativeTypeMarshallingStrategy nativeTypeMarshaller) { - TypeSyntax valuePropertyTypeSyntax = marshalInfo.ValuePropertyType!.Syntax; + TypeSyntax valuePropertyTypeSyntax = marshalInfo.NativeValueType!.Syntax; - if ((marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.NativeTypePinning) != 0) + if (marshalInfo.PinningFeatures.HasFlag(CustomTypeMarshallerPinning.NativeType) && marshalInfo.MarshallingFeatures.HasFlag(CustomTypeMarshallerFeatures.TwoStageMarshalling)) { return new PinnableMarshallerTypeMarshalling(nativeTypeMarshaller, valuePropertyTypeSyntax); } - return new CustomNativeTypeWithValuePropertyMarshalling(nativeTypeMarshaller, valuePropertyTypeSyntax); + return new CustomNativeTypeWithToFromNativeValueMarshalling(nativeTypeMarshaller, valuePropertyTypeSyntax); } private IMarshallingGenerator CreateNativeCollectionMarshaller( TypePositionInfo info, StubCodeContext context, - NativeContiguousCollectionMarshallingInfo collectionInfo, + NativeLinearCollectionMarshallingInfo collectionInfo, ICustomNativeTypeMarshallingStrategy marshallingStrategy) { var elementInfo = new TypePositionInfo(collectionInfo.ElementType, collectionInfo.ElementMarshallingInfo) { ManagedIndex = info.ManagedIndex }; IMarshallingGenerator elementMarshaller = _elementMarshallingGenerator.Create( elementInfo, - new ContiguousCollectionElementMarshallingCodeContext(StubCodeContext.Stage.Setup, string.Empty, context)); + new LinearCollectionElementMarshallingCodeContext(StubCodeContext.Stage.Setup, string.Empty, string.Empty, context)); TypeSyntax elementType = elementMarshaller.AsNativeType(elementInfo); + + ExpressionSyntax numElementsExpression = LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0)); + if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In)) + { + // In this case, we need a numElementsExpression supplied from metadata, so we'll calculate it here. + numElementsExpression = GetNumElementsExpressionFromMarshallingInfo(info, collectionInfo.ElementCountInfo, context); + } + bool isBlittable = elementMarshaller is BlittableMarshaller; if (isBlittable) { - marshallingStrategy = new ContiguousBlittableElementCollectionMarshalling(marshallingStrategy, collectionInfo.ElementType.Syntax); + marshallingStrategy = new LinearCollectionWithBlittableElementsMarshalling(marshallingStrategy, collectionInfo.ElementType.Syntax, numElementsExpression); } else { - marshallingStrategy = new ContiguousNonBlittableElementCollectionMarshalling(marshallingStrategy, elementMarshaller, elementInfo); + marshallingStrategy = new LinearCollectionWithNonBlittableElementsMarshalling(marshallingStrategy, elementMarshaller, elementInfo, numElementsExpression); } // Explicitly insert the Value property handling here (before numElements handling) so that the numElements handling will be emitted before the Value property handling in unmarshalling. - if (collectionInfo.ValuePropertyType is not null) - { - marshallingStrategy = DecorateWithValuePropertyStrategy(collectionInfo, marshallingStrategy); - } - - ExpressionSyntax numElementsExpression = LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0)); - if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In)) + if (collectionInfo.NativeValueType is not null) { - // In this case, we need a numElementsExpression supplied from metadata, so we'll calculate it here. - numElementsExpression = GetNumElementsExpressionFromMarshallingInfo(info, collectionInfo.ElementCountInfo, context); + marshallingStrategy = DecorateWithTwoStageMarshallingStrategy(collectionInfo, marshallingStrategy); } - marshallingStrategy = new NumElementsExpressionMarshalling( + marshallingStrategy = new SizeOfElementMarshalling( marshallingStrategy, - numElementsExpression, SizeOfExpression(elementType)); if (collectionInfo.UseDefaultMarshalling && info.ManagedType is SzArrayType) @@ -299,7 +307,7 @@ private IMarshallingGenerator CreateNativeCollectionMarshaller( IMarshallingGenerator marshallingGenerator = new CustomNativeTypeMarshallingGenerator(marshallingStrategy, enableByValueContentsMarshalling: false); - if ((collectionInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedTypePinning) != 0) + if (collectionInfo.PinningFeatures.HasFlag(CustomTypeMarshallerPinning.ManagedType)) { return new PinnableManagedValueMarshaller(marshallingGenerator); } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ICustomNativeTypeMarshallingStrategy.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ICustomNativeTypeMarshallingStrategy.cs index 2160d9f1be3bd..cb617994e3b69 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ICustomNativeTypeMarshallingStrategy.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ICustomNativeTypeMarshallingStrategy.cs @@ -89,7 +89,7 @@ public IEnumerable GenerateUnmarshalStatements(TypePositionInfo InvocationExpression( MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(nativeIdentifier), - IdentifierName(ManualTypeMarshallingHelper.ToManagedMethodName))))); + IdentifierName(ShapeMemberNames.Value.ToManaged))))); } public IEnumerable GetNativeTypeConstructorArguments(TypePositionInfo info, StubCodeContext context) @@ -111,9 +111,9 @@ public IEnumerable GeneratePinStatements(TypePositionInfo info, /// /// A context that redefines the 'native' identifier for a TypePositionInfo to be the marshaller identifier. /// - internal class CustomNativeTypeWithValuePropertyStubContext : StubCodeContext + internal class CustomNativeTypeWithToFromNativeValueContext : StubCodeContext { - public CustomNativeTypeWithValuePropertyStubContext(StubCodeContext parentContext) + public CustomNativeTypeWithToFromNativeValueContext(StubCodeContext parentContext) { ParentContext = parentContext; CurrentStage = parentContext.CurrentStage; @@ -130,22 +130,22 @@ public override (string managed, string native) GetIdentifiers(TypePositionInfo } /// - /// Marshaller that enables support of a Value property on a native type. + /// Marshaller that enables support of ToNativeValue/FromNativeValue methods on a native type. /// - internal sealed class CustomNativeTypeWithValuePropertyMarshalling : ICustomNativeTypeMarshallingStrategy + internal sealed class CustomNativeTypeWithToFromNativeValueMarshalling : ICustomNativeTypeMarshallingStrategy { private readonly ICustomNativeTypeMarshallingStrategy _innerMarshaller; - private readonly TypeSyntax _valuePropertyType; + private readonly TypeSyntax _nativeValueTypeSyntax; - public CustomNativeTypeWithValuePropertyMarshalling(ICustomNativeTypeMarshallingStrategy innerMarshaller, TypeSyntax valuePropertyType) + public CustomNativeTypeWithToFromNativeValueMarshalling(ICustomNativeTypeMarshallingStrategy innerMarshaller, TypeSyntax nativeValueTypeSyntax) { _innerMarshaller = innerMarshaller; - _valuePropertyType = valuePropertyType; + _nativeValueTypeSyntax = nativeValueTypeSyntax; } public TypeSyntax AsNativeType(TypePositionInfo info) { - return _valuePropertyType; + return _nativeValueTypeSyntax; } public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) @@ -155,14 +155,14 @@ public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) public IEnumerable GenerateCleanupStatements(TypePositionInfo info, StubCodeContext context) { - var subContext = new CustomNativeTypeWithValuePropertyStubContext(context); + var subContext = new CustomNativeTypeWithToFromNativeValueContext(context); // When temporary state does not live across stages, the marshaller state is uninitialized // in any stage other than Marshal and Unmarshal. So, we need to reinitialize it here in Cleanup // from the native value so we can safely run any cleanup functionality in the marshaller. if (!context.AdditionalTemporaryStateLivesAcrossStages) { - yield return GenerateValuePropertyAssignment(info, context, subContext); + yield return GenerateFromNativeValueInvocation(info, context, subContext); } foreach (StatementSyntax statement in _innerMarshaller.GenerateCleanupStatements(info, subContext)) @@ -173,41 +173,42 @@ public IEnumerable GenerateCleanupStatements(TypePositionInfo i public IEnumerable GenerateMarshalStatements(TypePositionInfo info, StubCodeContext context, IEnumerable nativeTypeConstructorArguments) { - var subContext = new CustomNativeTypeWithValuePropertyStubContext(context); + var subContext = new CustomNativeTypeWithToFromNativeValueContext(context); foreach (StatementSyntax statement in _innerMarshaller.GenerateMarshalStatements(info, subContext, nativeTypeConstructorArguments)) { yield return statement; } - // = .Value; + // = .ToNativeValue(); yield return ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, IdentifierName(context.GetIdentifiers(info).native), - MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(subContext.GetIdentifiers(info).native), - IdentifierName(ManualTypeMarshallingHelper.ValuePropertyName)))); + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(subContext.GetIdentifiers(info).native), + IdentifierName(ShapeMemberNames.Value.ToNativeValue)), + ArgumentList()))); } - private StatementSyntax GenerateValuePropertyAssignment(TypePositionInfo info, StubCodeContext context, CustomNativeTypeWithValuePropertyStubContext subContext) + private StatementSyntax GenerateFromNativeValueInvocation(TypePositionInfo info, StubCodeContext context, CustomNativeTypeWithToFromNativeValueContext subContext) { - // .Value = ; + // .FromNativeValue(); return ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, + InvocationExpression( MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(subContext.GetIdentifiers(info).native), - IdentifierName(ManualTypeMarshallingHelper.ValuePropertyName)), - IdentifierName(context.GetIdentifiers(info).native))); + IdentifierName(ShapeMemberNames.Value.FromNativeValue)), + ArgumentList(SingletonSeparatedList(Argument(IdentifierName(context.GetIdentifiers(info).native)))))); } public IEnumerable GenerateUnmarshalStatements(TypePositionInfo info, StubCodeContext context) { - var subContext = new CustomNativeTypeWithValuePropertyStubContext(context); + var subContext = new CustomNativeTypeWithToFromNativeValueContext(context); if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In)) { - yield return GenerateValuePropertyAssignment(info, context, subContext); + yield return GenerateFromNativeValueInvocation(info, context, subContext); } foreach (StatementSyntax statement in _innerMarshaller.GenerateUnmarshalStatements(info, subContext)) @@ -218,13 +219,13 @@ public IEnumerable GenerateUnmarshalStatements(TypePositionInfo public IEnumerable GetNativeTypeConstructorArguments(TypePositionInfo info, StubCodeContext context) { - var subContext = new CustomNativeTypeWithValuePropertyStubContext(context); + var subContext = new CustomNativeTypeWithToFromNativeValueContext(context); return _innerMarshaller.GetNativeTypeConstructorArguments(info, subContext); } public IEnumerable GenerateSetupStatements(TypePositionInfo info, StubCodeContext context) { - var subContext = new CustomNativeTypeWithValuePropertyStubContext(context); + var subContext = new CustomNativeTypeWithToFromNativeValueContext(context); yield return LocalDeclarationStatement( VariableDeclaration( _innerMarshaller.AsNativeType(info), @@ -240,7 +241,7 @@ public IEnumerable GenerateSetupStatements(TypePositionInfo inf public IEnumerable GeneratePinStatements(TypePositionInfo info, StubCodeContext context) { - var subContext = new CustomNativeTypeWithValuePropertyStubContext(context); + var subContext = new CustomNativeTypeWithToFromNativeValueContext(context); return _innerMarshaller.GeneratePinStatements(info, subContext); } } @@ -251,10 +252,12 @@ public IEnumerable GeneratePinStatements(TypePositionInfo info, internal sealed class StackallocOptimizationMarshalling : ICustomNativeTypeMarshallingStrategy { private readonly ICustomNativeTypeMarshallingStrategy _innerMarshaller; + private readonly int _bufferSize; - public StackallocOptimizationMarshalling(ICustomNativeTypeMarshallingStrategy innerMarshaller) + public StackallocOptimizationMarshalling(ICustomNativeTypeMarshallingStrategy innerMarshaller, int bufferSize) { _innerMarshaller = innerMarshaller; + _bufferSize = bufferSize; } public TypeSyntax AsNativeType(TypePositionInfo info) @@ -271,7 +274,7 @@ public IEnumerable GenerateMarshalStatements(TypePositionInfo i { if (StackAllocOptimizationValid(info, context)) { - // byte* __stackptr = stackalloc byte[<_nativeLocalType>.BufferSize]; + // byte* __stackptr = stackalloc byte[<_bufferSize>]; yield return LocalDeclarationStatement( VariableDeclaration( PointerType(PredefinedType(Token(SyntaxKind.ByteKeyword))), @@ -282,9 +285,7 @@ public IEnumerable GenerateMarshalStatements(TypePositionInfo i ArrayType( PredefinedType(Token(SyntaxKind.ByteKeyword)), SingletonList(ArrayRankSpecifier(SingletonSeparatedList( - MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - AsNativeType(info), - IdentifierName(ManualTypeMarshallingHelper.BufferSizeFieldName)) + LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(_bufferSize)) )))))))))); } @@ -336,9 +337,7 @@ public IEnumerable GetNativeTypeConstructorArguments(TypePositio ArgumentList(SeparatedList(new ArgumentSyntax[] { Argument(IdentifierName(GetStackAllocPointerIdentifier(info, context))), - Argument(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - AsNativeType(info), - IdentifierName(ManualTypeMarshallingHelper.BufferSizeFieldName))) + Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(_bufferSize))) })))); } } @@ -378,7 +377,7 @@ public IEnumerable GenerateCleanupStatements(TypePositionInfo i InvocationExpression( MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(context.GetIdentifiers(info).native), - IdentifierName(ManualTypeMarshallingHelper.FreeNativeMethodName)))); + IdentifierName(ShapeMemberNames.Value.FreeNative)))); } public IEnumerable GenerateMarshalStatements(TypePositionInfo info, StubCodeContext context, IEnumerable nativeTypeConstructorArguments) @@ -413,17 +412,17 @@ public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) } /// - /// Marshaller that calls the GetPinnableReference method on the marshaller value and enables support for the Value property. + /// Marshaller that calls the GetPinnableReference method on the marshaller value and enables support for the ToNativeValue and FromNativeValue methods. /// internal sealed class PinnableMarshallerTypeMarshalling : ICustomNativeTypeMarshallingStrategy { private readonly ICustomNativeTypeMarshallingStrategy _innerMarshaller; - private readonly TypeSyntax _valuePropertyType; + private readonly TypeSyntax _nativeValueType; - public PinnableMarshallerTypeMarshalling(ICustomNativeTypeMarshallingStrategy innerMarshaller, TypeSyntax valuePropertyType) + public PinnableMarshallerTypeMarshalling(ICustomNativeTypeMarshallingStrategy innerMarshaller, TypeSyntax nativeValueType) { _innerMarshaller = innerMarshaller; - _valuePropertyType = valuePropertyType; + _nativeValueType = nativeValueType; } private bool CanPinMarshaller(TypePositionInfo info, StubCodeContext context) @@ -433,17 +432,17 @@ private bool CanPinMarshaller(TypePositionInfo info, StubCodeContext context) public TypeSyntax AsNativeType(TypePositionInfo info) { - return _valuePropertyType; + return _nativeValueType; } public IEnumerable GenerateCleanupStatements(TypePositionInfo info, StubCodeContext context) { - var subContext = new CustomNativeTypeWithValuePropertyStubContext(context); + var subContext = new CustomNativeTypeWithToFromNativeValueContext(context); if (!context.AdditionalTemporaryStateLivesAcrossStages) { // .Value = ; - yield return GenerateValuePropertyAssignment(info, context, subContext); + yield return GenerateFromNativeValueInvocation(info, context, subContext); } foreach (StatementSyntax statement in _innerMarshaller.GenerateCleanupStatements(info, subContext)) @@ -454,46 +453,48 @@ public IEnumerable GenerateCleanupStatements(TypePositionInfo i public IEnumerable GenerateMarshalStatements(TypePositionInfo info, StubCodeContext context, IEnumerable nativeTypeConstructorArguments) { - var subContext = new CustomNativeTypeWithValuePropertyStubContext(context); + var subContext = new CustomNativeTypeWithToFromNativeValueContext(context); foreach (StatementSyntax statement in _innerMarshaller.GenerateMarshalStatements(info, subContext, nativeTypeConstructorArguments)) { yield return statement; } if (!CanPinMarshaller(info, context)) - yield return GenerateNativeAssignmentFromValueProperty(info, context, subContext); + yield return GenerateToNativeValueInvocation(info, context, subContext); } - private static StatementSyntax GenerateNativeAssignmentFromValueProperty(TypePositionInfo info, StubCodeContext context, CustomNativeTypeWithValuePropertyStubContext subContext) + private static StatementSyntax GenerateToNativeValueInvocation(TypePositionInfo info, StubCodeContext context, CustomNativeTypeWithToFromNativeValueContext subContext) { - // = .Value; + // = .ToNativeValue(); return ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, IdentifierName(context.GetIdentifiers(info).native), - MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(subContext.GetIdentifiers(info).native), - IdentifierName(ManualTypeMarshallingHelper.ValuePropertyName)))); + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(subContext.GetIdentifiers(info).native), + IdentifierName(ShapeMemberNames.Value.ToNativeValue)), + ArgumentList()))); } public IEnumerable GeneratePinStatements(TypePositionInfo info, StubCodeContext context) { // fixed (<_nativeTypeSyntax> = &) // - var subContext = new CustomNativeTypeWithValuePropertyStubContext(context); + var subContext = new CustomNativeTypeWithToFromNativeValueContext(context); yield return FixedStatement( VariableDeclaration( - _valuePropertyType, + _nativeValueType, SingletonSeparatedList( VariableDeclarator(Identifier(context.GetAdditionalIdentifier(info, "ignored"))) .WithInitializer(EqualsValueClause( IdentifierName(subContext.GetIdentifiers(info).native))))), - GenerateNativeAssignmentFromValueProperty(info, context, subContext)); + GenerateToNativeValueInvocation(info, context, subContext)); } public IEnumerable GenerateSetupStatements(TypePositionInfo info, StubCodeContext context) { - var subContext = new CustomNativeTypeWithValuePropertyStubContext(context); + var subContext = new CustomNativeTypeWithToFromNativeValueContext(context); yield return LocalDeclarationStatement( VariableDeclaration( _innerMarshaller.AsNativeType(info), @@ -507,26 +508,25 @@ public IEnumerable GenerateSetupStatements(TypePositionInfo inf } } - private StatementSyntax GenerateValuePropertyAssignment(TypePositionInfo info, StubCodeContext context, CustomNativeTypeWithValuePropertyStubContext subContext) + private StatementSyntax GenerateFromNativeValueInvocation(TypePositionInfo info, StubCodeContext context, CustomNativeTypeWithToFromNativeValueContext subContext) { - // .Value = ; + // .FromNativeValue(); return ExpressionStatement( - AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, + InvocationExpression( MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(subContext.GetIdentifiers(info).native), - IdentifierName(ManualTypeMarshallingHelper.ValuePropertyName)), - IdentifierName(context.GetIdentifiers(info).native))); + IdentifierName(ShapeMemberNames.Value.FromNativeValue)), + ArgumentList(SingletonSeparatedList(Argument(IdentifierName(context.GetIdentifiers(info).native)))))); } public IEnumerable GenerateUnmarshalStatements(TypePositionInfo info, StubCodeContext context) { - var subContext = new CustomNativeTypeWithValuePropertyStubContext(context); + var subContext = new CustomNativeTypeWithToFromNativeValueContext(context); if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In)) { // .Value = ; - yield return GenerateValuePropertyAssignment(info, context, subContext); + yield return GenerateFromNativeValueInvocation(info, context, subContext); } foreach (StatementSyntax statement in _innerMarshaller.GenerateUnmarshalStatements(info, subContext)) @@ -537,7 +537,7 @@ public IEnumerable GenerateUnmarshalStatements(TypePositionInfo public IEnumerable GetNativeTypeConstructorArguments(TypePositionInfo info, StubCodeContext context) { - var subContext = new CustomNativeTypeWithValuePropertyStubContext(context); + var subContext = new CustomNativeTypeWithToFromNativeValueContext(context); return _innerMarshaller.GetNativeTypeConstructorArguments(info, subContext); } @@ -548,18 +548,16 @@ public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) } /// - /// Marshaller that enables support for native types with the constructor variants that take a sizeOfElement int parameter and that have a SetUnmarshalledCollectionLength method. + /// Marshaller that enables support for native types with the constructor variants that take a sizeOfElement int parameter. /// - internal sealed class NumElementsExpressionMarshalling : ICustomNativeTypeMarshallingStrategy + internal sealed class SizeOfElementMarshalling : ICustomNativeTypeMarshallingStrategy { private readonly ICustomNativeTypeMarshallingStrategy _innerMarshaller; - private readonly ExpressionSyntax _numElementsExpression; private readonly ExpressionSyntax _sizeOfElementExpression; - public NumElementsExpressionMarshalling(ICustomNativeTypeMarshallingStrategy innerMarshaller, ExpressionSyntax numElementsExpression, ExpressionSyntax sizeOfElementExpression) + public SizeOfElementMarshalling(ICustomNativeTypeMarshallingStrategy innerMarshaller, ExpressionSyntax sizeOfElementExpression) { _innerMarshaller = innerMarshaller; - _numElementsExpression = numElementsExpression; _sizeOfElementExpression = sizeOfElementExpression; } @@ -611,17 +609,6 @@ private IEnumerable GenerateUnmarshallerCollectionInitializatio IdentifierName(marshalerIdentifier), ImplicitObjectCreationExpression().AddArgumentListArguments(Argument(_sizeOfElementExpression)))); } - - if (info.IsByRef || !info.ByValueContentsMarshalKind.HasFlag(ByValueContentsMarshalKind.Out)) - { - yield return ExpressionStatement( - InvocationExpression( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(marshalerIdentifier), - IdentifierName(ManualTypeMarshallingHelper.SetUnmarshalledCollectionLengthMethodName))) - .AddArgumentListArguments(Argument(_numElementsExpression))); - } } public IEnumerable GenerateUnmarshalStatements(TypePositionInfo info, StubCodeContext context) @@ -658,17 +645,19 @@ public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) } /// - /// Marshaller that enables support for marshalling blittable elements of a contiguous collection via a native type that implements the contiguous collection marshalling spec. + /// Marshaller that enables support for marshalling blittable elements of a collection via a native type that implements the LinearCollection marshalling spec. /// - internal sealed class ContiguousBlittableElementCollectionMarshalling : ICustomNativeTypeMarshallingStrategy + internal sealed class LinearCollectionWithBlittableElementsMarshalling : ICustomNativeTypeMarshallingStrategy { private readonly ICustomNativeTypeMarshallingStrategy _innerMarshaller; private readonly TypeSyntax _elementType; + private readonly ExpressionSyntax _numElementsExpression; - public ContiguousBlittableElementCollectionMarshalling(ICustomNativeTypeMarshallingStrategy innerMarshaller, TypeSyntax elementType) + public LinearCollectionWithBlittableElementsMarshalling(ICustomNativeTypeMarshallingStrategy innerMarshaller, TypeSyntax elementType, ExpressionSyntax numElementsExpression) { _innerMarshaller = innerMarshaller; _elementType = elementType; + _numElementsExpression = numElementsExpression; } public TypeSyntax AsNativeType(TypePositionInfo info) @@ -695,15 +684,17 @@ public IEnumerable GenerateMarshalStatements(TypePositionInfo i yield break; } - // .ManagedValues.CopyTo(MemoryMarshal.Cast>(.NativeValueStorage)); + // .GetManagedValuesSource().CopyTo(MemoryMarshal.Cast>(.GetNativeValuesDestination())); yield return ExpressionStatement( InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(nativeIdentifier), - IdentifierName(ManualTypeMarshallingHelper.ManagedValuesPropertyName)), + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nativeIdentifier), + IdentifierName(ShapeMemberNames.LinearCollection.GetManagedValuesSource)), + ArgumentList()), IdentifierName("CopyTo"))) .AddArgumentListArguments( Argument( @@ -723,10 +714,12 @@ public IEnumerable GenerateMarshalStatements(TypePositionInfo i }))))) .AddArgumentListArguments( Argument( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(nativeIdentifier), - IdentifierName(ManualTypeMarshallingHelper.NativeValueStoragePropertyName))))))); + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nativeIdentifier), + IdentifierName(ShapeMemberNames.LinearCollection.GetNativeValuesDestination)), + ArgumentList())))))); } public IEnumerable GeneratePinStatements(TypePositionInfo info, StubCodeContext context) @@ -742,7 +735,13 @@ public IEnumerable GenerateSetupStatements(TypePositionInfo inf public IEnumerable GenerateUnmarshalStatements(TypePositionInfo info, StubCodeContext context) { string nativeIdentifier = context.GetIdentifiers(info).native; - // MemoryMarshal.Cast>(.NativeValueStorage).CopyTo(.ManagedValues); + string numElementsIdentifier = context.GetAdditionalIdentifier(info, "numElements"); + yield return LocalDeclarationStatement( + VariableDeclaration( + PredefinedType(Token(SyntaxKind.IntKeyword)), + SingletonSeparatedList( + VariableDeclarator(numElementsIdentifier).WithInitializer(EqualsValueClause(_numElementsExpression))))); + // MemoryMarshal.Cast>(.GetNativeValuesSource()).CopyTo(.GetManagedValuesDestination()); yield return ExpressionStatement( InvocationExpression( MemberAccessExpression( @@ -763,17 +762,21 @@ public IEnumerable GenerateUnmarshalStatements(TypePositionInfo }))))) .AddArgumentListArguments( Argument( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(nativeIdentifier), - IdentifierName(ManualTypeMarshallingHelper.NativeValueStoragePropertyName)))), + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nativeIdentifier), + IdentifierName(ShapeMemberNames.LinearCollection.GetNativeValuesSource)), + ArgumentList(SingletonSeparatedList(Argument(IdentifierName(numElementsIdentifier))))))), IdentifierName("CopyTo"))) .AddArgumentListArguments( Argument( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(nativeIdentifier), - IdentifierName(ManualTypeMarshallingHelper.ManagedValuesPropertyName))))); + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nativeIdentifier), + IdentifierName(ShapeMemberNames.LinearCollection.GetManagedValuesDestination)), + ArgumentList(SingletonSeparatedList(Argument(IdentifierName(numElementsIdentifier)))))))); foreach (StatementSyntax statement in _innerMarshaller.GenerateUnmarshalStatements(info, context)) { @@ -793,24 +796,27 @@ public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) } /// - /// Marshaller that enables support for marshalling non-blittable elements of a contiguous collection via a native type that implements the contiguous collection marshalling spec. + /// Marshaller that enables support for marshalling non-blittable elements of a collection via a native type that implements the LinearCollection marshalling spec. /// - internal sealed class ContiguousNonBlittableElementCollectionMarshalling : ICustomNativeTypeMarshallingStrategy + internal sealed class LinearCollectionWithNonBlittableElementsMarshalling : ICustomNativeTypeMarshallingStrategy { private readonly ICustomNativeTypeMarshallingStrategy _innerMarshaller; private readonly IMarshallingGenerator _elementMarshaller; private readonly TypePositionInfo _elementInfo; + private readonly ExpressionSyntax _numElementsExpression; - public ContiguousNonBlittableElementCollectionMarshalling(ICustomNativeTypeMarshallingStrategy innerMarshaller, + public LinearCollectionWithNonBlittableElementsMarshalling(ICustomNativeTypeMarshallingStrategy innerMarshaller, IMarshallingGenerator elementMarshaller, - TypePositionInfo elementInfo) + TypePositionInfo elementInfo, + ExpressionSyntax numElementsExpression) { _innerMarshaller = innerMarshaller; _elementMarshaller = elementMarshaller; _elementInfo = elementInfo; + _numElementsExpression = numElementsExpression; } - private LocalDeclarationStatementSyntax GenerateNativeSpanDeclaration(TypePositionInfo info, StubCodeContext context) + private LocalDeclarationStatementSyntax GenerateNativeValuesDestinationDeclaration(TypePositionInfo info, StubCodeContext context) { string nativeIdentifier = context.GetIdentifiers(info).native; string nativeSpanIdentifier = MarshallerHelpers.GetNativeSpanIdentifier(info, context); @@ -838,28 +844,105 @@ private LocalDeclarationStatementSyntax GenerateNativeSpanDeclaration(TypePositi _elementMarshaller.AsNativeType(_elementInfo).GetCompatibleGenericTypeParameterSyntax() }))))) .AddArgumentListArguments( - Argument(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + Argument( + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nativeIdentifier), + IdentifierName(ShapeMemberNames.LinearCollection.GetNativeValuesDestination)), + ArgumentList())))))))); + } + + private LocalDeclarationStatementSyntax GenerateNativeValuesSourceDeclaration(TypePositionInfo info, StubCodeContext context, string numElementsIdentifier) + { + string nativeIdentifier = context.GetIdentifiers(info).native; + string nativeSpanIdentifier = MarshallerHelpers.GetNativeSpanIdentifier(info, context); + return LocalDeclarationStatement(VariableDeclaration( + GenericName( + Identifier(TypeNames.System_ReadOnlySpan), + TypeArgumentList( + SingletonSeparatedList(_elementMarshaller.AsNativeType(_elementInfo).GetCompatibleGenericTypeParameterSyntax())) + ), + SingletonSeparatedList( + VariableDeclarator(Identifier(nativeSpanIdentifier)) + .WithInitializer(EqualsValueClause( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ParseTypeName(TypeNames.System_Runtime_InteropServices_MemoryMarshal), + GenericName( + Identifier("Cast")) + .WithTypeArgumentList( + TypeArgumentList( + SeparatedList( + new[] + { + PredefinedType(Token(SyntaxKind.ByteKeyword)), + _elementMarshaller.AsNativeType(_elementInfo).GetCompatibleGenericTypeParameterSyntax() + }))))) + .AddArgumentListArguments( + Argument( + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nativeIdentifier), + IdentifierName(ShapeMemberNames.LinearCollection.GetNativeValuesSource)), + ArgumentList(SingletonSeparatedList(Argument(IdentifierName(numElementsIdentifier)))))))))))); + } + + private LocalDeclarationStatementSyntax GeneratedManagedValuesSourceDeclaration(TypePositionInfo info, StubCodeContext context) + { + string nativeIdentifier = context.GetIdentifiers(info).native; + string managedSpanIdentifier = MarshallerHelpers.GetManagedSpanIdentifier(info, context); + return LocalDeclarationStatement(VariableDeclaration( + GenericName( + Identifier(TypeNames.System_ReadOnlySpan), + TypeArgumentList( + SingletonSeparatedList(_elementInfo.ManagedType.Syntax)) + ), + SingletonSeparatedList( + VariableDeclarator(Identifier(managedSpanIdentifier)) + .WithInitializer(EqualsValueClause( + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(nativeIdentifier), - IdentifierName(ManualTypeMarshallingHelper.NativeValueStoragePropertyName))))))))); + IdentifierName(ShapeMemberNames.LinearCollection.GetManagedValuesSource)), + ArgumentList())))))); } - private StatementSyntax GenerateContentsMarshallingStatement(TypePositionInfo info, StubCodeContext context, bool useManagedSpanForLength) + private LocalDeclarationStatementSyntax GeneratedManagedValuesDestinationDeclaration(TypePositionInfo info, StubCodeContext context, string numElementsIdentifier) { string nativeIdentifier = context.GetIdentifiers(info).native; + string managedSpanIdentifier = MarshallerHelpers.GetManagedSpanIdentifier(info, context); + return LocalDeclarationStatement(VariableDeclaration( + GenericName( + Identifier(TypeNames.System_Span), + TypeArgumentList( + SingletonSeparatedList(_elementInfo.ManagedType.Syntax)) + ), + SingletonSeparatedList( + VariableDeclarator(Identifier(managedSpanIdentifier)) + .WithInitializer(EqualsValueClause( + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nativeIdentifier), + IdentifierName(ShapeMemberNames.LinearCollection.GetManagedValuesDestination)), + ArgumentList(SingletonSeparatedList(Argument(IdentifierName(numElementsIdentifier)))))))))); + } + + private StatementSyntax GenerateContentsMarshallingStatement(TypePositionInfo info, StubCodeContext context, ExpressionSyntax lengthExpression) + { + string managedSpanIdentifier = MarshallerHelpers.GetManagedSpanIdentifier(info, context); string nativeSpanIdentifier = MarshallerHelpers.GetNativeSpanIdentifier(info, context); - var elementSetupSubContext = new ContiguousCollectionElementMarshallingCodeContext( + var elementSetupSubContext = new LinearCollectionElementMarshallingCodeContext( StubCodeContext.Stage.Setup, + managedSpanIdentifier, nativeSpanIdentifier, context); - var elementSubContext = new ContiguousCollectionElementMarshallingCodeContext( + var elementSubContext = new LinearCollectionElementMarshallingCodeContext( context.CurrentStage, + managedSpanIdentifier, nativeSpanIdentifier, context); - string collectionIdentifierForLength = useManagedSpanForLength - ? $"{nativeIdentifier}.{ManualTypeMarshallingHelper.ManagedValuesPropertyName}" - : nativeSpanIdentifier; - TypePositionInfo localElementInfo = _elementInfo with { InstanceIdentifier = info.InstanceIdentifier, @@ -883,10 +966,8 @@ private StatementSyntax GenerateContentsMarshallingStatement(TypePositionInfo in } // Iterate through the elements of the native collection to unmarshal them - return Block( - GenerateNativeSpanDeclaration(info, context), - MarshallerHelpers.GetForLoop(collectionIdentifierForLength, elementSubContext.IndexerIdentifier) - .WithStatement(marshallingStatement)); + return MarshallerHelpers.GetForLoop(lengthExpression, elementSubContext.IndexerIdentifier) + .WithStatement(marshallingStatement); } return EmptyStatement(); } @@ -898,7 +979,18 @@ public TypeSyntax AsNativeType(TypePositionInfo info) public IEnumerable GenerateCleanupStatements(TypePositionInfo info, StubCodeContext context) { - yield return GenerateContentsMarshallingStatement(info, context, useManagedSpanForLength: false); + StatementSyntax contentsCleanupStatements = GenerateContentsMarshallingStatement(info, context, + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(MarshallerHelpers.GetNativeSpanIdentifier(info, context)), + IdentifierName("Length"))); + + if (!contentsCleanupStatements.IsKind(SyntaxKind.EmptyStatement)) + { + yield return Block( + GenerateNativeValuesDestinationDeclaration(info, context), + contentsCleanupStatements); + } + foreach (StatementSyntax statement in _innerMarshaller.GenerateCleanupStatements(info, context)) { yield return statement; @@ -916,21 +1008,29 @@ public IEnumerable GenerateMarshalStatements(TypePositionInfo i { // If the parameter is marshalled by-value [Out], then we don't marshal the contents of the collection. // We do clear the span, so that if the invoke target doesn't fill it, we aren't left with undefined content. - // .NativeValueStorage.Clear(); + // .GetNativeValuesDestination().Clear(); string nativeIdentifier = context.GetIdentifiers(info).native; yield return ExpressionStatement( InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(nativeIdentifier), - IdentifierName(ManualTypeMarshallingHelper.NativeValueStoragePropertyName)), + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nativeIdentifier), + IdentifierName(ShapeMemberNames.LinearCollection.GetNativeValuesDestination)), + ArgumentList()), IdentifierName("Clear")))); yield break; } - yield return GenerateContentsMarshallingStatement(info, context, useManagedSpanForLength: true); + yield return Block( + GeneratedManagedValuesSourceDeclaration(info, context), + GenerateNativeValuesDestinationDeclaration(info, context), + GenerateContentsMarshallingStatement(info, context, + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(MarshallerHelpers.GetManagedSpanIdentifier(info, context)), + IdentifierName("Length")))); } public IEnumerable GeneratePinStatements(TypePositionInfo info, StubCodeContext context) @@ -943,9 +1043,101 @@ public IEnumerable GenerateSetupStatements(TypePositionInfo inf return _innerMarshaller.GenerateSetupStatements(info, context); } + private StatementSyntax GenerateByValueUnmarshalStatement(TypePositionInfo info, StubCodeContext context) + { + // Use ManagedSource and NativeDestination spans for by-value marshalling since we're just marshalling back the contents, + // not the array itself. + // This code is ugly since we're now enforcing readonly safety with ReadOnlySpan for all other scenarios, + // but this is an uncommon case so we don't want to design the API around enabling just it. + var (_, nativeIdentifier) = context.GetIdentifiers(info); + string numElementsIdentifier = context.GetAdditionalIdentifier(info, "numElements"); + // int = .GetManagedValuesSource().Length; + LocalDeclarationStatementSyntax numElementsDeclaration = LocalDeclarationStatement( + VariableDeclaration( + PredefinedType(Token(SyntaxKind.IntKeyword)), + SingletonSeparatedList( + VariableDeclarator(numElementsIdentifier).WithInitializer(EqualsValueClause( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nativeIdentifier), + IdentifierName(ShapeMemberNames.LinearCollection.GetManagedValuesSource)), + ArgumentList()), + IdentifierName("Length"))))))); + + string managedSpanIdentifier = MarshallerHelpers.GetManagedSpanIdentifier(info, context); + // Span = MemoryMarshal.CreateSpan(ref Unsafe.AsRef(ref .GetManagedValuesSource().GetPinnableReference(), )); + LocalDeclarationStatementSyntax managedValuesDeclaration = LocalDeclarationStatement(VariableDeclaration( + GenericName( + Identifier(TypeNames.System_Span), + TypeArgumentList( + SingletonSeparatedList(_elementInfo.ManagedType.Syntax)) + ), + SingletonSeparatedList(VariableDeclarator(managedSpanIdentifier).WithInitializer(EqualsValueClause( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ParseName(TypeNames.System_Runtime_InteropServices_MemoryMarshal), + IdentifierName("CreateSpan"))) + .WithArgumentList( + ArgumentList( + SeparatedList( + new[] + { + Argument( + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + ParseName(TypeNames.System_Runtime_CompilerServices_Unsafe), + IdentifierName("AsRef")), + ArgumentList(SingletonSeparatedList( + Argument( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nativeIdentifier), + IdentifierName(ShapeMemberNames.LinearCollection.GetManagedValuesSource)), + ArgumentList()), + IdentifierName("GetPinnableReference")), + ArgumentList())) + .WithRefKindKeyword( + Token(SyntaxKind.InKeyword)))))) + .WithRefKindKeyword( + Token(SyntaxKind.RefKeyword)), + Argument( + IdentifierName(numElementsIdentifier)) + })))))))); + + LocalDeclarationStatementSyntax nativeValuesDeclaration = GenerateNativeValuesDestinationDeclaration(info, context); + + return Block(numElementsDeclaration, + managedValuesDeclaration, + nativeValuesDeclaration, + GenerateContentsMarshallingStatement(info, context, + IdentifierName(numElementsIdentifier))); + } + public IEnumerable GenerateUnmarshalStatements(TypePositionInfo info, StubCodeContext context) { - yield return GenerateContentsMarshallingStatement(info, context, useManagedSpanForLength: false); + string numElementsIdentifier = context.GetAdditionalIdentifier(info, "numElements"); + if (!info.IsByRef && info.ByValueContentsMarshalKind.HasFlag(ByValueContentsMarshalKind.Out)) + { + yield return GenerateByValueUnmarshalStatement(info, context); + } + else + { + yield return Block(LocalDeclarationStatement( + VariableDeclaration( + PredefinedType(Token(SyntaxKind.IntKeyword)), + SingletonSeparatedList( + VariableDeclarator(numElementsIdentifier).WithInitializer(EqualsValueClause(_numElementsExpression))))), + GeneratedManagedValuesDestinationDeclaration(info, context, numElementsIdentifier), + GenerateNativeValuesSourceDeclaration(info, context, numElementsIdentifier), + GenerateContentsMarshallingStatement(info, context, + IdentifierName(numElementsIdentifier))); + } + foreach (StatementSyntax statement in _innerMarshaller.GenerateUnmarshalStatements(info, context)) { yield return statement; @@ -992,6 +1184,16 @@ public override SyntaxNode VisitAssignmentExpression(AssignmentExpressionSyntax return node; } + + public override SyntaxNode? VisitArgument(ArgumentSyntax node) + { + if (node.Expression.ToString() == _nativeIdentifier) + { + return node.WithExpression( + CastExpression(_nativeType, node.Expression)); + } + return node; + } } } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs index 01b0c4aae2bf5..dc1a04c698110 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallerHelpers.cs @@ -22,9 +22,9 @@ public static class MarshallerHelpers public static readonly TypeSyntax SystemIntPtrType = ParseTypeName(TypeNames.System_IntPtr); - public static ForStatementSyntax GetForLoop(string collectionIdentifier, string indexerIdentifier) + public static ForStatementSyntax GetForLoop(ExpressionSyntax lengthExpression, string indexerIdentifier) { - // for(int = 0; < .Length; ++) + // for(int = 0; < ; ++) // ; return ForStatement(EmptyStatement()) .WithDeclaration( @@ -32,7 +32,7 @@ public static ForStatementSyntax GetForLoop(string collectionIdentifier, string PredefinedType( Token(SyntaxKind.IntKeyword))) .WithVariables( - SingletonSeparatedList( + SingletonSeparatedList( VariableDeclarator( Identifier(indexerIdentifier)) .WithInitializer( @@ -44,10 +44,7 @@ public static ForStatementSyntax GetForLoop(string collectionIdentifier, string BinaryExpression( SyntaxKind.LessThanExpression, IdentifierName(indexerIdentifier), - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(collectionIdentifier), - IdentifierName("Length")))) + lengthExpression)) .WithIncrementors( SingletonSeparatedList( PrefixUnaryExpression( @@ -102,6 +99,11 @@ public static string GetMarshallerIdentifier(TypePositionInfo info, StubCodeCont return context.GetAdditionalIdentifier(info, "marshaller"); } + public static string GetManagedSpanIdentifier(TypePositionInfo info, StubCodeContext context) + { + return context.GetAdditionalIdentifier(info, "managedSpan"); + } + public static string GetNativeSpanIdentifier(TypePositionInfo info, StubCodeContext context) { return context.GetAdditionalIdentifier(info, "nativeSpan"); @@ -232,7 +234,7 @@ public bool AnyIncomingEdge(int to) public static IEnumerable GetDependentElementsOfMarshallingInfo( MarshallingInfo elementMarshallingInfo) { - if (elementMarshallingInfo is NativeContiguousCollectionMarshallingInfo nestedCollection) + if (elementMarshallingInfo is NativeLinearCollectionMarshallingInfo nestedCollection) { if (nestedCollection.ElementCountInfo is CountElementCountInfo { ElementInfo: TypePositionInfo nestedCountElement }) { diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallingGeneratorExtensions.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallingGeneratorExtensions.cs index 6cd7da3f43057..8de5682ac0158 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallingGeneratorExtensions.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallingGeneratorExtensions.cs @@ -118,7 +118,7 @@ private static bool TryRehydrateMarshalAsAttribute(TypePositionInfo info, out At { CountInfo countInfo; MarshallingInfo elementMarshallingInfo; - if (info.MarshallingAttributeInfo is NativeContiguousCollectionMarshallingInfo collectionMarshalling + if (info.MarshallingAttributeInfo is NativeLinearCollectionMarshallingInfo collectionMarshalling && collectionMarshalling.UseDefaultMarshalling && collectionMarshalling.ElementCountInfo is NoCountInfo or SizeAndParamIndexInfo && collectionMarshalling.ElementMarshallingInfo is NoMarshallingInfo or MarshalAsInfo { UnmanagedType: not UnmanagedType.CustomMarshaler } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs index 0000772d5442e..86bab61b2200c 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshallingAttributeInfo.cs @@ -98,15 +98,11 @@ public sealed record MarshalAsInfo( public sealed record UnmanagedBlittableMarshallingInfo : MarshallingInfo; [Flags] - public enum CustomMarshallingFeatures + public enum CustomTypeMarshallerPinning { None = 0, - ManagedToNative = 0x1, - NativeToManaged = 0x2, - ManagedToNativeStackalloc = 0x4, - ManagedTypePinning = 0x8, - NativeTypePinning = 0x10, - FreeNativeResources = 0x20, + ManagedType = 0x1, + NativeType = 0x2 } public abstract record CountInfo @@ -142,9 +138,12 @@ public sealed record SizeAndParamIndexInfo(int ConstSize, TypePositionInfo? Para /// public record NativeMarshallingAttributeInfo( ManagedTypeInfo NativeMarshallingType, - ManagedTypeInfo? ValuePropertyType, - CustomMarshallingFeatures MarshallingFeatures, - bool UseDefaultMarshalling) : MarshallingInfo; + ManagedTypeInfo? NativeValueType, + CustomTypeMarshallerDirection Direction, + CustomTypeMarshallerFeatures MarshallingFeatures, + CustomTypeMarshallerPinning PinningFeatures, + bool UseDefaultMarshalling, + int? BufferSize) : MarshallingInfo; /// /// User-applied System.Runtime.InteropServices.GeneratedMarshallingAttribute @@ -162,18 +161,24 @@ public sealed record SafeHandleMarshallingInfo(bool AccessibleDefaultConstructor /// User-applied System.Runtime.InteropServices.NativeMarshallingAttribute /// with a contiguous collection marshaller /// - public sealed record NativeContiguousCollectionMarshallingInfo( + public sealed record NativeLinearCollectionMarshallingInfo( ManagedTypeInfo NativeMarshallingType, - ManagedTypeInfo? ValuePropertyType, - CustomMarshallingFeatures MarshallingFeatures, + ManagedTypeInfo? NativeValueType, + CustomTypeMarshallerDirection Direction, + CustomTypeMarshallerFeatures MarshallingFeatures, + CustomTypeMarshallerPinning PinningFeatures, bool UseDefaultMarshalling, + int? BufferSize, CountInfo ElementCountInfo, ManagedTypeInfo ElementType, MarshallingInfo ElementMarshallingInfo) : NativeMarshallingAttributeInfo( NativeMarshallingType, - ValuePropertyType, + NativeValueType, + Direction, MarshallingFeatures, - UseDefaultMarshalling + PinningFeatures, + UseDefaultMarshalling, + BufferSize ); @@ -223,36 +228,36 @@ private MarshallingInfo ParseMarshallingInfo( IEnumerable useSiteAttributes, ImmutableHashSet inspectedElements) { - Dictionary marshallingAttributesByIndirectionLevel = new(); + Dictionary marshallingAttributesByIndirectionDepth = new(); int maxIndirectionLevelDataProvided = 0; foreach (AttributeData attribute in useSiteAttributes) { if (TryGetAttributeIndirectionLevel(attribute, out int indirectionLevel)) { - if (marshallingAttributesByIndirectionLevel.ContainsKey(indirectionLevel)) + if (marshallingAttributesByIndirectionDepth.ContainsKey(indirectionLevel)) { _diagnostics.ReportInvalidMarshallingAttributeInfo(attribute, nameof(Resources.DuplicateMarshallingInfo), indirectionLevel.ToString()); return NoMarshallingInfo.Instance; } - marshallingAttributesByIndirectionLevel.Add(indirectionLevel, attribute); + marshallingAttributesByIndirectionDepth.Add(indirectionLevel, attribute); maxIndirectionLevelDataProvided = Math.Max(maxIndirectionLevelDataProvided, indirectionLevel); } } - int maxIndirectionLevelUsed = 0; + int maxIndirectionDepthUsed = 0; MarshallingInfo info = GetMarshallingInfo( managedType, - marshallingAttributesByIndirectionLevel, + marshallingAttributesByIndirectionDepth, indirectionLevel: 0, inspectedElements, - ref maxIndirectionLevelUsed); - if (maxIndirectionLevelUsed < maxIndirectionLevelDataProvided) + ref maxIndirectionDepthUsed); + if (maxIndirectionDepthUsed < maxIndirectionLevelDataProvided) { _diagnostics.ReportInvalidMarshallingAttributeInfo( - marshallingAttributesByIndirectionLevel[maxIndirectionLevelDataProvided], + marshallingAttributesByIndirectionDepth[maxIndirectionLevelDataProvided], nameof(Resources.ExtraneousMarshallingInfo), maxIndirectionLevelDataProvided.ToString(), - maxIndirectionLevelUsed.ToString()); + maxIndirectionDepthUsed.ToString()); } return info; } @@ -262,9 +267,9 @@ private MarshallingInfo GetMarshallingInfo( Dictionary useSiteAttributes, int indirectionLevel, ImmutableHashSet inspectedElements, - ref int maxIndirectionLevelUsed) + ref int maxIndirectionDepthUsed) { - maxIndirectionLevelUsed = Math.Max(indirectionLevel, maxIndirectionLevelUsed); + maxIndirectionDepthUsed = Math.Max(indirectionLevel, maxIndirectionDepthUsed); CountInfo parsedCountInfo = NoCountInfo.Instance; if (useSiteAttributes.TryGetValue(indirectionLevel, out AttributeData useSiteAttribute)) @@ -275,7 +280,7 @@ private MarshallingInfo GetMarshallingInfo( && SymbolEqualityComparer.Default.Equals(_compilation.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_MarshalAsAttribute), attributeClass)) { // https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshalasattribute - return CreateInfoFromMarshalAs(type, useSiteAttribute, inspectedElements, ref maxIndirectionLevelUsed); + return CreateInfoFromMarshalAs(type, useSiteAttribute, inspectedElements, ref maxIndirectionDepthUsed); } else if (SymbolEqualityComparer.Default.Equals(_compilation.GetTypeByMetadataName(TypeNames.MarshalUsingAttribute), attributeClass)) { @@ -296,7 +301,7 @@ private MarshallingInfo GetMarshallingInfo( parsedCountInfo, useSiteAttributes, inspectedElements, - ref maxIndirectionLevelUsed); + ref maxIndirectionDepthUsed); } } } @@ -318,7 +323,7 @@ private MarshallingInfo GetMarshallingInfo( parsedCountInfo, useSiteAttributes, inspectedElements, - ref maxIndirectionLevelUsed); + ref maxIndirectionDepthUsed); } else if (attributeClass.ToDisplayString() == TypeNames.GeneratedMarshallingAttribute) { @@ -334,7 +339,7 @@ private MarshallingInfo GetMarshallingInfo( indirectionLevel, useSiteAttributes, inspectedElements, - ref maxIndirectionLevelUsed, + ref maxIndirectionDepthUsed, out MarshallingInfo infoMaybe)) { return infoMaybe; @@ -467,7 +472,7 @@ private MarshallingInfo CreateInfoFromMarshalAs( ITypeSymbol type, AttributeData attrData, ImmutableHashSet inspectedElements, - ref int maxIndirectionLevelUsed) + ref int maxIndirectionDepthUsed) { object unmanagedTypeObj = attrData.ConstructorArguments[0].Value!; UnmanagedType unmanagedType = unmanagedTypeObj is short unmanagedTypeAsShort @@ -547,8 +552,8 @@ private MarshallingInfo CreateInfoFromMarshalAs( } else { - maxIndirectionLevelUsed = 1; - elementMarshallingInfo = GetMarshallingInfo(elementType, new Dictionary(), 1, ImmutableHashSet.Empty, ref maxIndirectionLevelUsed); + maxIndirectionDepthUsed = 1; + elementMarshallingInfo = GetMarshallingInfo(elementType, new Dictionary(), 1, ImmutableHashSet.Empty, ref maxIndirectionDepthUsed); } INamedTypeSymbol? arrayMarshaller; @@ -568,13 +573,20 @@ private MarshallingInfo CreateInfoFromMarshalAs( return new MissingSupportCollectionMarshallingInfo(arraySizeInfo, elementMarshallingInfo); } - ITypeSymbol? valuePropertyType = ManualTypeMarshallingHelper.FindValueProperty(arrayMarshaller)?.Type; + var (_, _, customTypeMarshallerData) = ManualTypeMarshallingHelper.GetMarshallerShapeInfo(arrayMarshaller); + + Debug.Assert(customTypeMarshallerData is not null); - return new NativeContiguousCollectionMarshallingInfo( + ITypeSymbol? nativeValueType = ManualTypeMarshallingHelper.FindToNativeValueMethod(arrayMarshaller)?.ReturnType; + + return new NativeLinearCollectionMarshallingInfo( NativeMarshallingType: ManagedTypeInfo.CreateTypeInfoForTypeSymbol(arrayMarshaller), - ValuePropertyType: valuePropertyType is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(valuePropertyType) : null, - MarshallingFeatures: ~CustomMarshallingFeatures.ManagedTypePinning, + NativeValueType: nativeValueType is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(nativeValueType) : null, + Direction: customTypeMarshallerData.Value.Direction, + MarshallingFeatures: customTypeMarshallerData.Value.Features, + PinningFeatures: CustomTypeMarshallerPinning.NativeType, UseDefaultMarshalling: true, + BufferSize: customTypeMarshallerData.Value.BufferSize, ElementCountInfo: arraySizeInfo, ElementType: ManagedTypeInfo.CreateTypeInfoForTypeSymbol(elementType), ElementMarshallingInfo: elementMarshallingInfo); @@ -589,16 +601,9 @@ private MarshallingInfo CreateNativeMarshallingInfo( CountInfo parsedCountInfo, Dictionary useSiteAttributes, ImmutableHashSet inspectedElements, - ref int maxIndirectionLevelUsed) + ref int maxIndirectionDepthUsed) { - CustomMarshallingFeatures features = CustomMarshallingFeatures.None; - - if (!isMarshalUsingAttribute && ManualTypeMarshallingHelper.FindGetPinnableReference(type) is not null) - { - features |= CustomMarshallingFeatures.ManagedTypePinning; - } - - ITypeSymbol spanOfByte = _compilation.GetTypeByMetadataName(TypeNames.System_Span_Metadata)!.Construct(_compilation.GetSpecialType(SpecialType.System_Byte)); + INamedTypeSymbol readOnlySpanOfT = _compilation.GetTypeByMetadataName(TypeNames.System_ReadOnlySpan_Metadata)!; if (nativeType.IsUnboundGenericType) { @@ -626,93 +631,55 @@ private MarshallingInfo CreateNativeMarshallingInfo( } } - ITypeSymbol contiguousCollectionMarshalerAttribute = _compilation.GetTypeByMetadataName(TypeNames.GenericContiguousCollectionMarshallerAttribute)!; - - bool isContiguousCollectionMarshaller = nativeType.GetAttributes().Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, contiguousCollectionMarshalerAttribute)); - IPropertySymbol? valueProperty = ManualTypeMarshallingHelper.FindValueProperty(nativeType); - - ManualTypeMarshallingHelper.NativeTypeMarshallingVariant marshallingVariant = isContiguousCollectionMarshaller - ? ManualTypeMarshallingHelper.NativeTypeMarshallingVariant.ContiguousCollection - : ManualTypeMarshallingHelper.NativeTypeMarshallingVariant.Standard; - - bool hasInt32Constructor = false; - foreach (IMethodSymbol ctor in nativeType.Constructors) - { - if (ManualTypeMarshallingHelper.IsManagedToNativeConstructor(ctor, type, marshallingVariant) && (valueProperty is null or { GetMethod: not null })) - { - features |= CustomMarshallingFeatures.ManagedToNative; - } - else if (ManualTypeMarshallingHelper.IsCallerAllocatedSpanConstructor(ctor, type, spanOfByte, marshallingVariant) - && (valueProperty is null or { GetMethod: not null })) - { - features |= CustomMarshallingFeatures.ManagedToNativeStackalloc; - } - else if (ctor.Parameters.Length == 1 && ctor.Parameters[0].Type.SpecialType == SpecialType.System_Int32) - { - hasInt32Constructor = true; - } - } - - // The constructor that takes only the native element size is required for collection marshallers - // in the native-to-managed scenario. - if ((!isContiguousCollectionMarshaller - || (hasInt32Constructor && ManualTypeMarshallingHelper.HasSetUnmarshalledCollectionLengthMethod(nativeType))) - && ManualTypeMarshallingHelper.HasToManagedMethod(nativeType, type) - && (valueProperty is null or { SetMethod: not null })) + var (_, _, customTypeMarshallerData) = ManualTypeMarshallingHelper.GetMarshallerShapeInfo(nativeType); + if (customTypeMarshallerData is null) { - features |= CustomMarshallingFeatures.NativeToManaged; - } - - if (features == CustomMarshallingFeatures.None) - { - _diagnostics.ReportInvalidMarshallingAttributeInfo( - attrData, - isContiguousCollectionMarshaller - ? nameof(Resources.CollectionNativeTypeMustHaveRequiredShapeMessage) - : nameof(Resources.NativeTypeMustHaveRequiredShapeMessage), - nativeType.ToDisplayString()); return NoMarshallingInfo.Instance; } - if (ManualTypeMarshallingHelper.HasFreeNativeMethod(nativeType)) + CustomTypeMarshallerPinning pinning = CustomTypeMarshallerPinning.None; + + if (!isMarshalUsingAttribute && ManualTypeMarshallingHelper.FindGetPinnableReference(type) is not null) { - features |= CustomMarshallingFeatures.FreeNativeResources; + pinning |= CustomTypeMarshallerPinning.ManagedType; } if (ManualTypeMarshallingHelper.FindGetPinnableReference(nativeType) is not null) { - features |= CustomMarshallingFeatures.NativeTypePinning; + pinning |= CustomTypeMarshallerPinning.NativeType; } - if (isContiguousCollectionMarshaller) - { - if (!ManualTypeMarshallingHelper.HasNativeValueStorageProperty(nativeType, spanOfByte)) - { - _diagnostics.ReportInvalidMarshallingAttributeInfo(attrData, nameof(Resources.CollectionNativeTypeMustHaveRequiredShapeMessage), nativeType.ToDisplayString()); - return NoMarshallingInfo.Instance; - } + IMethodSymbol? toNativeValueMethod = ManualTypeMarshallingHelper.FindToNativeValueMethod(nativeType); - if (!ManualTypeMarshallingHelper.TryGetElementTypeFromContiguousCollectionMarshaller(nativeType, out ITypeSymbol elementType)) + if (customTypeMarshallerData.Value.Kind == CustomTypeMarshallerKind.LinearCollection) + { + if (!ManualTypeMarshallingHelper.TryGetElementTypeFromLinearCollectionMarshaller(nativeType, readOnlySpanOfT, out ITypeSymbol elementType)) { _diagnostics.ReportInvalidMarshallingAttributeInfo(attrData, nameof(Resources.CollectionNativeTypeMustHaveRequiredShapeMessage), nativeType.ToDisplayString()); return NoMarshallingInfo.Instance; } - return new NativeContiguousCollectionMarshallingInfo( + return new NativeLinearCollectionMarshallingInfo( ManagedTypeInfo.CreateTypeInfoForTypeSymbol(nativeType), - valueProperty is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(valueProperty.Type) : null, - features, + toNativeValueMethod is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(toNativeValueMethod.ReturnType) : null, + customTypeMarshallerData.Value.Direction, + customTypeMarshallerData.Value.Features, + pinning, UseDefaultMarshalling: !isMarshalUsingAttribute, + customTypeMarshallerData.Value.BufferSize, parsedCountInfo, ManagedTypeInfo.CreateTypeInfoForTypeSymbol(elementType), - GetMarshallingInfo(elementType, useSiteAttributes, indirectionLevel + 1, inspectedElements, ref maxIndirectionLevelUsed)); + GetMarshallingInfo(elementType, useSiteAttributes, indirectionLevel + 1, inspectedElements, ref maxIndirectionDepthUsed)); } return new NativeMarshallingAttributeInfo( ManagedTypeInfo.CreateTypeInfoForTypeSymbol(nativeType), - valueProperty is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(valueProperty.Type) : null, - features, - UseDefaultMarshalling: !isMarshalUsingAttribute); + toNativeValueMethod is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(toNativeValueMethod.ReturnType) : null, + customTypeMarshallerData.Value.Direction, + customTypeMarshallerData.Value.Features, + pinning, + UseDefaultMarshalling: !isMarshalUsingAttribute, + customTypeMarshallerData.Value.BufferSize); } private bool TryCreateTypeBasedMarshallingInfo( @@ -721,7 +688,7 @@ private bool TryCreateTypeBasedMarshallingInfo( int indirectionLevel, Dictionary useSiteAttributes, ImmutableHashSet inspectedElements, - ref int maxIndirectionLevelUsed, + ref int maxIndirectionDepthUsed, out MarshallingInfo marshallingInfo) { // Check for an implicit SafeHandle conversion. @@ -767,20 +734,24 @@ private bool TryCreateTypeBasedMarshallingInfo( if (arrayMarshaller is null) { // If the array marshaler type is not available, then we cannot marshal arrays but indicate it is missing. - marshallingInfo = new MissingSupportCollectionMarshallingInfo(parsedCountInfo, GetMarshallingInfo(elementType, useSiteAttributes, indirectionLevel + 1, inspectedElements, ref maxIndirectionLevelUsed)); + marshallingInfo = new MissingSupportCollectionMarshallingInfo(parsedCountInfo, GetMarshallingInfo(elementType, useSiteAttributes, indirectionLevel + 1, inspectedElements, ref maxIndirectionDepthUsed)); return true; } - ITypeSymbol? valuePropertyType = ManualTypeMarshallingHelper.FindValueProperty(arrayMarshaller)?.Type; + var (_, _, customTypeMarshallerData) = ManualTypeMarshallingHelper.GetMarshallerShapeInfo(arrayMarshaller); + ITypeSymbol? valuePropertyType = ManualTypeMarshallingHelper.FindToNativeValueMethod(arrayMarshaller)?.ReturnType; - marshallingInfo = new NativeContiguousCollectionMarshallingInfo( + marshallingInfo = new NativeLinearCollectionMarshallingInfo( NativeMarshallingType: ManagedTypeInfo.CreateTypeInfoForTypeSymbol(arrayMarshaller), - ValuePropertyType: valuePropertyType is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(valuePropertyType) : null, - MarshallingFeatures: ~CustomMarshallingFeatures.ManagedTypePinning, + NativeValueType: valuePropertyType is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(valuePropertyType) : null, + Direction: CustomTypeMarshallerDirection.Ref, + MarshallingFeatures: CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer, + PinningFeatures: CustomTypeMarshallerPinning.NativeType, UseDefaultMarshalling: true, + customTypeMarshallerData.Value.BufferSize, ElementCountInfo: parsedCountInfo, ElementType: ManagedTypeInfo.CreateTypeInfoForTypeSymbol(elementType), - ElementMarshallingInfo: GetMarshallingInfo(elementType, useSiteAttributes, indirectionLevel + 1, inspectedElements, ref maxIndirectionLevelUsed)); + ElementMarshallingInfo: GetMarshallingInfo(elementType, useSiteAttributes, indirectionLevel + 1, inspectedElements, ref maxIndirectionDepthUsed)); return true; } @@ -793,7 +764,7 @@ private bool TryCreateTypeBasedMarshallingInfo( AttributeData attrData = _contextSymbol is IMethodSymbol ? _contextSymbol.GetAttributes().First(a => a.AttributeClass.ToDisplayString() == TypeNames.LibraryImportAttribute) : default; - marshallingInfo = CreateNativeMarshallingInfo(type, _defaultInfo.StringMarshallingCustomType, attrData, true, indirectionLevel, parsedCountInfo, useSiteAttributes, inspectedElements, ref maxIndirectionLevelUsed); + marshallingInfo = CreateNativeMarshallingInfo(type, _defaultInfo.StringMarshallingCustomType, attrData, true, indirectionLevel, parsedCountInfo, useSiteAttributes, inspectedElements, ref maxIndirectionDepthUsed); return true; } @@ -862,7 +833,7 @@ private bool TryGetAttributeIndirectionLevel(AttributeData attrData, out int ind foreach (KeyValuePair arg in attrData.NamedArguments) { - if (arg.Key == ManualTypeMarshallingHelper.MarshalUsingProperties.ElementIndirectionLevel) + if (arg.Key == ManualTypeMarshallingHelper.MarshalUsingProperties.ElementIndirectionDepth) { indirectionLevel = (int)arg.Value.Value!; return true; diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj index a542f34e72174..8c9dbccb9f1d8 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj @@ -5,6 +5,7 @@ enable Microsoft.Interop true + $(DefineConstants);LIBRARYIMPORT_GENERATOR_TEST @@ -12,6 +13,12 @@ DependentUpon="Resources.resx" DesignTime="True" AutoGen="True" /> + + + diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Resources.Designer.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Resources.Designer.cs index 90e95ecef5c6d..626e683b776e9 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Resources.Designer.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Resources.Designer.cs @@ -124,7 +124,7 @@ internal static string CollectionSizeParamTypeMustBeIntegral { } /// - /// Looks up a localized string similar to Only one of 'ConstantElementCount' or 'ElementCountInfo' may be used in a 'MarshalUsingAttribute' for a given 'ElementIndirectionLevel'. + /// Looks up a localized string similar to Only one of 'ConstantElementCount' or 'ElementCountInfo' may be used in a 'MarshalUsingAttribute' for a given 'ElementIndirectionDepth'. /// internal static string ConstantAndElementCountInfoDisallowed { get { @@ -178,7 +178,7 @@ internal static string DuplicateMarshallingInfo { } /// - /// Looks up a localized string similar to Marshalling info was specified for 'ElementIndirectionLevel' {0}, but marshalling info was only needed for {1} level(s) of indirection. + /// Looks up a localized string similar to Marshalling info was specified for 'ElementIndirectionDepth' {0}, but marshalling info was only needed for {1} level(s) of indirection. /// internal static string ExtraneousMarshallingInfo { get { @@ -223,7 +223,7 @@ internal static string InOutAttributeMarshalerNotSupported { } /// - /// Marshalling bool without explicit marshalling information is not supported. Specify either 'MarshalUsingAttribute' or 'MarshalAsAttribute'.. + /// Looks up a localized string similar to Marshalling bool without explicit marshalling information is not supported. Specify either 'MarshalUsingAttribute' or 'MarshalAsAttribute'.. /// internal static string MarshallingBoolAsUndefinedNotSupported { get { @@ -297,18 +297,18 @@ internal static string OutByValueNotSupportedMessage { /// /// Looks up a localized string similar to The 'Value' property must not be a 'ref' or 'readonly ref' property.. /// - internal static string RefValuePropertyUnsupportedDescription { + internal static string RefNativeValueUnsupportedDescription { get { - return ResourceManager.GetString("RefValuePropertyUnsupportedDescription", resourceCulture); + return ResourceManager.GetString("RefNativeValueUnsupportedDescription", resourceCulture); } } /// /// Looks up a localized string similar to The 'Value' property on the native type '{0}' must not be a 'ref' or 'readonly ref' property.. /// - internal static string RefValuePropertyUnsupportedMessage { + internal static string RefNativeValueUnsupportedMessage { get { - return ResourceManager.GetString("RefValuePropertyUnsupportedMessage", resourceCulture); + return ResourceManager.GetString("RefNativeValueUnsupportedMessage", resourceCulture); } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Resources.resx b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Resources.resx index abac39ff66078..2097f8a82ec0b 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Resources.resx +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Resources.resx @@ -165,33 +165,12 @@ The '[Out]' attribute is not supported on the '{0}' parameter. - - The 'Value' property must not be a 'ref' or 'readonly ref' property. - - - The 'Value' property on the native type '{0}' must not be a 'ref' or 'readonly ref' property. - An abstract type derived from 'SafeHandle' cannot be marshalled by reference. The provided type must be concrete. Specified type is not supported by source-generated P/Invokes - - Marshalling a value between managed and native with a native type with a 'Value' property requires extra state, which is not supported in this context. - - - The native type's 'Value' property must have a getter to support marshalling from managed to native. - - - The 'Value' property on the native type '{0}' must have a getter - - - The native type's 'Value' property must have a setter to support marshalling from native to managed. - - - The 'Value' property on the native type '{0}' must have a setter - This element cannot depend on '{0}' for collection size information without creating a dependency cycle @@ -202,17 +181,11 @@ Multiple marshalling attributes per element per indirection level is unsupported, but duplicate information was provided for indirection level {0} - Marshalling info was specified for 'ElementIndirectionLevel' {0}, but marshalling info was only needed for {1} level(s) of indirection + Marshalling info was specified for 'ElementIndirectionDepth' {0}, but marshalling info was only needed for {1} level(s) of indirection The native type '{0}' for managed type '{1}' must be a closed generic type or have the same arity as the managed type. - - The native type must have at least one of the two marshalling methods to enable marshalling the managed type. - - - The native type '{0}' must be a value type and have a constructor that takes one parameter of type '{1}' or a parameterless instance method named 'ToManaged' that returns '{1}' - A native type with the 'GenericContiguousCollectionMarshallerAttribute' must have at least one of the two marshalling methods as well as a 'ManagedValues' property of type 'Span<T>' for some 'T' and a 'NativeValueStorage' property of type 'Span<byte>' to enable marshalling the managed type. @@ -220,7 +193,7 @@ The native type '{0}' must be a value type and have a constructor that takes two parameters, one of type '{1}' and an 'int', or have a parameterless instance method named 'ToManaged' that returns '{1}' as well as a 'ManagedValues' property of type 'Span<T>' for some 'T' and a 'NativeValueStorage' property of type 'Span<byte>' - Only one of 'ConstantElementCount' or 'ElementCountInfo' may be used in a 'MarshalUsingAttribute' for a given 'ElementIndirectionLevel' + Only one of 'ConstantElementCount' or 'ElementCountInfo' may be used in a 'MarshalUsingAttribute' for a given 'ElementIndirectionDepth' Runtime marshalling must be disabled in this project by applying the 'System.Runtime.InteropServices.DisableRuntimeMarshallingAttribute' to the assembly to enable marshalling this type. diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs index 1a812d49696d3..ba09ebee8677e 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs @@ -15,7 +15,9 @@ public static class TypeNames public const string MarshalUsingAttribute = "System.Runtime.InteropServices.MarshalUsingAttribute"; - public const string GenericContiguousCollectionMarshallerAttribute = "System.Runtime.InteropServices.GenericContiguousCollectionMarshallerAttribute"; + public const string CustomTypeMarshallerAttribute = "System.Runtime.InteropServices.CustomTypeMarshallerAttribute"; + + public const string CustomTypeMarshallerAttributeGenericPlaceholder = "System.Runtime.InteropServices.CustomTypeMarshallerAttribute.GenericPlaceholder"; public const string LCIDConversionAttribute = "System.Runtime.InteropServices.LCIDConversionAttribute"; @@ -24,6 +26,8 @@ public static class TypeNames public const string UnmanagedCallConvAttribute = "System.Runtime.InteropServices.UnmanagedCallConvAttribute"; public const string System_Span_Metadata = "System.Span`1"; public const string System_Span = "System.Span"; + public const string System_ReadOnlySpan_Metadata = "System.ReadOnlySpan`1"; + public const string System_ReadOnlySpan = "System.ReadOnlySpan"; public const string System_IntPtr = "System.IntPtr"; diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeSymbolExtensions.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeSymbolExtensions.cs index c03e880f124b6..bb10cf31c122d 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeSymbolExtensions.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeSymbolExtensions.cs @@ -28,16 +28,7 @@ private static bool HasOnlyBlittableFields(this ITypeSymbol type, ImmutableHashS { if (!field.IsStatic) { - bool fieldBlittable = field switch - { - // Assume that type parameters that can be blittable are blittable. - // We'll re-evaluate blittability for generic fields of generic types at instantation time. - { Type: ITypeParameterSymbol { IsReferenceType: false } } => true, - { Type.IsUnmanagedType: false } => false, - _ => IsConsideredBlittable(field.Type, seenTypes.Add(type)) - }; - - if (!fieldBlittable) + if (!IsConsideredBlittable(field.Type, seenTypes.Add(type))) { return false; } @@ -51,6 +42,12 @@ private static bool HasOnlyBlittableFields(this ITypeSymbol type, ImmutableHashS private static bool IsConsideredBlittable(this ITypeSymbol type, ImmutableHashSet seenTypes) { + // Assume that type parameters that can be blittable are blittable. + // We'll re-evaluate blittability for generic fields of generic types at instantation time. + if (type.TypeKind == TypeKind.TypeParameter && !type.IsReferenceType) + { + return true; + } if (!type.IsUnmanagedType || type.IsAutoLayout()) { return false; diff --git a/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/Ancillary.Interop.csproj b/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/Ancillary.Interop.csproj index 710d935b82f00..a1c0dd2757429 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/Ancillary.Interop.csproj +++ b/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/Ancillary.Interop.csproj @@ -12,6 +12,9 @@ + + + diff --git a/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/SpanMarshallers.cs b/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/SpanMarshallers.cs index 46cd7302fbff5..358fc92cf6be1 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/SpanMarshallers.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/SpanMarshallers.cs @@ -6,7 +6,10 @@ namespace System.Runtime.InteropServices.GeneratedMarshalling { - [GenericContiguousCollectionMarshaller] + // Stack-alloc threshold set to 256 bytes to enable small arrays to be passed on the stack. + // Number kept small to ensure that P/Invokes with a lot of array parameters doesn't + // blow the stack since this is a new optimization in the code-generated interop. + [CustomTypeMarshaller(typeof(ReadOnlySpan<>), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 0x200)] public unsafe ref struct ReadOnlySpanMarshaller { private ReadOnlySpan _managedSpan; @@ -47,47 +50,15 @@ public ReadOnlySpanMarshaller(ReadOnlySpan managed, Span stackSpace, in } } - /// - /// Stack-alloc threshold set to 256 bytes to enable small arrays to be passed on the stack. - /// Number kept small to ensure that P/Invokes with a lot of array parameters doesn't - /// blow the stack since this is a new optimization in the code-generated interop. - /// - public const int BufferSize = 0x200; - public const bool RequiresStackBuffer = true; + public ReadOnlySpan GetManagedValuesSource() => _managedSpan; - public Span ManagedValues => MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(_managedSpan), _managedSpan.Length); + public Span GetNativeValuesDestination() => NativeValueStorage; - public Span NativeValueStorage { get; private set; } + private Span NativeValueStorage { get; } public ref byte GetPinnableReference() => ref MemoryMarshal.GetReference(NativeValueStorage); - public void SetUnmarshalledCollectionLength(int length) - { - _managedSpan = new T[length]; - } - - public byte* Value - { - get - { - return (byte*)Unsafe.AsPointer(ref GetPinnableReference()); - } - set - { - if (value == null) - { - _managedSpan = null; - NativeValueStorage = default; - } - else - { - _allocatedMemory = (IntPtr)value; - NativeValueStorage = new Span(value, _managedSpan.Length * _sizeOfNativeElement); - } - } - } - - public ReadOnlySpan ToManaged() => _managedSpan; + public byte* ToNativeValue() => (byte*)Unsafe.AsPointer(ref GetPinnableReference()); public void FreeNative() { @@ -95,68 +66,69 @@ public void FreeNative() } } - [GenericContiguousCollectionMarshaller] + [CustomTypeMarshaller(typeof(Span<>), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 0x200)] public unsafe ref struct SpanMarshaller { - private ReadOnlySpanMarshaller _inner; + private Span _managedSpan; + private readonly int _sizeOfNativeElement; + private IntPtr _allocatedMemory; public SpanMarshaller(int sizeOfNativeElement) : this() { - _inner = new ReadOnlySpanMarshaller(sizeOfNativeElement); + _sizeOfNativeElement = sizeOfNativeElement; } public SpanMarshaller(Span managed, int sizeOfNativeElement) + :this(managed, Span.Empty, sizeOfNativeElement) { - _inner = new ReadOnlySpanMarshaller(managed, sizeOfNativeElement); } public SpanMarshaller(Span managed, Span stackSpace, int sizeOfNativeElement) { - _inner = new ReadOnlySpanMarshaller(managed, stackSpace, sizeOfNativeElement); - } - - /// - /// Stack-alloc threshold set to 256 bytes to enable small arrays to be passed on the stack. - /// Number kept small to ensure that P/Invokes with a lot of array parameters doesn't - /// blow the stack since this is a new optimization in the code-generated interop. - /// - public const int BufferSize = ReadOnlySpanMarshaller.BufferSize; - public const bool RequiresStackBuffer = ReadOnlySpanMarshaller.RequiresStackBuffer; - - public Span ManagedValues => _inner.ManagedValues; - - public Span NativeValueStorage - { - get => _inner.NativeValueStorage; + _allocatedMemory = default; + _sizeOfNativeElement = sizeOfNativeElement; + if (managed.Length == 0) + { + _managedSpan = default; + NativeValueStorage = default; + return; + } + _managedSpan = managed; + int spaceToAllocate = managed.Length * sizeOfNativeElement; + if (spaceToAllocate <= stackSpace.Length) + { + NativeValueStorage = stackSpace[0..spaceToAllocate]; + } + else + { + _allocatedMemory = Marshal.AllocCoTaskMem(spaceToAllocate); + NativeValueStorage = new Span((void*)_allocatedMemory, spaceToAllocate); + } } - public ref byte GetPinnableReference() => ref _inner.GetPinnableReference(); - - public void SetUnmarshalledCollectionLength(int length) - { - _inner.SetUnmarshalledCollectionLength(length); - } + private Span NativeValueStorage { get; set; } - public byte* Value - { - get => _inner.Value; - set => _inner.Value = value; - } + public ReadOnlySpan GetManagedValuesSource() => _managedSpan; + public Span GetManagedValuesDestination(int length) => _managedSpan = new T[length]; + public Span GetNativeValuesDestination() => NativeValueStorage; + public ReadOnlySpan GetNativeValuesSource(int length) => NativeValueStorage = new Span((void*)_allocatedMemory, length * _sizeOfNativeElement); + public ref byte GetPinnableReference() => ref NativeValueStorage.GetPinnableReference(); + public byte* ToNativeValue() => (byte*)Unsafe.AsPointer(ref GetPinnableReference()); + public void FromNativeValue(byte* value) => _allocatedMemory = (IntPtr)value; public Span ToManaged() { - ReadOnlySpan managedInner = _inner.ToManaged(); - return MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(managedInner), managedInner.Length); + return _managedSpan; } public void FreeNative() { - _inner.FreeNative(); + Marshal.FreeCoTaskMem(_allocatedMemory); } } - [GenericContiguousCollectionMarshaller] + [CustomTypeMarshaller(typeof(Span<>), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 0x200)] public unsafe ref struct NeverNullSpanMarshaller { private SpanMarshaller _inner; @@ -177,47 +149,14 @@ public NeverNullSpanMarshaller(Span managed, Span stackSpace, int sizeO _inner = new SpanMarshaller(managed, stackSpace, sizeOfNativeElement); } - /// - /// Stack-alloc threshold set to 256 bytes to enable small spans to be passed on the stack. - /// Number kept small to ensure that P/Invokes with a lot of span parameters doesn't - /// blow the stack. - /// - public const int BufferSize = SpanMarshaller.BufferSize; - - public Span ManagedValues => _inner.ManagedValues; - - public Span NativeValueStorage - { - get => _inner.NativeValueStorage; - } - - public ref byte GetPinnableReference() - { - if (_inner.ManagedValues.Length == 0) - { - return ref *(byte*)0xa5a5a5a5; - } - return ref _inner.GetPinnableReference(); - } - - public void SetUnmarshalledCollectionLength(int length) - { - _inner.SetUnmarshalledCollectionLength(length); - } - public byte* Value - { - get - { - if (_inner.ManagedValues.Length == 0) - { - return (byte*)0xa5a5a5a5; - } - return _inner.Value; - } - - set => _inner.Value = value; - } + public ReadOnlySpan GetManagedValuesSource() => _inner.GetManagedValuesSource(); + public Span GetManagedValuesDestination(int length) => _inner.GetManagedValuesDestination(length); + public Span GetNativeValuesDestination() => _inner.GetNativeValuesDestination(); + public ReadOnlySpan GetNativeValuesSource(int length) => _inner.GetNativeValuesSource(length); + public ref byte GetPinnableReference() => ref _inner.GetPinnableReference(); + public byte* ToNativeValue() => _inner.GetManagedValuesSource().Length == 0 ? (byte*)0xa5a5a5a5 : (byte*)Unsafe.AsPointer(ref GetPinnableReference()); + public void FromNativeValue(byte* value) => _inner.FromNativeValue(value); public Span ToManaged() => _inner.ToManaged(); @@ -227,7 +166,7 @@ public void FreeNative() } } - [GenericContiguousCollectionMarshaller] + [CustomTypeMarshaller(typeof(ReadOnlySpan<>), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 0x200)] public unsafe ref struct NeverNullReadOnlySpanMarshaller { private ReadOnlySpanMarshaller _inner; @@ -248,50 +187,10 @@ public NeverNullReadOnlySpanMarshaller(ReadOnlySpan managed, Span stack _inner = new ReadOnlySpanMarshaller(managed, stackSpace, sizeOfNativeElement); } - /// - /// Stack-alloc threshold set to 256 bytes to enable small spans to be passed on the stack. - /// Number kept small to ensure that P/Invokes with a lot of span parameters doesn't - /// blow the stack. - /// - public const int BufferSize = SpanMarshaller.BufferSize; - public const bool RequiresStackBuffer = SpanMarshaller.RequiresStackBuffer; - - public Span ManagedValues => _inner.ManagedValues; - - public Span NativeValueStorage - { - get => _inner.NativeValueStorage; - } - - public ref byte GetPinnableReference() - { - if (_inner.ManagedValues.Length == 0) - { - return ref *(byte*)0xa5a5a5a5; - } - return ref _inner.GetPinnableReference(); - } - - public void SetUnmarshalledCollectionLength(int length) - { - _inner.SetUnmarshalledCollectionLength(length); - } - - public byte* Value - { - get - { - if (_inner.ManagedValues.Length == 0) - { - return (byte*)0xa5a5a5a5; - } - return _inner.Value; - } - - set => _inner.Value = value; - } - - public ReadOnlySpan ToManaged() => _inner.ToManaged(); + public ReadOnlySpan GetManagedValuesSource() => _inner.GetManagedValuesSource(); + public Span GetNativeValuesDestination() => _inner.GetNativeValuesDestination(); + public ref byte GetPinnableReference() => ref _inner.GetPinnableReference(); + public byte* ToNativeValue() => _inner.GetManagedValuesSource().Length == 0 ? (byte*)0xa5a5a5a5 : (byte*)Unsafe.AsPointer(ref GetPinnableReference()); public void FreeNative() { @@ -299,22 +198,18 @@ public void FreeNative() } } - [GenericContiguousCollectionMarshaller] + // Stack-alloc threshold set to 0 so that the generator can use the constructor that takes a stackSpace to let the marshaller know that the original data span can be used and safely pinned. + [CustomTypeMarshaller(typeof(Span<>), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 0)] public unsafe ref struct DirectSpanMarshaller where T : unmanaged { - private int _unmarshalledLength; private T* _allocatedMemory; + private T* _nativeValue; private Span _data; public DirectSpanMarshaller(int sizeOfNativeElement) :this() { - // This check is not exhaustive, but it will catch the majority of cases. - if (typeof(T) == typeof(bool) || typeof(T) == typeof(char) || Unsafe.SizeOf() != sizeOfNativeElement) - { - throw new ArgumentException("This marshaller only supports blittable element types. The provided type parameter must be blittable", nameof(T)); - } } public DirectSpanMarshaller(Span managed, int sizeOfNativeElement) @@ -337,42 +232,29 @@ public DirectSpanMarshaller(Span managed, Span stackSpace, int sizeOfNa _data = managed; } - /// - /// Stack-alloc threshold set to 0 so that the generator can use the constructor that takes a stackSpace to let the marshaller know that the original data span can be used and safely pinned. - /// - public const int BufferSize = 0; - - public Span ManagedValues => _data; - - public Span NativeValueStorage => _allocatedMemory != null + public ReadOnlySpan GetManagedValuesSource() => _data; + public Span GetManagedValuesDestination(int length) => _data = new Span(_nativeValue, length); + public Span GetNativeValuesDestination() => _allocatedMemory != null ? new Span(_allocatedMemory, _data.Length * Unsafe.SizeOf()) : MemoryMarshal.Cast(_data); + public ReadOnlySpan GetNativeValuesSource(int length) => new ReadOnlySpan(_nativeValue, length * sizeof(T)); + public ref T GetPinnableReference() => ref _data.GetPinnableReference(); - public void SetUnmarshalledCollectionLength(int length) + public T* ToNativeValue() { - _unmarshalledLength = length; + if (_allocatedMemory != null) + { + return _allocatedMemory; + } + return (T*)Unsafe.AsPointer(ref GetPinnableReference()); } - public T* Value + public void FromNativeValue(T* value) { - get - { - if (_allocatedMemory != null) - { - return _allocatedMemory; - } - return (T*)Unsafe.AsPointer(ref GetPinnableReference()); - } - set - { - // We don't save the pointer assigned here to be freed - // since this marshaller passes back the actual memory span from native code - // back to managed code. - _allocatedMemory = null; - _data = new Span(value, _unmarshalledLength); - } + _allocatedMemory = null; + _nativeValue = value; } public Span ToManaged() diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/ArrayTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/ArrayTests.cs index 8fd114021a4cb..6075ce224eeb8 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/ArrayTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/ArrayTests.cs @@ -77,7 +77,7 @@ public partial class Arrays [LibraryImport(NativeExportsNE_Binary, EntryPoint = "transpose_matrix")] [return: MarshalUsing(CountElementName = "numColumns")] - [return: MarshalUsing(CountElementName = "numRows", ElementIndirectionLevel = 1)] + [return: MarshalUsing(CountElementName = "numRows", ElementIndirectionDepth = 1)] public static partial int[][] TransposeMatrix(int[][] matrix, int[] numRows, int numColumns); } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs index f8676d68e26d2..f77258710ca37 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs @@ -36,20 +36,20 @@ public partial class Collections public static partial void CreateRange_Out(int start, int end, out int numValues, [MarshalUsing(typeof(ListMarshaller), CountElementName = "numValues")] out List res); [LibraryImport(NativeExportsNE_Binary, EntryPoint = "sum_string_lengths")] - public static partial int SumStringLengths([MarshalUsing(typeof(ListMarshaller)), MarshalUsing(typeof(Utf16StringMarshaller), ElementIndirectionLevel = 1)] List strArray); + public static partial int SumStringLengths([MarshalUsing(typeof(ListMarshaller)), MarshalUsing(typeof(Utf16StringMarshaller), ElementIndirectionDepth = 1)] List strArray); [LibraryImport(NativeExportsNE_Binary, EntryPoint = "reverse_strings_replace")] - public static partial void ReverseStrings_Ref([MarshalUsing(typeof(ListMarshaller), CountElementName = "numElements"), MarshalUsing(typeof(Utf16StringMarshaller), ElementIndirectionLevel = 1)] ref List strArray, out int numElements); + public static partial void ReverseStrings_Ref([MarshalUsing(typeof(ListMarshaller), CountElementName = "numElements"), MarshalUsing(typeof(Utf16StringMarshaller), ElementIndirectionDepth = 1)] ref List strArray, out int numElements); [LibraryImport(NativeExportsNE_Binary, EntryPoint = "reverse_strings_return")] - [return: MarshalUsing(typeof(ListMarshaller), CountElementName = "numElements"), MarshalUsing(typeof(Utf16StringMarshaller), ElementIndirectionLevel = 1)] - public static partial List ReverseStrings_Return([MarshalUsing(typeof(ListMarshaller), CountElementName = "numElements"), MarshalUsing(typeof(Utf16StringMarshaller), ElementIndirectionLevel = 1)] List strArray, out int numElements); + [return: MarshalUsing(typeof(ListMarshaller), CountElementName = "numElements"), MarshalUsing(typeof(Utf16StringMarshaller), ElementIndirectionDepth = 1)] + public static partial List ReverseStrings_Return([MarshalUsing(typeof(ListMarshaller), CountElementName = "numElements"), MarshalUsing(typeof(Utf16StringMarshaller), ElementIndirectionDepth = 1)] List strArray, out int numElements); [LibraryImport(NativeExportsNE_Binary, EntryPoint = "reverse_strings_out")] public static partial void ReverseStrings_Out( - [MarshalUsing(typeof(ListMarshaller)), MarshalUsing(typeof(Utf16StringMarshaller), ElementIndirectionLevel = 1)] List strArray, + [MarshalUsing(typeof(ListMarshaller)), MarshalUsing(typeof(Utf16StringMarshaller), ElementIndirectionDepth = 1)] List strArray, out int numElements, - [MarshalUsing(typeof(ListMarshaller), CountElementName = "numElements"), MarshalUsing(typeof(Utf16StringMarshaller), ElementIndirectionLevel = 1)] out List res); + [MarshalUsing(typeof(ListMarshaller), CountElementName = "numElements"), MarshalUsing(typeof(Utf16StringMarshaller), ElementIndirectionDepth = 1)] out List res); [LibraryImport(NativeExportsNE_Binary, EntryPoint = "get_long_bytes")] [return:MarshalUsing(typeof(ListMarshaller), ConstantElementCount = sizeof(long))] diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/SetLastErrorTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/SetLastErrorTests.cs index 057bef953c7f5..18a3ac4fc1a6a 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/SetLastErrorTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/SetLastErrorTests.cs @@ -8,6 +8,7 @@ namespace LibraryImportGenerator.IntegrationTests { + [CustomTypeMarshaller(typeof(int))] public struct SetLastErrorMarshaller { public int val; diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AdditionalAttributesOnStub.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AdditionalAttributesOnStub.cs index e9a7f5c47b41f..fa879e7d134a2 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AdditionalAttributesOnStub.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AdditionalAttributesOnStub.cs @@ -31,6 +31,7 @@ struct S { } +[CustomTypeMarshaller(typeof(S))] struct Native { public Native(S s) { } @@ -82,6 +83,7 @@ struct S { } +[CustomTypeMarshaller(typeof(S))] struct Native { public Native(S s) { } @@ -172,6 +174,7 @@ struct S { } +[CustomTypeMarshaller(typeof(S))] struct Native { public Native(S s) { } @@ -204,6 +207,7 @@ struct S { } +[CustomTypeMarshaller(typeof(S))] struct Native { public Native(S s) { } @@ -236,6 +240,7 @@ struct S { } +[CustomTypeMarshaller(typeof(S))] struct Native { public Native(S s) { } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AttributeForwarding.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AttributeForwarding.cs index f876a914e0f0c..7edda25746c6c 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AttributeForwarding.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AttributeForwarding.cs @@ -38,6 +38,7 @@ struct S {{ }} +[CustomTypeMarshaller(typeof(S))] struct Native {{ public Native(S s) {{ }} @@ -79,6 +80,7 @@ struct S { } +[CustomTypeMarshaller(typeof(S))] struct Native { public Native(S s) { } @@ -122,6 +124,7 @@ struct S { } +[CustomTypeMarshaller(typeof(S))] struct Native { public Native(S s) { } @@ -169,6 +172,7 @@ struct S { } +[CustomTypeMarshaller(typeof(S))] struct Native { public Native(S s) { } @@ -220,6 +224,7 @@ struct S {{ }} +[CustomTypeMarshaller(typeof(S))] struct Native {{ public Native(S s) {{ }} @@ -268,6 +273,7 @@ struct S { } +[CustomTypeMarshaller(typeof(S))] struct Native { public Native(S s) { } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs index 93b822d6f7b3d..3312098bfe535 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs @@ -381,6 +381,7 @@ public static string CustomStringMarshallingParametersAndModifiers() { string typeName = typeof(T).ToString(); return BasicParametersAndModifiersWithStringMarshallingCustomType(typeName, "Native", DisableRuntimeMarshalling) + @$" +[CustomTypeMarshaller(typeof({typeName}))] struct Native {{ public Native({typeName} s) {{ }} @@ -636,6 +637,7 @@ struct S public bool b; } +[CustomTypeMarshaller(typeof(S))] struct Native { private int i; @@ -655,6 +657,7 @@ struct S public bool b; } +[CustomTypeMarshaller(typeof(S))] struct Native { private int i; @@ -674,6 +677,7 @@ struct S public bool b; } +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer, BufferSize = 1)] struct Native { private int i; @@ -683,8 +687,6 @@ public Native(S s, System.Span b) } public S ToManaged() => new S { b = i != 0 }; - - public const int BufferSize = 1; } "; public static string CustomStructMarshallingStackallocOnlyRefParameter = BasicParameterWithByRefModifier("ref", "S", DisableRuntimeMarshalling) + @" @@ -694,6 +696,7 @@ struct S public bool b; } +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.Out, Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer, BufferSize = 1)] struct Native { private int i; @@ -703,9 +706,6 @@ public Native(S s, System.Span b) } public S ToManaged() => new S { b = i != 0 }; - - public const int BufferSize = 1; - public const bool RequiresStackBuffer = false; } "; public static string CustomStructMarshallingOptionalStackallocParametersAndModifiers = BasicParametersAndModifiers("S", DisableRuntimeMarshalling) + @" @@ -715,6 +715,7 @@ struct S public bool b; } +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer, BufferSize = 1)] struct Native { private int i; @@ -728,9 +729,6 @@ public Native(S s) } public S ToManaged() => new S { b = i != 0 }; - - public const int BufferSize = 1; - public const bool RequiresStackBuffer = true; } "; @@ -741,18 +739,17 @@ struct S public bool b; } +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 1)] struct Native { public Native(S s, System.Span b) { - Value = s.b ? 1 : 0; } - public S ToManaged() => new S { b = Value != 0 }; - - public int Value { get; set; } + public S ToManaged() => new S { b = true }; - public const int BufferSize = 1; + public int ToNativeValue() => throw null; + public void FromNativeValue(int value) => throw null; } "; public static string CustomStructMarshallingValuePropertyParametersAndModifiers = BasicParametersAndModifiers("S", DisableRuntimeMarshalling) + @" @@ -762,16 +759,17 @@ struct S public bool b; } +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] struct Native { public Native(S s) { - Value = s.b ? 1 : 0; } - public S ToManaged() => new S { b = Value != 0 }; + public S ToManaged() => new S { b = true }; - public int Value { get; set; } + public int ToNativeValue() => throw null; + public void FromNativeValue(int value) => throw null; } "; public static string CustomStructMarshallingPinnableParametersAndModifiers = BasicParametersAndModifiers("S", DisableRuntimeMarshalling) + @" @@ -783,6 +781,7 @@ class S public ref int GetPinnableReference() => ref i; } +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] unsafe struct Native { private int* ptr; @@ -794,15 +793,14 @@ public Native(S s) public S ToManaged() => new S { i = *ptr }; - public nint Value - { - get => (nint)ptr; - set => ptr = (int*)value; - } + public nint ToNativeValue() => (nint)ptr; + + public void FromNativeValue(nint value) => ptr = (int*)value; } "; public static string CustomStructMarshallingNativeTypePinnable = @" +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System; @@ -814,6 +812,7 @@ class S public byte c; } +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 1)] unsafe ref struct Native { private byte* ptr; @@ -823,6 +822,7 @@ public Native(S s) : this() { ptr = (byte*)Marshal.AllocCoTaskMem(sizeof(byte)); *ptr = s.c; + stackBuffer = new Span(ptr, 1); } public Native(S s, Span buffer) : this() @@ -831,18 +831,16 @@ public Native(S s, Span buffer) : this() stackBuffer[0] = s.c; } - public ref byte GetPinnableReference() => ref (ptr != null ? ref *ptr : ref stackBuffer.GetPinnableReference()); + public ref byte GetPinnableReference() => ref stackBuffer.GetPinnableReference(); public S ToManaged() { return new S { c = *ptr }; } - public byte* Value - { - get => ptr != null ? ptr : throw new InvalidOperationException(); - set => ptr = value; - } + public byte* ToNativeValue() => (byte*)Unsafe.AsPointer(ref GetPinnableReference()); + + public void FromNativeValue(byte* value) => ptr = value; public void FreeNative() { @@ -851,8 +849,6 @@ public void FreeNative() Marshal.FreeCoTaskMem((IntPtr)ptr); } } - - public const int BufferSize = 1; } partial class Test @@ -871,6 +867,7 @@ class S public byte c = 0; } +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] unsafe struct Native { private S value; @@ -880,7 +877,7 @@ public Native(S s) : this() value = s; } - public ref byte Value { get => ref value.c; } + public ref byte ToNativeValue() => ref value.c; } "; @@ -921,6 +918,7 @@ struct S public bool b; } +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.In)] struct Native { private int i; @@ -939,6 +937,7 @@ struct S public bool b; } +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.In)] struct Native { private int i; @@ -957,6 +956,7 @@ struct S } [StructLayout(LayoutKind.Sequential)] +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.Out)] struct Native { private int i; @@ -971,16 +971,17 @@ public struct IntStructWrapper public int Value; } +[CustomTypeMarshaller(typeof(IntStructWrapper), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] public struct IntStructWrapperNative { public IntStructWrapperNative(IntStructWrapper managed) { - Value = managed.Value; } - public int Value { get; set; } + public int ToNativeValue() => throw null; + public void FromNativeValue(int value) => throw null; - public IntStructWrapper ToManaged() => new IntStructWrapper { Value = Value }; + public IntStructWrapper ToManaged() => new IntStructWrapper { Value = 1 }; } "; @@ -991,6 +992,7 @@ public struct IntStructWrapper public int Value; } +[CustomTypeMarshaller(typeof(IntStructWrapper))] public struct IntStructWrapperNative { private int value; @@ -1116,13 +1118,18 @@ public static string CollectionByValue(string elementType) => BasicParameterByVa [NativeMarshalling(typeof(Marshaller<>))] class TestCollection {} -[GenericContiguousCollectionMarshaller] +[CustomTypeMarshaller(typeof(TestCollection<>), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] ref struct Marshaller { + public Marshaller(int nativeElementSize) : this() {} public Marshaller(TestCollection managed, int nativeElementSize) : this() {} - public System.Span ManagedValues { get; } - public System.Span NativeValueStorage { get; } - public System.IntPtr Value { get; } + public System.ReadOnlySpan GetManagedValuesSource() => throw null; + public System.Span GetManagedValuesDestination(int length) => throw null; + public System.ReadOnlySpan GetNativeValuesSource(int length) => throw null; + public System.Span GetNativeValuesDestination() => throw null; + public System.IntPtr ToNativeValue() => throw null; + public void FromNativeValue(System.IntPtr value) => throw null; + public TestCollection ToManaged() => throw null; } "; @@ -1150,15 +1157,17 @@ public static string CustomCollectionWithMarshaller(bool enableDefaultMarshallin string nativeMarshallingAttribute = enableDefaultMarshalling ? "[NativeMarshalling(typeof(Marshaller<>))]" : string.Empty; return nativeMarshallingAttribute + @"class TestCollection {} -[GenericContiguousCollectionMarshaller] +[CustomTypeMarshaller(typeof(TestCollection<>), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] ref struct Marshaller { public Marshaller(int nativeElementSize) : this() {} public Marshaller(TestCollection managed, int nativeElementSize) : this() {} - public System.Span ManagedValues { get; } - public System.Span NativeValueStorage { get; } - public System.IntPtr Value { get; set; } - public void SetUnmarshalledCollectionLength(int length) {} + public System.ReadOnlySpan GetManagedValuesSource() => throw null; + public System.Span GetManagedValuesDestination(int length) => throw null; + public System.ReadOnlySpan GetNativeValuesSource(int length) => throw null; + public System.Span GetNativeValuesDestination() => throw null; + public System.IntPtr ToNativeValue() => throw null; + public void FromNativeValue(System.IntPtr value) => throw null; public TestCollection ToManaged() => throw null; }"; } @@ -1247,13 +1256,18 @@ public static partial void Method( [NativeMarshalling(typeof(Marshaller<,>))] class TestCollection {} -[GenericContiguousCollectionMarshaller] +[CustomTypeMarshaller(typeof(TestCollection<>), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] ref struct Marshaller { public Marshaller(TestCollection managed, int nativeElementSize) : this() {} - public System.Span ManagedValues { get; } - public System.Span NativeValueStorage { get; } - public System.IntPtr Value { get; } + + public System.ReadOnlySpan GetManagedValuesSource() => throw null; + public System.Span GetManagedValuesDestination(int length) => throw null; + public System.ReadOnlySpan GetNativeValuesSource(int length) => throw null; + public System.Span GetNativeValuesDestination() => throw null; + public System.IntPtr ToNativeValue() => throw null; + public void FromNativeValue(System.IntPtr value) => throw null; + public TestCollection ToManaged() => throw null; }"; @@ -1264,13 +1278,13 @@ partial class Test { [LibraryImport(""DoesNotExist"")] [return:MarshalUsing(ConstantElementCount=10)] - [return:MarshalUsing(typeof(IntWrapper), ElementIndirectionLevel = 1)] + [return:MarshalUsing(typeof(IntWrapper), ElementIndirectionDepth = 1)] public static partial TestCollection Method( - [MarshalUsing(typeof(IntWrapper), ElementIndirectionLevel = 1)] TestCollection p, - [MarshalUsing(typeof(IntWrapper), ElementIndirectionLevel = 1)] in TestCollection pIn, + [MarshalUsing(typeof(IntWrapper), ElementIndirectionDepth = 1)] TestCollection p, + [MarshalUsing(typeof(IntWrapper), ElementIndirectionDepth = 1)] in TestCollection pIn, int pRefSize, - [MarshalUsing(CountElementName = ""pRefSize""), MarshalUsing(typeof(IntWrapper), ElementIndirectionLevel = 1)] ref TestCollection pRef, - [MarshalUsing(CountElementName = ""pOutSize"")][MarshalUsing(typeof(IntWrapper), ElementIndirectionLevel = 1)] out TestCollection pOut, + [MarshalUsing(CountElementName = ""pRefSize""), MarshalUsing(typeof(IntWrapper), ElementIndirectionDepth = 1)] ref TestCollection pRef, + [MarshalUsing(CountElementName = ""pOutSize"")][MarshalUsing(typeof(IntWrapper), ElementIndirectionDepth = 1)] out TestCollection pOut, out int pOutSize ); } @@ -1283,14 +1297,14 @@ public IntWrapper(int i){} " + CustomCollectionWithMarshaller(enableDefaultMarshalling: true); - public static string GenericCollectionWithCustomElementMarshallingDuplicateElementIndirectionLevel => @" + public static string GenericCollectionWithCustomElementMarshallingDuplicateElementIndirectionDepth => @" using System.Runtime.InteropServices; [assembly:System.Runtime.CompilerServices.DisableRuntimeMarshalling] partial class Test { [LibraryImport(""DoesNotExist"")] public static partial void Method( - [MarshalUsing(typeof(IntWrapper), ElementIndirectionLevel = 1)] [MarshalUsing(typeof(IntWrapper), ElementIndirectionLevel = 1)] TestCollection p); + [MarshalUsing(typeof(IntWrapper), ElementIndirectionDepth = 1)] [MarshalUsing(typeof(IntWrapper), ElementIndirectionDepth = 1)] TestCollection p); } struct IntWrapper @@ -1301,14 +1315,14 @@ public IntWrapper(int i){} " + CustomCollectionWithMarshaller(enableDefaultMarshalling: true); - public static string GenericCollectionWithCustomElementMarshallingUnusedElementIndirectionLevel => @" + public static string GenericCollectionWithCustomElementMarshallingUnusedElementIndirectionDepth => @" using System.Runtime.InteropServices; [assembly:System.Runtime.CompilerServices.DisableRuntimeMarshalling] partial class Test { [LibraryImport(""DoesNotExist"")] public static partial void Method( - [MarshalUsing(typeof(IntWrapper), ElementIndirectionLevel = 2)] TestCollection p); + [MarshalUsing(typeof(IntWrapper), ElementIndirectionDepth = 2)] TestCollection p); } struct IntWrapper @@ -1385,72 +1399,72 @@ partial class Test { [LibraryImport(""DoesNotExist"")] public static partial void Method( - [MarshalUsing(CountElementName=""arr0"", ElementIndirectionLevel = 0)] - [MarshalUsing(CountElementName=""arr1"", ElementIndirectionLevel = 1)] - [MarshalUsing(CountElementName=""arr2"", ElementIndirectionLevel = 2)] - [MarshalUsing(CountElementName=""arr3"", ElementIndirectionLevel = 3)] - [MarshalUsing(CountElementName=""arr4"", ElementIndirectionLevel = 4)] - [MarshalUsing(CountElementName=""arr5"", ElementIndirectionLevel = 5)] - [MarshalUsing(CountElementName=""arr6"", ElementIndirectionLevel = 6)] - [MarshalUsing(CountElementName=""arr7"", ElementIndirectionLevel = 7)] - [MarshalUsing(CountElementName=""arr8"", ElementIndirectionLevel = 8)] - [MarshalUsing(CountElementName=""arr9"", ElementIndirectionLevel = 9)] - [MarshalUsing(CountElementName=""arr10"", ElementIndirectionLevel = 10)] ref int[][][][][][][][][][][] arr11, - [MarshalUsing(CountElementName=""arr0"", ElementIndirectionLevel = 0)] - [MarshalUsing(CountElementName=""arr1"", ElementIndirectionLevel = 1)] - [MarshalUsing(CountElementName=""arr2"", ElementIndirectionLevel = 2)] - [MarshalUsing(CountElementName=""arr3"", ElementIndirectionLevel = 3)] - [MarshalUsing(CountElementName=""arr4"", ElementIndirectionLevel = 4)] - [MarshalUsing(CountElementName=""arr5"", ElementIndirectionLevel = 5)] - [MarshalUsing(CountElementName=""arr6"", ElementIndirectionLevel = 6)] - [MarshalUsing(CountElementName=""arr7"", ElementIndirectionLevel = 7)] - [MarshalUsing(CountElementName=""arr8"", ElementIndirectionLevel = 8)] - [MarshalUsing(CountElementName=""arr9"", ElementIndirectionLevel = 9)]ref int[][][][][][][][][][] arr10, - [MarshalUsing(CountElementName=""arr0"", ElementIndirectionLevel = 0)] - [MarshalUsing(CountElementName=""arr1"", ElementIndirectionLevel = 1)] - [MarshalUsing(CountElementName=""arr2"", ElementIndirectionLevel = 2)] - [MarshalUsing(CountElementName=""arr3"", ElementIndirectionLevel = 3)] - [MarshalUsing(CountElementName=""arr4"", ElementIndirectionLevel = 4)] - [MarshalUsing(CountElementName=""arr5"", ElementIndirectionLevel = 5)] - [MarshalUsing(CountElementName=""arr6"", ElementIndirectionLevel = 6)] - [MarshalUsing(CountElementName=""arr7"", ElementIndirectionLevel = 7)] - [MarshalUsing(CountElementName=""arr8"", ElementIndirectionLevel = 8)]ref int[][][][][][][][][] arr9, - [MarshalUsing(CountElementName=""arr0"", ElementIndirectionLevel = 0)] - [MarshalUsing(CountElementName=""arr1"", ElementIndirectionLevel = 1)] - [MarshalUsing(CountElementName=""arr2"", ElementIndirectionLevel = 2)] - [MarshalUsing(CountElementName=""arr3"", ElementIndirectionLevel = 3)] - [MarshalUsing(CountElementName=""arr4"", ElementIndirectionLevel = 4)] - [MarshalUsing(CountElementName=""arr5"", ElementIndirectionLevel = 5)] - [MarshalUsing(CountElementName=""arr6"", ElementIndirectionLevel = 6)] - [MarshalUsing(CountElementName=""arr7"", ElementIndirectionLevel = 7)]ref int[][][][][][][][][] arr8, - [MarshalUsing(CountElementName=""arr0"", ElementIndirectionLevel = 0)] - [MarshalUsing(CountElementName=""arr1"", ElementIndirectionLevel = 1)] - [MarshalUsing(CountElementName=""arr2"", ElementIndirectionLevel = 2)] - [MarshalUsing(CountElementName=""arr3"", ElementIndirectionLevel = 3)] - [MarshalUsing(CountElementName=""arr4"", ElementIndirectionLevel = 4)] - [MarshalUsing(CountElementName=""arr5"", ElementIndirectionLevel = 5)] - [MarshalUsing(CountElementName=""arr6"", ElementIndirectionLevel = 6)]ref int[][][][][][][] arr7, - [MarshalUsing(CountElementName=""arr0"", ElementIndirectionLevel = 0)] - [MarshalUsing(CountElementName=""arr1"", ElementIndirectionLevel = 1)] - [MarshalUsing(CountElementName=""arr2"", ElementIndirectionLevel = 2)] - [MarshalUsing(CountElementName=""arr3"", ElementIndirectionLevel = 3)] - [MarshalUsing(CountElementName=""arr4"", ElementIndirectionLevel = 4)] - [MarshalUsing(CountElementName=""arr5"", ElementIndirectionLevel = 5)]ref int[][][][][][] arr6, - [MarshalUsing(CountElementName=""arr0"", ElementIndirectionLevel = 0)] - [MarshalUsing(CountElementName=""arr1"", ElementIndirectionLevel = 1)] - [MarshalUsing(CountElementName=""arr2"", ElementIndirectionLevel = 2)] - [MarshalUsing(CountElementName=""arr3"", ElementIndirectionLevel = 3)] - [MarshalUsing(CountElementName=""arr4"", ElementIndirectionLevel = 4)]ref int[][][][][] arr5, - [MarshalUsing(CountElementName=""arr0"", ElementIndirectionLevel = 0)] - [MarshalUsing(CountElementName=""arr1"", ElementIndirectionLevel = 1)] - [MarshalUsing(CountElementName=""arr2"", ElementIndirectionLevel = 2)] - [MarshalUsing(CountElementName=""arr3"", ElementIndirectionLevel = 3)]ref int[][][][] arr4, - [MarshalUsing(CountElementName=""arr0"", ElementIndirectionLevel = 0)] - [MarshalUsing(CountElementName=""arr1"", ElementIndirectionLevel = 1)] - [MarshalUsing(CountElementName=""arr2"", ElementIndirectionLevel = 2)]ref int[][][] arr3, - [MarshalUsing(CountElementName=""arr0"", ElementIndirectionLevel = 0)] - [MarshalUsing(CountElementName=""arr1"", ElementIndirectionLevel = 1)]ref int[][] arr2, - [MarshalUsing(CountElementName=""arr0"", ElementIndirectionLevel = 0)]ref int[] arr1, + [MarshalUsing(CountElementName=""arr0"", ElementIndirectionDepth = 0)] + [MarshalUsing(CountElementName=""arr1"", ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName=""arr2"", ElementIndirectionDepth = 2)] + [MarshalUsing(CountElementName=""arr3"", ElementIndirectionDepth = 3)] + [MarshalUsing(CountElementName=""arr4"", ElementIndirectionDepth = 4)] + [MarshalUsing(CountElementName=""arr5"", ElementIndirectionDepth = 5)] + [MarshalUsing(CountElementName=""arr6"", ElementIndirectionDepth = 6)] + [MarshalUsing(CountElementName=""arr7"", ElementIndirectionDepth = 7)] + [MarshalUsing(CountElementName=""arr8"", ElementIndirectionDepth = 8)] + [MarshalUsing(CountElementName=""arr9"", ElementIndirectionDepth = 9)] + [MarshalUsing(CountElementName=""arr10"", ElementIndirectionDepth = 10)] ref int[][][][][][][][][][][] arr11, + [MarshalUsing(CountElementName=""arr0"", ElementIndirectionDepth = 0)] + [MarshalUsing(CountElementName=""arr1"", ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName=""arr2"", ElementIndirectionDepth = 2)] + [MarshalUsing(CountElementName=""arr3"", ElementIndirectionDepth = 3)] + [MarshalUsing(CountElementName=""arr4"", ElementIndirectionDepth = 4)] + [MarshalUsing(CountElementName=""arr5"", ElementIndirectionDepth = 5)] + [MarshalUsing(CountElementName=""arr6"", ElementIndirectionDepth = 6)] + [MarshalUsing(CountElementName=""arr7"", ElementIndirectionDepth = 7)] + [MarshalUsing(CountElementName=""arr8"", ElementIndirectionDepth = 8)] + [MarshalUsing(CountElementName=""arr9"", ElementIndirectionDepth = 9)]ref int[][][][][][][][][][][] arr10, + [MarshalUsing(CountElementName=""arr0"", ElementIndirectionDepth = 0)] + [MarshalUsing(CountElementName=""arr1"", ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName=""arr2"", ElementIndirectionDepth = 2)] + [MarshalUsing(CountElementName=""arr3"", ElementIndirectionDepth = 3)] + [MarshalUsing(CountElementName=""arr4"", ElementIndirectionDepth = 4)] + [MarshalUsing(CountElementName=""arr5"", ElementIndirectionDepth = 5)] + [MarshalUsing(CountElementName=""arr6"", ElementIndirectionDepth = 6)] + [MarshalUsing(CountElementName=""arr7"", ElementIndirectionDepth = 7)] + [MarshalUsing(CountElementName=""arr8"", ElementIndirectionDepth = 8)]ref int[][][][][][][][][][] arr9, + [MarshalUsing(CountElementName=""arr0"", ElementIndirectionDepth = 0)] + [MarshalUsing(CountElementName=""arr1"", ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName=""arr2"", ElementIndirectionDepth = 2)] + [MarshalUsing(CountElementName=""arr3"", ElementIndirectionDepth = 3)] + [MarshalUsing(CountElementName=""arr4"", ElementIndirectionDepth = 4)] + [MarshalUsing(CountElementName=""arr5"", ElementIndirectionDepth = 5)] + [MarshalUsing(CountElementName=""arr6"", ElementIndirectionDepth = 6)] + [MarshalUsing(CountElementName=""arr7"", ElementIndirectionDepth = 7)]ref int[][][][][][][][][] arr8, + [MarshalUsing(CountElementName=""arr0"", ElementIndirectionDepth = 0)] + [MarshalUsing(CountElementName=""arr1"", ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName=""arr2"", ElementIndirectionDepth = 2)] + [MarshalUsing(CountElementName=""arr3"", ElementIndirectionDepth = 3)] + [MarshalUsing(CountElementName=""arr4"", ElementIndirectionDepth = 4)] + [MarshalUsing(CountElementName=""arr5"", ElementIndirectionDepth = 5)] + [MarshalUsing(CountElementName=""arr6"", ElementIndirectionDepth = 6)]ref int[][][][][][][] arr7, + [MarshalUsing(CountElementName=""arr0"", ElementIndirectionDepth = 0)] + [MarshalUsing(CountElementName=""arr1"", ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName=""arr2"", ElementIndirectionDepth = 2)] + [MarshalUsing(CountElementName=""arr3"", ElementIndirectionDepth = 3)] + [MarshalUsing(CountElementName=""arr4"", ElementIndirectionDepth = 4)] + [MarshalUsing(CountElementName=""arr5"", ElementIndirectionDepth = 5)]ref int[][][][][][] arr6, + [MarshalUsing(CountElementName=""arr0"", ElementIndirectionDepth = 0)] + [MarshalUsing(CountElementName=""arr1"", ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName=""arr2"", ElementIndirectionDepth = 2)] + [MarshalUsing(CountElementName=""arr3"", ElementIndirectionDepth = 3)] + [MarshalUsing(CountElementName=""arr4"", ElementIndirectionDepth = 4)]ref int[][][][][] arr5, + [MarshalUsing(CountElementName=""arr0"", ElementIndirectionDepth = 0)] + [MarshalUsing(CountElementName=""arr1"", ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName=""arr2"", ElementIndirectionDepth = 2)] + [MarshalUsing(CountElementName=""arr3"", ElementIndirectionDepth = 3)]ref int[][][][] arr4, + [MarshalUsing(CountElementName=""arr0"", ElementIndirectionDepth = 0)] + [MarshalUsing(CountElementName=""arr1"", ElementIndirectionDepth = 1)] + [MarshalUsing(CountElementName=""arr2"", ElementIndirectionDepth = 2)]ref int[][][] arr3, + [MarshalUsing(CountElementName=""arr0"", ElementIndirectionDepth = 0)] + [MarshalUsing(CountElementName=""arr1"", ElementIndirectionDepth = 1)]ref int[][] arr2, + [MarshalUsing(CountElementName=""arr0"", ElementIndirectionDepth = 0)]ref int[] arr1, ref int arr0 ); } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CompileFails.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CompileFails.cs index 262072e940c7b..3a5403e70c3e4 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CompileFails.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CompileFails.cs @@ -110,8 +110,8 @@ public static IEnumerable CodeSnippetsToCompile() yield return new object[] { CodeSnippets.GenericCollectionMarshallingArityMismatch, 2, 0 }; yield return new object[] { CodeSnippets.MarshalAsAndMarshalUsingOnReturnValue, 2, 0 }; - yield return new object[] { CodeSnippets.GenericCollectionWithCustomElementMarshallingDuplicateElementIndirectionLevel, 2, 0 }; - yield return new object[] { CodeSnippets.GenericCollectionWithCustomElementMarshallingUnusedElementIndirectionLevel, 1, 0 }; + yield return new object[] { CodeSnippets.GenericCollectionWithCustomElementMarshallingDuplicateElementIndirectionDepth, 2, 0 }; + yield return new object[] { CodeSnippets.GenericCollectionWithCustomElementMarshallingUnusedElementIndirectionDepth, 1, 0 }; yield return new object[] { CodeSnippets.RecursiveCountElementNameOnReturnValue, 2, 0 }; yield return new object[] { CodeSnippets.RecursiveCountElementNameOnParameter, 2, 0 }; yield return new object[] { CodeSnippets.MutuallyRecursiveCountElementNameOnParameter, 4, 0 }; diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CustomTypeMarshallerFixerTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CustomTypeMarshallerFixerTests.cs new file mode 100644 index 0000000000000..9707345179c44 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CustomTypeMarshallerFixerTests.cs @@ -0,0 +1,1769 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; +using static Microsoft.Interop.Analyzers.CustomTypeMarshallerAnalyzer; + +using VerifyCS = LibraryImportGenerator.UnitTests.Verifiers.CSharpCodeFixVerifier< + Microsoft.Interop.Analyzers.CustomTypeMarshallerAnalyzer, + Microsoft.Interop.Analyzers.CustomTypeMarshallerFixer>; + +namespace LibraryImportGenerator.UnitTests +{ + [ActiveIssue("https://github.com/dotnet/runtime/issues/60650", TestRuntimes.Mono)] + public class CustomTypeMarshallerFixerTests + { + [Fact] + public async Task NullNativeType_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; + +[{|#0:NativeMarshalling(null)|}] +struct S +{ + public string s; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(NativeTypeMustHaveCustomTypeMarshallerAttributeRule).WithLocation(0).WithArguments("S"), + source); + } + + [Fact] + public async Task NonNamedNativeType_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; + +[{|#0:NativeMarshalling(typeof(int*))|}] +struct S +{ + public string s; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(NativeTypeMustHaveCustomTypeMarshallerAttributeRule).WithLocation(0).WithArguments("S"), + source); + } + + [Fact] + public async Task NonBlittableNativeType_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +struct S +{ + public string s; +} + +[CustomTypeMarshaller(typeof(S))] +struct {|#0:Native|} +{ + private string value; + + public Native(S s) + { + value = s.s; + } + + public S ToManaged() => new S { s = value }; +}"; + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithLocation(0).WithArguments("Native", "S"), + source); + } + + [Fact] + public async Task ClassNativeType_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +struct S +{ + public string s; +} + +[{|CS0592:CustomTypeMarshaller|}(typeof(S))] +class {|#0:Native|} +{ + private IntPtr value; + + public Native(S s) + { + } + + public S ToManaged() => new S(); +}"; + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithLocation(0).WithArguments("Native", "S"), + source); + } + + [Fact] + public async Task BlittableNativeType_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +struct S +{ + public string s; +} + +[CustomTypeMarshaller(typeof(S))] +struct Native +{ + private IntPtr value; + + public Native(S s) + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task BlittableNativeWithNonBlittableToNativeValue_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +struct S +{ + public string s; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +struct Native +{ + private IntPtr value; + + public Native(S s) + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); + + public string {|#0:ToNativeValue|}() => throw null; + public void FromNativeValue(string value) => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithLocation(0).WithArguments("string", "S"), + source); + } + + [Fact] + public async Task NonBlittableNativeTypeWithBlittableToNativeValue_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +struct S +{ + public string s; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +struct Native +{ + private string value; + + public Native(S s) + { + value = s.s; + } + + public S ToManaged() => new S() { s = value }; + + public IntPtr ToNativeValue() => throw null; + public void FromNativeValue(IntPtr value) => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task NonBlittableGetPinnableReferenceReturnType_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public string s; + + public ref string {|#0:GetPinnableReference|}() => ref s; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +unsafe struct Native +{ + private IntPtr value; + + public Native(S s) + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); + + public IntPtr ToNativeValue() => throw null; + public void FromNativeValue(IntPtr value) => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(GetPinnableReferenceReturnTypeBlittableRule).WithLocation(0), + source); + } + + [Fact] + public async Task BlittableGetPinnableReferenceReturnType_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; + + public ref byte GetPinnableReference() => ref c; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +unsafe struct Native +{ + private IntPtr value; + + public Native(S s) : this() + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); + + public IntPtr ToNativeValue() => throw null; + public void FromNativeValue(IntPtr value) => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task NonBlittableMarshallerGetPinnableReferenceReturnType_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public char c; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +unsafe struct Native +{ + private IntPtr value; + + public Native(S s) + { + value = IntPtr.Zero; + } + + public ref char GetPinnableReference() => ref System.Runtime.CompilerServices.Unsafe.NullRef(); + + public S ToManaged() => new S(); + + public IntPtr ToNativeValue() => throw null; + public void FromNativeValue(IntPtr value) => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task BlittableMarshallerGetPinnableReferenceReturnType_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +unsafe struct Native +{ + private IntPtr value; + + public Native(S s) : this() + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); + + public ref byte GetPinnableReference() => ref System.Runtime.CompilerServices.Unsafe.NullRef(); + + public IntPtr ToNativeValue() => throw null; + public void FromNativeValue(IntPtr value) => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task TypeWithGetPinnableReferenceNonPointerReturnType_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; + + public ref byte GetPinnableReference() => ref c; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +unsafe struct Native +{ + private IntPtr value; + + public Native(S s) : this() + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); + + public int {|#0:ToNativeValue|}() => throw null; + public void FromNativeValue(int value) => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBePointerSizedRule).WithLocation(0).WithArguments("int", "S"), + source); + } + + [Fact] + public async Task TypeWithGetPinnableReferencePointerReturnType_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; + + public ref byte GetPinnableReference() => ref c; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +unsafe struct Native +{ + private IntPtr value; + + public Native(S s) : this() + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); + + public int* ToNativeValue() => throw null; + public void FromNativeValue(int* value) => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task TypeWithGetPinnableReferenceByRefValuePropertyType_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; + + public ref byte GetPinnableReference() => ref c; +} + +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +unsafe struct Native +{ + private S value; + + public Native(S s) : this() + { + value = s; + } + public ref byte {|#0:ToNativeValue|}() => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(RefNativeValueUnsupportedRule).WithLocation(0).WithArguments("Native"), + source); + } + + [Fact] + public async Task NativeTypeWithGetPinnableReferenceByRefValuePropertyType_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +unsafe struct Native +{ + private S value; + + public Native(S s) : this() + { + value = s; + } + + public ref byte GetPinnableReference() => ref value.c; + + public ref byte {|#0:ToNativeValue|}() => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(RefNativeValueUnsupportedRule).WithLocation(0).WithArguments("Native"), + source); + } + + [Fact] + public async Task NativeTypeWithGetPinnableReferenceNoValueProperty_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.In)] +unsafe struct Native +{ + private byte value; + + public Native(S s) : this() + { + value = s.c; + } + + public ref byte {|#0:GetPinnableReference|}() => ref System.Runtime.CompilerServices.Unsafe.NullRef(); +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(MarshallerGetPinnableReferenceRequiresTwoStageMarshallingRule).WithLocation(0).WithArguments("Native"), + source); + } + + [Fact] + public async Task NativeTypeWithGetPinnableReferenceWithNonPointerValueProperty_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +unsafe struct Native +{ + private byte value; + + public Native(S s) : this() + { + value = s.c; + } + + public ref byte GetPinnableReference() => ref System.Runtime.CompilerServices.Unsafe.NullRef(); + + public int ToNativeValue() => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task NativeTypeWithNoMarshallingMethods_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.None)] +struct {|#0:Native|} +{ +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(CustomMarshallerTypeMustSupportDirectionRule).WithLocation(0).WithArguments("Native", "S"), + source); + } + + [Fact] + public async Task CollectionNativeTypeWithNoMarshallingMethods_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.None)] +struct {|#0:Native|} +{ +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(CustomMarshallerTypeMustSupportDirectionRule).WithLocation(0).WithArguments("Native", "S"), + source); + } + + [Fact] + public async Task CollectionNativeTypeWithWrongConstructor_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +ref struct {|#0:Native|} +{ + public Native(S s) : this() {} + + public System.ReadOnlySpan GetManagedValuesSource() => throw null; + public System.Span GetNativeValuesDestination() => throw null; + public System.IntPtr ToNativeValue() => throw null; +}"; + string fixedSource = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +ref struct Native +{ + public Native(S s) : this() {} + + public System.ReadOnlySpan GetManagedValuesSource() => throw null; + public System.Span GetNativeValuesDestination() => throw null; + public System.IntPtr ToNativeValue() => throw null; + + public Native(S managed, int nativeElementSize) + { + throw new NotImplementedException(); + } +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(LinearCollectionInRequiresTwoParameterConstructorRule).WithLocation(0).WithArguments("Native", "S"), + fixedSource); + } + + [Fact] + public async Task CollectionNativeTypeWithCorrectConstructor_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +ref struct Native +{ + public Native(S s, int nativeElementSize) : this() {} + + public System.ReadOnlySpan GetManagedValuesSource() => throw null; + public System.Span GetNativeValuesDestination() => throw null; + public System.IntPtr ToNativeValue() => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task CollectionNativeTypeWithIncorrectStackallocConstructor_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 1)] +ref struct {|#0:Native|} +{ + public Native(S s, Span stackSpace) : this() {} + + public System.ReadOnlySpan GetManagedValuesSource() => throw null; + public System.Span GetNativeValuesDestination() => throw null; + public System.IntPtr ToNativeValue() => throw null; +}"; + string fixedSource = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 1)] +ref struct Native +{ + public Native(S s, Span stackSpace) : this() {} + + public System.ReadOnlySpan GetManagedValuesSource() => throw null; + public System.Span GetNativeValuesDestination() => throw null; + public System.IntPtr ToNativeValue() => throw null; + + public Native(S managed, int nativeElementSize) + { + throw new NotImplementedException(); + } + + public Native(S managed, Span buffer, int nativeElementSize) + { + throw new NotImplementedException(); + } +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + fixedSource, + VerifyCS.Diagnostic(LinearCollectionInRequiresTwoParameterConstructorRule).WithLocation(0).WithArguments("Native", "S"), + VerifyCS.Diagnostic(LinearCollectionInCallerAllocatedBufferRequiresSpanConstructorRule).WithLocation(0).WithArguments("Native", "S")); + } + + [Fact] + public async Task CollectionNativeTypeWithOnlyStackallocConstructor_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 1)] +ref struct {|#0:Native|} +{ + public Native(S s, Span stackSpace, int nativeElementSize) : this() {} + + public System.ReadOnlySpan GetManagedValuesSource() => throw null; + public System.Span GetNativeValuesDestination() => throw null; + public System.IntPtr ToNativeValue() => throw null; +}"; + string fixedSource = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer | CustomTypeMarshallerFeatures.TwoStageMarshalling, BufferSize = 1)] +ref struct Native +{ + public Native(S s, Span stackSpace, int nativeElementSize) : this() {} + + public System.ReadOnlySpan GetManagedValuesSource() => throw null; + public System.Span GetNativeValuesDestination() => throw null; + public System.IntPtr ToNativeValue() => throw null; + + public Native(S managed, int nativeElementSize) + { + throw new NotImplementedException(); + } +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + fixedSource, + VerifyCS.Diagnostic(CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackRule).WithLocation(0).WithArguments("Native", "S"), + VerifyCS.Diagnostic(LinearCollectionInRequiresTwoParameterConstructorRule).WithLocation(0).WithArguments("Native", "S")); + } + + [Fact] + public async Task CollectionNativeTypeWithMissingManagedValuesSource_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +ref struct {|#0:Native|} +{ + public Native(S s, int nativeElementSize) : this() {} + + public System.Span GetNativeValuesDestination() => throw null; + public System.IntPtr ToNativeValue() => throw null; +}"; + string fixedSource = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +ref struct Native +{ + public Native(S s, int nativeElementSize) : this() {} + + public System.Span GetNativeValuesDestination() => throw null; + public System.IntPtr ToNativeValue() => throw null; + + public ReadOnlySpan GetManagedValuesSource() + { + throw new NotImplementedException(); + } +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(LinearCollectionInRequiresCollectionMethodsRule).WithLocation(0).WithArguments("Native", "S"), + fixedSource); + } + + [Fact] + public async Task CollectionNativeTypeWithMissingNativeValuesDestination_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +ref struct {|#0:Native|} +{ + public Native(S s, int nativeElementSize) : this() {} + + public System.ReadOnlySpan GetManagedValuesSource() => throw null; + public System.IntPtr ToNativeValue() => throw null; +}"; + string fixedSource = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +ref struct Native +{ + public Native(S s, int nativeElementSize) : this() {} + + public System.ReadOnlySpan GetManagedValuesSource() => throw null; + public System.IntPtr ToNativeValue() => throw null; + + public Span GetNativeValuesDestination() + { + throw new NotImplementedException(); + } +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(LinearCollectionInRequiresCollectionMethodsRule).WithLocation(0).WithArguments("Native", "S"), + fixedSource); + } + + [Fact] + public async Task CollectionNativeTypeWithCorrectRefShape_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +ref struct Native +{ + public Native(int nativeElementSize) : this() {} + public Native(S s, int nativeElementSize) : this() {} + + public ReadOnlySpan GetManagedValuesSource() => throw null; + public Span GetNativeValuesDestination() => throw null; + public ReadOnlySpan GetNativeValuesSource(int length) => throw null; + public Span GetManagedValuesDestination(int length) => throw null; + public IntPtr ToNativeValue() => throw null; + public void FromNativeValue(IntPtr value) => throw null; + public S ToManaged() => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task CollectionNativeTypeWithMismatched_Element_Type_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +ref struct Native +{ + public Native(int nativeElementSize) : this() {} + public Native(S s, int nativeElementSize) : this() {} + + public ReadOnlySpan {|#0:GetManagedValuesSource|}() => throw null; + public Span GetNativeValuesDestination() => throw null; + public ReadOnlySpan GetNativeValuesSource(int length) => throw null; + public Span GetManagedValuesDestination(int length) => throw null; + public IntPtr ToNativeValue() => throw null; + public void FromNativeValue(IntPtr value) => throw null; + public S ToManaged() => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + source, + VerifyCS.Diagnostic(LinearCollectionElementTypesMustMatchRule) + .WithLocation(0)); + } + + [Fact] + public async Task NativeTypeWithOnlyConstructor_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.In)] +struct Native +{ + public Native(S s) {} +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task NativeTypeWithOnlyToManagedMethod_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.Out)] +struct Native +{ + public S ToManaged() => new S(); +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task NativeTypeWithOnlyStackallocConstructor_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer, BufferSize = 0x100)] +struct {|#0:Native|} +{ + public Native(S s, Span buffer) {} +}"; + string fixedSource = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer, BufferSize = 0x100)] +struct Native +{ + public Native(S s, Span buffer) {} + + public Native(S managed) + { + throw new NotImplementedException(); + } +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + fixedSource, + VerifyCS.Diagnostic(CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackRule).WithLocation(0).WithArguments("Native", "S"), + VerifyCS.Diagnostic(ValueInRequiresOneParameterConstructorRule).WithLocation(0).WithArguments("Native", "S")); + } + + [Fact] + public async Task TypeWithOnlyGetPinnableReference_AndInSupport_ReportsDiagnostics() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class {|#0:S|} +{ + public byte c; + public ref byte GetPinnableReference() => ref c; +} + +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.Out)] +struct {|#1:Native|} +{ + public S ToManaged() => default; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule).WithLocation(0).WithArguments("S", "Native"), + source); + } + + [Fact] + public async Task NativeTypeWithConstructorAndFromNativeValueMethod_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +struct {|#0:Native|} +{ + public Native(S s) {} + + public void FromNativeValue(IntPtr value) => throw null; + + public S ToManaged() => new S(); +}"; + + string fixedSource = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +struct Native +{ + public Native(S s) {} + + public void FromNativeValue(IntPtr value) => throw null; + + public S ToManaged() => new S(); + + public IntPtr ToNativeValue() + { + throw new NotImplementedException(); + } +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(InTwoStageMarshallingRequiresToNativeValueRule).WithLocation(0).WithArguments("Native"), + fixedSource); + } + + [Fact] + public async Task NativeTypeWithToManagedAndToNativeValueMethod_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +struct {|#0:Native|} +{ + public Native(S managed) {} + + public S ToManaged() => new S(); + + public IntPtr ToNativeValue() => IntPtr.Zero; +}"; + string fixedSource = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +struct Native +{ + public Native(S managed) {} + + public S ToManaged() => new S(); + + public IntPtr ToNativeValue() => IntPtr.Zero; + + public void FromNativeValue(IntPtr value) + { + throw new NotImplementedException(); + } +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(OutTwoStageMarshallingRequiresFromNativeValueRule).WithLocation(0).WithArguments("Native"), + fixedSource); + } + + [Fact] + public async Task BlittableNativeTypeOnMarshalUsingParameter_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +struct S +{ + public string s; +} + +[CustomTypeMarshaller(typeof(S))] +struct Native +{ + private IntPtr value; + + public Native(S s) + { + value = IntPtr.Zero; + } + + public S ToManaged() => new S(); +} + + +static class Test +{ + static void Foo([MarshalUsing(typeof(Native))] S s) + {} +} +"; + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task NonBlittableNativeTypeOnMarshalUsingParameter_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +struct S +{ + public string s; +} + +[CustomTypeMarshaller(typeof(S))] +struct {|#0:Native|} +{ + private string value; + + public Native(S s) : this() + { + } + + public S ToManaged() => new S(); +} + + +static class Test +{ + static void Foo([MarshalUsing(typeof(Native))] S s) + {} +} +"; + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithLocation(0).WithArguments("Native", "S"), + source); + } + + [Fact] + public async Task NonBlittableNativeTypeOnMarshalUsingReturn_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +struct S +{ + public string s; +} + +[CustomTypeMarshaller(typeof(S))] +struct {|#0:Native|} +{ + private string value; + + public Native(S s) : this() + { + } + + public S ToManaged() => new S(); +} + + +static class Test +{ + [return: MarshalUsing(typeof(Native))] + static S Foo() => new S(); +} +"; + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithLocation(0).WithArguments("Native", "S"), + source); + } + + [Fact] + public async Task GenericNativeTypeWithGenericMemberInstantiatedWithBlittable_DoesNotReportDiagnostic() + { + + string source = @" +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +struct S +{ + public string s; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +struct Native + where T : unmanaged +{ + public Native(S s) + { + } + + public S ToManaged() => new S(); + + public T ToNativeValue() => throw null; + public void FromNativeValue(T value) => throw null; +}"; + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task UninstantiatedGenericNativeTypeOnNonGeneric_ReportsDiagnostic() + { + + string source = @" +using System.Runtime.InteropServices; + +[{|#0:NativeMarshalling(typeof(Native<>))|}] +struct S +{ + public string s; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +struct Native + where T : unmanaged +{ + public Native(S s) + { + } + + public S ToManaged() => new S(); + + public T ToNativeValue() => throw null; + public void FromNativeValue(T value) => throw null; +}"; + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(NativeGenericTypeMustBeClosedOrMatchArityRule).WithLocation(0).WithArguments("Native<>", "S"), + source); + } + + [Fact] + public async Task MarshalUsingUninstantiatedGenericNativeType_ReportsDiagnostic() + { + + string source = @" +using System.Runtime.InteropServices; + +struct S +{ + public string s; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +struct Native + where T : unmanaged +{ + public Native(S s) + { + } + + public S ToManaged() => new S(); + + public T ToNativeValue() => throw null; + public void FromNativeValue(T value) => throw null; +} + +static class Test +{ + static void Foo([{|#0:MarshalUsing(typeof(Native<>))|}] S s) + {} +}"; + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(NativeGenericTypeMustBeClosedOrMatchArityRule).WithLocation(0).WithArguments("Native<>", "S"), + source); + } + + [Fact] + public async Task UninstantiatedGenericNativeTypeOnGenericWithArityMismatch_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; + +[{|#0:NativeMarshalling(typeof(Native<,>))|}] +struct S +{ + public string s; +} + +[CustomTypeMarshaller(typeof(S<>), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +struct {|#1:Native|} + where T : new() +{ + public Native(S s) + { + } + + public S ToManaged() => new S(); + + public T ToNativeValue() => throw null; + public void FromNativeValue(T value) => throw null; +}"; + await VerifyCS.VerifyCodeFixAsync(source, + source, + VerifyCS.Diagnostic(NativeTypeMustHaveCustomTypeMarshallerAttributeRule).WithLocation(0).WithArguments("S"), + VerifyCS.Diagnostic(NativeGenericTypeMustBeClosedOrMatchArityRule).WithLocation(1).WithArguments("Native", "S<>")); + } + + [Fact] + public async Task UninstantiatedGenericNativeTypeOnGenericWithArityMatch_DoesNotReportDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native<>))] +struct S +{ + public T t; +} + +[CustomTypeMarshaller(typeof(S<>), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +struct Native + where T : new() +{ + public Native(S s) + { + } + + public S ToManaged() => new S(); + + public T ToNativeValue() => throw null; + public void FromNativeValue(T value) => throw null; +}"; + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task NativeTypeWithStackallocConstructorWithoutBufferSize_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer)] +struct Native +{ + public Native(S s) {} + public {|#0:Native|}(S s, Span buffer) {} +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + VerifyCS.Diagnostic(CallerAllocConstructorMustHaveBufferSizeRule).WithLocation(0).WithArguments("Native"), + source); + } + + [Fact] + public async Task CustomTypeMarshallerForTypeWithPlaceholder_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[CustomTypeMarshaller(typeof(CustomTypeMarshallerAttribute.GenericPlaceholder), Direction = CustomTypeMarshallerDirection.In)] +struct Native +{ + public Native(T a) {} +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task CustomTypeMarshallerForArrayTypeWithPlaceholder_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[CustomTypeMarshaller(typeof(CustomTypeMarshallerAttribute.GenericPlaceholder[]), Direction = CustomTypeMarshallerDirection.In)] +struct Native +{ + public Native(T[] a) {} +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task CustomTypeMarshallerForPointerTypeWithPlaceholder_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[CustomTypeMarshaller(typeof(CustomTypeMarshallerAttribute.GenericPlaceholder*), Direction = CustomTypeMarshallerDirection.In)] +unsafe struct Native where T : unmanaged +{ + public Native(T* a) {} +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task CustomTypeMarshallerForArrayOfPointerTypeWithPlaceholder_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[CustomTypeMarshaller(typeof(CustomTypeMarshallerAttribute.GenericPlaceholder*[]), Direction = CustomTypeMarshallerDirection.In)] +unsafe struct Native where T : unmanaged +{ + public Native(T*[] a) {} +}"; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Fact] + public async Task CustomTypeMarshallerWithFreeNativeMethod_NoUnmanagedResourcesFeatures_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +struct S { } + +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.In)] +unsafe struct {|#0:Native|} +{ + public Native(S s){} + + public void FreeNative() { } +}"; + string fixedSource = @" +using System; +using System.Runtime.InteropServices; + +struct S { } + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.Value, Direction = CustomTypeMarshallerDirection.In, Features = CustomTypeMarshallerFeatures.UnmanagedResources)] +unsafe struct Native +{ + public Native(S s){} + + public void FreeNative() { } +}"; + await VerifyCS.VerifyCodeFixAsync(source, + fixedSource, + VerifyCS.Diagnostic(FreeNativeMethodProvidedShouldSpecifyUnmanagedResourcesFeatureRule) + .WithArguments("Native") + .WithLocation(0)); + } + [Fact] + public async Task CustomTypeMarshallerWithCallerAllocatedBufferConstructor_NoFeature_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +struct S { } + +[CustomTypeMarshaller(typeof(S), Direction = CustomTypeMarshallerDirection.In, BufferSize = 0x100)] +unsafe struct {|#0:Native|} +{ + public Native(S s){} + + public Native(S s, Span buffer) { } +}"; + string fixedSource = @" +using System; +using System.Runtime.InteropServices; + +struct S { } + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.Value, Direction = CustomTypeMarshallerDirection.In, BufferSize = 256, Features = CustomTypeMarshallerFeatures.CallerAllocatedBuffer)] +unsafe struct Native +{ + public Native(S s){} + + public Native(S s, Span buffer) { } +}"; + await VerifyCS.VerifyCodeFixAsync(source, + fixedSource, + VerifyCS.Diagnostic(CallerAllocatedBufferConstructorProvidedShouldSpecifyFeatureRule) + .WithArguments("Native") + .WithLocation(0)); + } + + [Fact] + public async Task Add_Feature_Declaration_Preserves_Attribute_Argument_Location() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +struct S { } + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.None, Direction = CustomTypeMarshallerDirection.In)] +unsafe struct {|#0:Native|} +{ + public Native(S s){} + + public void FreeNative() { } +}"; + string fixedSource = @" +using System; +using System.Runtime.InteropServices; + +struct S { } + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.Value, Features = CustomTypeMarshallerFeatures.UnmanagedResources, Direction = CustomTypeMarshallerDirection.In)] +unsafe struct Native +{ + public Native(S s){} + + public void FreeNative() { } +}"; + await VerifyCS.VerifyCodeFixAsync(source, + fixedSource, + VerifyCS.Diagnostic(FreeNativeMethodProvidedShouldSpecifyUnmanagedResourcesFeatureRule) + .WithArguments("Native") + .WithLocation(0)); + } + + [Fact] + public async Task CustomTypeMarshallerWithTwoStageMarshallingMethod_NoFeature_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +struct S { } + +[CustomTypeMarshaller(typeof(S))] +unsafe struct {|#0:Native|} +{ + public Native(S s){} + + public int ToNativeValue() => throw null; + + public S ToManaged() => throw null; +} + +[CustomTypeMarshaller(typeof(S))] +unsafe struct {|#1:Native2|} +{ + public Native2(S s){} + + public void FromNativeValue(int value) { } + + public S ToManaged() => throw null; +}"; + string fixedSource = @" +using System; +using System.Runtime.InteropServices; + +struct S { } + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.Value, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +unsafe struct Native +{ + public Native(S s){} + + public int ToNativeValue() => throw null; + + public S ToManaged() => throw null; + + public void FromNativeValue(int value) + { + throw new NotImplementedException(); + } +} + +[CustomTypeMarshaller(typeof(S), CustomTypeMarshallerKind.Value, Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +unsafe struct Native2 +{ + public Native2(S s){} + + public void FromNativeValue(int value) { } + + public S ToManaged() => throw null; + + public int ToNativeValue() + { + throw new NotImplementedException(); + } +}"; + await VerifyCS.VerifyCodeFixAsync(source, + new[] + { + VerifyCS.Diagnostic(ToNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureRule) + .WithArguments("Native") + .WithLocation(0), + VerifyCS.Diagnostic(FromNativeValueMethodProvidedShouldSpecifyTwoStageMarshallingFeatureRule) + .WithArguments("Native2") + .WithLocation(1) + }, + fixedSource, + // One code-fix run is expected for each of the two diagnostics. + // Each fix of the "specifiy the feature" diagnostic will result in code that reports another diagnostic + // for the missing other member. + // The second two code-fix runs are the fixes for those diagnostics. + numIncrementalIterations: 4, + // The first run adds the feature flag and the second adds the missing members for the feature. + numFixAllIterations: 2); + } + + [Fact] + public async Task Mismatched_NativeValue_Type_ReportsDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +unsafe struct Native +{ + public Native(S s) { } + + public S ToManaged() => new S(); + + public int {|#0:ToNativeValue|}() => throw null; + public void FromNativeValue(long value) => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + source, + VerifyCS.Diagnostic(TwoStageMarshallingNativeTypesMustMatchRule) + .WithLocation(0)); + } + + [Fact] + public async Task Same_NativeValue_Type_DifferentName_DoesNotReportDiagnostic() + { + string source = @" +using System; +using System.Runtime.InteropServices; +using Value2 = N.Value; + +namespace N +{ + struct Value + { + private int i; + } +} + +[NativeMarshalling(typeof(Native))] +class S +{ + public byte c; +} + +[CustomTypeMarshaller(typeof(S), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] +unsafe struct Native +{ + public Native(S s) { } + + public S ToManaged() => new S(); + + public N.Value ToNativeValue() => throw null; + public void FromNativeValue(Value2 value) => throw null; +}"; + + await VerifyCS.VerifyCodeFixAsync(source, + source); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ManualTypeMarshallingAnalyzerTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ManualTypeMarshallingAnalyzerTests.cs deleted file mode 100644 index bdc1eaf362505..0000000000000 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ManualTypeMarshallingAnalyzerTests.cs +++ /dev/null @@ -1,1240 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Testing; -using System.Collections.Generic; -using System.Threading.Tasks; -using Xunit; -using static Microsoft.Interop.Analyzers.ManualTypeMarshallingAnalyzer; - -using VerifyCS = LibraryImportGenerator.UnitTests.Verifiers.CSharpAnalyzerVerifier; - -namespace LibraryImportGenerator.UnitTests -{ - [ActiveIssue("https://github.com/dotnet/runtime/issues/60650", TestRuntimes.Mono)] - public class ManualTypeMarshallingAnalyzerTests - { - [Fact] - public async Task NullNativeType_ReportsDiagnostic() - { - string source = @" -using System.Runtime.InteropServices; - -[{|#0:NativeMarshalling(null)|}] -struct S -{ - public string s; -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(NativeTypeMustBeNonNullRule).WithLocation(0).WithArguments("S")); - } - - [Fact] - public async Task NonNamedNativeType_ReportsDiagnostic() - { - string source = @" -using System.Runtime.InteropServices; - -[{|#0:NativeMarshalling(typeof(int*))|}] -struct S -{ - public string s; -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(NativeTypeMustHaveRequiredShapeRule).WithLocation(0).WithArguments("int*", "S")); - } - - [Fact] - public async Task NonBlittableNativeType_ReportsDiagnostic() - { - string source = @" -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -struct S -{ - public string s; -} - -struct {|#0:Native|} -{ - private string value; - - public Native(S s) - { - value = s.s; - } - - public S ToManaged() => new S { s = value }; -}"; - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithLocation(0).WithArguments("Native", "S")); - } - - [Fact] - public async Task ClassNativeType_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -struct S -{ - public string s; -} - -class {|#0:Native|} -{ - private IntPtr value; - - public Native(S s) - { - } - - public S ToManaged() => new S(); -}"; - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(NativeTypeMustHaveRequiredShapeRule).WithLocation(0).WithArguments("Native", "S")); - } - - [Fact] - public async Task BlittableNativeType_DoesNotReportDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -struct S -{ - public string s; -} - -struct Native -{ - private IntPtr value; - - public Native(S s) - { - value = IntPtr.Zero; - } - - public S ToManaged() => new S(); -}"; - - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task BlittableNativeWithNonBlittableValueProperty_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -struct S -{ - public string s; -} - -struct Native -{ - private IntPtr value; - - public Native(S s) - { - value = IntPtr.Zero; - } - - public S ToManaged() => new S(); - - public string {|#0:Value|} { get => null; set {} } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithLocation(0).WithArguments("string", "S")); - } - - [Fact] - public async Task NonBlittableNativeTypeWithBlittableValueProperty_DoesNotReportDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -struct S -{ - public string s; -} - -struct Native -{ - private string value; - - public Native(S s) - { - value = s.s; - } - - public S ToManaged() => new S() { s = value }; - - public IntPtr Value { get => IntPtr.Zero; set {} } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task ClassNativeTypeWithValueProperty_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -struct S -{ - public string s; -} - -class {|#0:Native|} -{ - private string value; - - public Native(S s) - { - value = s.s; - } - - public S ToManaged() => new S() { s = value }; - - public IntPtr Value { get => IntPtr.Zero; set {} } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(NativeTypeMustHaveRequiredShapeRule).WithLocation(0).WithArguments("Native", "S")); - } - - [Fact] - public async Task NonBlittableGetPinnableReferenceReturnType_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public string s; - - public ref string {|#0:GetPinnableReference|}() => ref s; -} - -unsafe struct Native -{ - private IntPtr value; - - public Native(S s) - { - value = IntPtr.Zero; - } - - public S ToManaged() => new S(); - - public IntPtr Value { get => IntPtr.Zero; set {} } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(GetPinnableReferenceReturnTypeBlittableRule).WithLocation(0)); - } - - [Fact] - public async Task BlittableGetPinnableReferenceReturnType_DoesNotReportDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; - - public ref byte GetPinnableReference() => ref c; -} - -unsafe struct Native -{ - private IntPtr value; - - public Native(S s) : this() - { - value = IntPtr.Zero; - } - - public S ToManaged() => new S(); - - public IntPtr Value { get => IntPtr.Zero; set {} } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task NonBlittableMarshallerGetPinnableReferenceReturnType_DoesNotReportDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public char c; -} - -unsafe struct Native -{ - private IntPtr value; - - public Native(S s) - { - value = IntPtr.Zero; - } - - public ref char GetPinnableReference() => ref System.Runtime.CompilerServices.Unsafe.NullRef(); - - public S ToManaged() => new S(); - - public IntPtr Value { get => IntPtr.Zero; set {} } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task BlittableMarshallerGetPinnableReferenceReturnType_DoesNotReportDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -unsafe struct Native -{ - private IntPtr value; - - public Native(S s) : this() - { - value = IntPtr.Zero; - } - - public S ToManaged() => new S(); - - public ref byte GetPinnableReference() => ref System.Runtime.CompilerServices.Unsafe.NullRef(); - - public IntPtr Value { get => IntPtr.Zero; set {} } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task TypeWithGetPinnableReferenceNonPointerReturnType_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; - - public ref byte GetPinnableReference() => ref c; -} - -unsafe struct Native -{ - private IntPtr value; - - public Native(S s) : this() - { - value = IntPtr.Zero; - } - - public S ToManaged() => new S(); - - public int {|#0:Value|} { get => 0; set {} } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(NativeTypeMustBePointerSizedRule).WithLocation(0).WithArguments("int", "S")); - } - - [Fact] - public async Task TypeWithGetPinnableReferencePointerReturnType_DoesNotReportDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; - - public ref byte GetPinnableReference() => ref c; -} - -unsafe struct Native -{ - private IntPtr value; - - public Native(S s) : this() - { - value = IntPtr.Zero; - } - - public S ToManaged() => new S(); - - public int* Value { get => null; set {} } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task TypeWithGetPinnableReferenceByRefValuePropertyType_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; - - public ref byte GetPinnableReference() => ref c; -} - -unsafe struct Native -{ - private S value; - - public Native(S s) : this() - { - value = s; - } - - public ref byte {|#0:Value|} { get => ref value.GetPinnableReference(); } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RefValuePropertyUnsupportedRule).WithLocation(0).WithArguments("Native")); - } - - [Fact] - public async Task NativeTypeWithGetPinnableReferenceByRefValuePropertyType_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -unsafe struct Native -{ - private S value; - - public Native(S s) : this() - { - value = s; - } - - public ref byte GetPinnableReference() => ref value.c; - - public ref byte {|#0:Value|} { get => ref GetPinnableReference(); } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RefValuePropertyUnsupportedRule).WithLocation(0).WithArguments("Native")); - } - - [Fact] - public async Task NativeTypeWithGetPinnableReferenceNoValueProperty_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -unsafe struct Native -{ - private byte value; - - public Native(S s) : this() - { - value = s.c; - } - - public ref byte {|#0:GetPinnableReference|}() => ref System.Runtime.CompilerServices.Unsafe.NullRef(); -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(MarshallerGetPinnableReferenceRequiresValuePropertyRule).WithLocation(0).WithArguments("Native")); - } - - [Fact] - public async Task NativeTypeWithGetPinnableReferenceWithNonPointerValueProperty_DoesNotReportDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -unsafe struct Native -{ - private byte value; - - public Native(S s) : this() - { - value = s.c; - } - - public ref byte GetPinnableReference() => ref System.Runtime.CompilerServices.Unsafe.NullRef(); - - public int Value { get; set; } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task NativeTypeWithNoMarshallingMethods_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -struct {|#0:Native|} -{ -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(NativeTypeMustHaveRequiredShapeRule).WithLocation(0).WithArguments("Native", "S")); - } - - [Fact] - public async Task CollectionNativeTypeWithNoMarshallingMethods_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -[GenericContiguousCollectionMarshaller] -struct {|#0:Native|} -{ -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(CollectionNativeTypeMustHaveRequiredShapeRule).WithLocation(0).WithArguments("Native", "S")); - } - - [Fact] - public async Task CollectionNativeTypeWithWrongConstructor_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -[GenericContiguousCollectionMarshaller] -ref struct {|#0:Native|} -{ - public Native(S s) : this() {} - - public Span ManagedValues { get; set; } - public Span NativeValueStorage { get; set; } - - public IntPtr Value { get; } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(CollectionNativeTypeMustHaveRequiredShapeRule).WithLocation(0).WithArguments("Native", "S")); - } - - [Fact] - public async Task CollectionNativeTypeWithCorrectConstructor_DoesNotReportDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -[GenericContiguousCollectionMarshaller] -ref struct Native -{ - public Native(S s, int nativeElementSize) : this() {} - - public Span ManagedValues { get; set; } - public Span NativeValueStorage { get; set; } - - public IntPtr Value { get; } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task CollectionNativeTypeWithIncorrectStackallocConstructor_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -[GenericContiguousCollectionMarshaller] -ref struct {|#0:Native|} -{ - public Native(S s, Span stackSpace) : this() {} - - public const int BufferSize = 1; - - public Span ManagedValues { get; set; } - public Span NativeValueStorage { get; set; } - - public IntPtr Value { get; } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(CollectionNativeTypeMustHaveRequiredShapeRule).WithLocation(0).WithArguments("Native", "S")); - } - - [Fact] - public async Task CollectionNativeTypeWithOnlyStackallocConstructor_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -[GenericContiguousCollectionMarshaller] -ref struct {|#0:Native|} -{ - public Native(S s, Span stackSpace, int nativeElementSize) : this() {} - - public const int BufferSize = 1; - - public Span ManagedValues { get; set; } - public Span NativeValueStorage { get; set; } - - public IntPtr Value { get; } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackRule).WithLocation(0).WithArguments("Native", "S")); - } - - [Fact] - public async Task CollectionNativeTypeWithMissingManagedValuesProperty_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -[GenericContiguousCollectionMarshaller] -ref struct {|#0:Native|} -{ - public Native(S s, int nativeElementSize) : this() {} - - public Span NativeValueStorage { get; set; } - - public IntPtr Value { get; } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(CollectionNativeTypeMustHaveRequiredShapeRule).WithLocation(0).WithArguments("Native", "S")); - } - - [Fact] - public async Task CollectionNativeTypeWithMissingNativeValueStorageProperty_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -[GenericContiguousCollectionMarshaller] -ref struct {|#0:Native|} -{ - public Native(S s, int nativeElementSize) : this() {} - - public Span ManagedValues { get; set; } - - public IntPtr Value { get; } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(CollectionNativeTypeMustHaveRequiredShapeRule).WithLocation(0).WithArguments("Native", "S")); - } - - [Fact] - public async Task NativeTypeWithOnlyConstructor_DoesNotReportDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -struct Native -{ - public Native(S s) {} -}"; - - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task NativeTypeWithOnlyToManagedMethod_DoesNotReportDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -struct Native -{ - public S ToManaged() => new S(); -}"; - - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task NativeTypeWithOnlyStackallocConstructor_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -struct {|#0:Native|} -{ - public Native(S s, Span buffer) {} - - public const int BufferSize = 0x100; -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackRule).WithLocation(0).WithArguments("Native")); - } - - [Fact] - public async Task TypeWithOnlyNativeStackallocConstructorAndGetPinnableReference_ReportsDiagnostics() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[{|#0:NativeMarshalling(typeof(Native))|}] -class S -{ - public byte c; - public ref byte GetPinnableReference() => ref c; -} - -struct {|#1:Native|} -{ - public Native(S s, Span buffer) {} - - public IntPtr Value => IntPtr.Zero; - - public const int BufferSize = 0x100; -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(CallerAllocMarshallingShouldSupportAllocatingMarshallingFallbackRule).WithLocation(1).WithArguments("Native"), - VerifyCS.Diagnostic(GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackRule).WithLocation(0).WithArguments("S", "Native")); - } - - [Fact] - public async Task NativeTypeWithConstructorAndSetOnlyValueProperty_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -struct Native -{ - public Native(S s) {} - - public IntPtr {|#0:Value|} { set {} } -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(ValuePropertyMustHaveGetterRule).WithLocation(0).WithArguments("Native")); - } - - [Fact] - public async Task NativeTypeWithToManagedAndGetOnlyValueProperty_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -struct Native -{ - public S ToManaged() => new S(); - - public IntPtr {|#0:Value|} => IntPtr.Zero; -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(ValuePropertyMustHaveSetterRule).WithLocation(0).WithArguments("Native")); - } - - [Fact] - public async Task BlittableNativeTypeOnMarshalUsingParameter_DoesNotReportDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -struct S -{ - public string s; -} - -struct Native -{ - private IntPtr value; - - public Native(S s) - { - value = IntPtr.Zero; - } - - public S ToManaged() => new S(); -} - - -static class Test -{ - static void Foo([MarshalUsing(typeof(Native))] S s) - {} -} -"; - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task NonBlittableNativeTypeOnMarshalUsingParameter_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -struct S -{ - public string s; -} - -struct {|#0:Native|} -{ - private string value; - - public Native(S s) : this() - { - } - - public S ToManaged() => new S(); -} - - -static class Test -{ - static void Foo([MarshalUsing(typeof(Native))] S s) - {} -} -"; - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithLocation(0).WithArguments("Native", "S")); - } - - [Fact] - public async Task NonBlittableNativeTypeOnMarshalUsingParameter_MultipleCompilations_ReportsDiagnostic_WithLocation() - { - string source1 = @" -using System; -using System.Runtime.InteropServices; - -public struct S -{ - public string s; -} - -public struct Native -{ - private string value; - - public Native(S s) : this() - { - } - - public S ToManaged() => new S(); -} -"; - Compilation compilation1 = await TestUtils.CreateCompilation(source1); - - string source2 = @" -using System; -using System.Runtime.InteropServices; - -static class Test -{ - static void Foo([{|#0:MarshalUsing(typeof(Native))|}] S s) - {} -} -"; - var test = new Verifiers.CSharpCodeFixVerifier.Test - { - ExpectedDiagnostics = - { - VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithLocation(0).WithArguments("Native", "S") - }, - SolutionTransforms = - { - (solution, projectId) => solution.AddMetadataReference(projectId, compilation1.ToMetadataReference()) - }, - TestCode = source2 - }; - - await test.RunAsync(); - } - - [Fact] - public async Task NonBlittableNativeTypeOnMarshalUsingReturn_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -struct S -{ - public string s; -} - -struct {|#0:Native|} -{ - private string value; - - public Native(S s) : this() - { - } - - public S ToManaged() => new S(); -} - - -static class Test -{ - [return: MarshalUsing(typeof(Native))] - static S Foo() => new S(); -} -"; - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithLocation(0).WithArguments("Native", "S")); - } - - [Fact] - public async Task NonBlittableNativeTypeOnMarshalUsingField_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -struct S -{ - public string s; -} - -struct {|#0:Native|} -{ - private string value; - - public Native(S s) : this() - { - } - - public S ToManaged() => new S(); -} - - -struct Test -{ - [MarshalUsing(typeof(Native))] - S s; -} -"; - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(NativeTypeMustBeBlittableRule).WithLocation(0).WithArguments("Native", "S")); - } - - [Fact] - public async Task GenericNativeTypeWithValueTypeValueProperty_DoesNotReportDiagnostic() - { - string source = @" -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -struct S -{ - public string s; -} - -struct Native - where T : new() -{ - public Native(T s) - { - Value = 0; - } - - public T ToManaged() => new T(); - - public int Value { get; set; } -}"; - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task GenericNativeTypeWithGenericMemberInstantiatedWithBlittable_DoesNotReportDiagnostic() - { - - string source = @" -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -struct S -{ - public string s; -} - -struct Native - where T : new() -{ - public Native(S s) - { - Value = new T(); - } - - public S ToManaged() => new S(); - - public T Value { get; set; } -}"; - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task UninstantiatedGenericNativeTypeOnNonGeneric_ReportsDiagnostic() - { - - string source = @" -using System.Runtime.InteropServices; - -[{|#0:NativeMarshalling(typeof(Native<>))|}] -struct S -{ - public string s; -} - -struct Native - where T : new() -{ - public Native(S s) - { - Value = new T(); - } - - public S ToManaged() => new S(); - - public T Value { get; set; } -}"; - await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(NativeGenericTypeMustBeClosedOrMatchArityRule).WithLocation(0).WithArguments("Native<>", "S")); - } - - [Fact] - public async Task UninstantiatedGenericNativeTypeOnGenericWithArityMismatch_ReportsDiagnostic() - { - string source = @" -using System.Runtime.InteropServices; - -[{|#0:NativeMarshalling(typeof(Native<,>))|}] -struct S -{ - public string s; -} - -struct Native - where T : new() -{ - public Native(S s) - { - Value = 0; - } - - public S ToManaged() => new S(); - - public int Value { get; set; } -}"; - await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(NativeGenericTypeMustBeClosedOrMatchArityRule).WithLocation(0).WithArguments("Native<,>", "S")); - } - - [Fact] - public async Task UninstantiatedGenericNativeTypeOnGenericWithArityMatch_DoesNotReportDiagnostic() - { - string source = @" -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native<>))] -struct S -{ - public T t; -} - -struct Native - where T : new() -{ - public Native(S s) - { - Value = 0; - } - - public S ToManaged() => new S(); - - public int Value { get; set; } -}"; - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [Fact] - public async Task NativeTypeWithStackallocConstructorWithoutBufferSize_ReportsDiagnostic() - { - string source = @" -using System; -using System.Runtime.InteropServices; - -[NativeMarshalling(typeof(Native))] -class S -{ - public byte c; -} - -struct Native -{ - public Native(S s) {} - public {|#0:Native|}(S s, Span buffer) {} -}"; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(CallerAllocConstructorMustHaveBufferSizeConstantRule).WithLocation(0).WithArguments("Native")); - } - } -} diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Verifiers/CSharpCodeFixVerifier.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Verifiers/CSharpCodeFixVerifier.cs index f56377d272ecc..dd30ab7a8c8a8 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Verifiers/CSharpCodeFixVerifier.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Verifiers/CSharpCodeFixVerifier.cs @@ -66,6 +66,34 @@ public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] ex await test.RunAsync(CancellationToken.None); } + /// + public static async Task VerifyCodeFixAsync(string source, string fixedSource, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource, + int numIncrementalIterations, int numFixAllIterations) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + NumberOfIncrementalIterations = numIncrementalIterations, + NumberOfFixAllIterations = numFixAllIterations + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + internal class Test : CSharpCodeFixTest { public Test() diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/NonBlittable.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/NonBlittable.cs index e121166439096..155edea1bc9c2 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/NonBlittable.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/NonBlittable.cs @@ -17,6 +17,7 @@ public struct StringContainer public string str2; } + [CustomTypeMarshaller(typeof(StringContainer), Features = CustomTypeMarshallerFeatures.UnmanagedResources)] public struct StringContainerNative { public IntPtr str1; @@ -44,6 +45,7 @@ public void FreeNative() } } + [CustomTypeMarshaller(typeof(double), Features = CustomTypeMarshallerFeatures.TwoStageMarshalling)] public struct DoubleToLongMarshaler { public long l; @@ -55,11 +57,9 @@ public DoubleToLongMarshaler(double d) public double ToManaged() => MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref l, 1))[0]; - public long Value - { - get => l; - set => l = value; - } + public long ToNativeValue() => l; + + public void FromNativeValue(long value) => l = value; } [NativeMarshalling(typeof(BoolStructNative))] @@ -70,6 +70,7 @@ public struct BoolStruct public bool b3; } + [CustomTypeMarshaller(typeof(BoolStruct))] public struct BoolStructNative { public byte b1; @@ -101,6 +102,7 @@ public class IntWrapper public ref int GetPinnableReference() => ref i; } + [CustomTypeMarshaller(typeof(IntWrapper), Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling)] public unsafe struct IntWrapperMarshaler { public IntWrapperMarshaler(IntWrapper managed) @@ -109,7 +111,10 @@ public IntWrapperMarshaler(IntWrapper managed) *Value = managed.i; } - public int* Value { get; set; } + private int* Value { get; set; } + + public int* ToNativeValue() => Value; + public void FromNativeValue(int* value) => Value = value; public IntWrapper ToManaged() => new IntWrapper { i = *Value }; @@ -119,6 +124,7 @@ public void FreeNative() } } + [CustomTypeMarshaller(typeof(string), Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer, BufferSize = 0x100)] public unsafe ref struct Utf16StringMarshaller { private ushort* allocated; @@ -160,24 +166,19 @@ public ref ushort GetPinnableReference() return ref span.GetPinnableReference(); } - public ushort* Value + public ushort* ToNativeValue() => (ushort*)Unsafe.AsPointer(ref GetPinnableReference()); + + public void FromNativeValue(ushort* value) { - get - { - return (ushort*)Unsafe.AsPointer(ref GetPinnableReference()); - } - set + allocated = value; + span = new Span(value, value == null ? 0 : FindStringLength(value)); + isNullString = value == null; + + static int FindStringLength(ushort* ptr) { - allocated = value; - span = new Span(value, value == null ? 0 : FindStringLength(value)); - isNullString = value == null; - - static int FindStringLength(ushort* ptr) - { - // Implemented similarly to string.wcslen as we can't access that outside of CoreLib - var searchSpace = new Span(ptr, int.MaxValue); - return searchSpace.IndexOf((ushort)0); - } + // Implemented similarly to string.wcslen as we can't access that outside of CoreLib + var searchSpace = new Span(ptr, int.MaxValue); + return searchSpace.IndexOf((ushort)0); } } @@ -190,11 +191,9 @@ public void FreeNative() { Marshal.FreeCoTaskMem((IntPtr)allocated); } - - public const int BufferSize = 0x100; - public const bool RequiresStackBuffer = true; } + [CustomTypeMarshaller(typeof(string), Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer, BufferSize = 0x100)] public unsafe ref struct Utf8StringMarshaller { private byte* allocated; @@ -223,24 +222,13 @@ public Utf8StringMarshaller(string str, Span buffer) } } - public byte* Value - { - get - { - return allocated != null ? allocated : (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); - } - set - { - allocated = value; - } - } + public byte* ToNativeValue() => allocated != null ? allocated : (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); - public string? ToManaged() => Marshal.PtrToStringUTF8((IntPtr)Value); + public void FromNativeValue(byte* value) => allocated = value; - public void FreeNative() => Marshal.FreeCoTaskMem((IntPtr)allocated); + public string? ToManaged() => Marshal.PtrToStringUTF8((IntPtr)allocated); - public const int BufferSize = 0x100; - public const bool RequiresStackBuffer = true; + public void FreeNative() => Marshal.FreeCoTaskMem((IntPtr)allocated); } [NativeMarshalling(typeof(IntStructWrapperNative))] @@ -249,6 +237,7 @@ public struct IntStructWrapper public int Value; } + [CustomTypeMarshaller(typeof(IntStructWrapper))] public struct IntStructWrapperNative { public int value; @@ -260,7 +249,7 @@ public IntStructWrapperNative(IntStructWrapper managed) public IntStructWrapper ToManaged() => new IntStructWrapper { Value = value }; } - [GenericContiguousCollectionMarshaller] + [CustomTypeMarshaller(typeof(List<>), CustomTypeMarshallerKind.LinearCollection, Features = CustomTypeMarshallerFeatures.UnmanagedResources | CustomTypeMarshallerFeatures.TwoStageMarshalling | CustomTypeMarshallerFeatures.CallerAllocatedBuffer, BufferSize = 0x200)] public unsafe ref struct ListMarshaller { private List managedList; @@ -302,48 +291,39 @@ public ListMarshaller(List managed, Span stackSpace, int sizeOfNativeEl } } - /// - /// Stack-alloc threshold set to 256 bytes to enable small arrays to be passed on the stack. - /// Number kept small to ensure that P/Invokes with a lot of array parameters doesn't - /// blow the stack since this is a new optimization in the code-generated interop. - /// - public const int BufferSize = 0x200; - public const bool RequiresStackBuffer = true; - - public Span ManagedValues => CollectionsMarshal.AsSpan(managedList); + public ReadOnlySpan GetManagedValuesSource() => CollectionsMarshal.AsSpan(managedList); - public Span NativeValueStorage { get; private set; } - - public ref byte GetPinnableReference() => ref NativeValueStorage.GetPinnableReference(); - - public void SetUnmarshalledCollectionLength(int length) + public Span GetManagedValuesDestination(int length) { + if (allocatedMemory == IntPtr.Zero) + { + managedList = null; + return default; + } managedList = new List(length); for (int i = 0; i < length; i++) { managedList.Add(default); } + return CollectionsMarshal.AsSpan(managedList); } - public byte* Value + private Span NativeValueStorage { get; set; } + + public Span GetNativeValuesDestination() => NativeValueStorage; + + public ReadOnlySpan GetNativeValuesSource(int length) { - get - { - return (byte*)Unsafe.AsPointer(ref GetPinnableReference()); - } - set - { - if (value == null) - { - managedList = null; - NativeValueStorage = default; - } - else - { - allocatedMemory = (IntPtr)value; - NativeValueStorage = new Span(value, (managedList?.Count ?? 0) * sizeOfNativeElement); - } - } + return allocatedMemory == IntPtr.Zero ? default : NativeValueStorage = new Span((void*)allocatedMemory, length * sizeOfNativeElement); + } + + public ref byte GetPinnableReference() => ref NativeValueStorage.GetPinnableReference(); + + public byte* ToNativeValue() => (byte*)Unsafe.AsPointer(ref GetPinnableReference()); + + public void FromNativeValue(byte* value) + { + allocatedMemory = (IntPtr)value; } public List ToManaged() => managedList;