From 18aa6f1961aa891569e384ef9c12e631ce4939d7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 4 Jan 2021 14:29:27 -0800 Subject: [PATCH] Add handling for [In, Out] attributes. (#380) --- .../ArrayTests.cs | 30 +++ .../CodeSnippets.cs | 14 ++ .../CompileFails.cs | 16 ++ .../DllImportGenerator.UnitTests/Compiles.cs | 5 + .../Marshalling/BlittableArrayMarshaller.cs | 179 ++++++++++-------- .../Marshalling/BlittableMarshaller.cs | 2 + .../Marshalling/BoolMarshaller.cs | 2 + .../Marshalling/CharMarshaller.cs | 2 + ...nditionalStackallocMarshallingGenerator.cs | 3 + .../Marshalling/CustomNativeTypeMarshaller.cs | 2 + .../Marshalling/DelegateMarshaller.cs | 2 + .../Marshalling/Forwarder.cs | 2 + .../Marshalling/HResultExceptionMarshaller.cs | 2 + .../Marshalling/MarshallerHelpers.cs | 13 ++ .../Marshalling/MarshallingGenerator.cs | 52 ++++- .../NonBlittableArrayMarshaller.cs | 64 ++++++- .../Marshalling/SafeHandleMarshaller.cs | 2 + .../Marshalling/StringMarshaller.Ansi.cs | 2 + .../StringMarshaller.PlatformDefined.cs | 2 + .../Marshalling/StringMarshaller.Utf16.cs | 2 + .../Marshalling/StringMarshaller.Utf8.cs | 2 + .../DllImportGenerator/Resources.Designer.cs | 44 +++++ .../DllImportGenerator/Resources.resx | 15 ++ .../DllImportGenerator/TypeNames.cs | 4 + .../DllImportGenerator/TypePositionInfo.cs | 56 +++++- .../TestAssets/NativeExports/Arrays.cs | 29 ++- .../TestAssets/SharedTypes/NonBlittable.cs | 17 ++ DllImportGenerator/designs/Compatibility.md | 4 + 28 files changed, 481 insertions(+), 88 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator.IntegrationTests/ArrayTests.cs b/DllImportGenerator/DllImportGenerator.IntegrationTests/ArrayTests.cs index 7a7b738e71dc..5bc762921831 100644 --- a/DllImportGenerator/DllImportGenerator.IntegrationTests/ArrayTests.cs +++ b/DllImportGenerator/DllImportGenerator.IntegrationTests/ArrayTests.cs @@ -39,6 +39,13 @@ public partial class Arrays [GeneratedDllImport(NativeExportsNE_Binary, EntryPoint = "append_int_to_array")] public static partial void Append([MarshalAs(UnmanagedType.LPArray, SizeConst = 1, SizeParamIndex = 1)] ref int[] values, int numOriginalValues, int newValue); + [GeneratedDllImport(NativeExportsNE_Binary, EntryPoint = "fill_range_array")] + [return: MarshalAs(UnmanagedType.U1)] + public static partial bool FillRangeArray([Out] IntStructWrapper[] array, int length, int start); + + [GeneratedDllImport(NativeExportsNE_Binary, EntryPoint = "double_values")] + public static partial bool DoubleValues([In, Out] IntStructWrapper[] array, int length); + [GeneratedDllImport(NativeExportsNE_Binary, EntryPoint = "and_all_members")] [return:MarshalAs(UnmanagedType.U1)] public static partial bool AndAllMembers(BoolStruct[] pArray, int length); @@ -156,6 +163,29 @@ public void DynamicSizedArrayWithConstantComponent() Assert.Equal(array.Concat(new [] { newValue }), newArray); } + [Fact] + public void ArrayByValueOutParameter() + { + var testArray = new IntStructWrapper[10]; + int start = 5; + + NativeExportsNE.Arrays.FillRangeArray(testArray, testArray.Length, start); + + Assert.Equal(Enumerable.Range(start, 10), testArray.Select(wrapper => wrapper.Value)); + } + + [Fact] + public void ArrayByValueInOutParameter() + { + var testValues = Enumerable.Range(42, 15).Select(i => new IntStructWrapper { Value = i }); + + var testArray = testValues.ToArray(); + + NativeExportsNE.Arrays.DoubleValues(testArray, testArray.Length); + + Assert.Equal(testValues.Select(wrapper => wrapper.Value * 2), testArray.Select(wrapper => wrapper.Value)); + } + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/CodeSnippets.cs b/DllImportGenerator/DllImportGenerator.UnitTests/CodeSnippets.cs index 214537e518e1..324b66ece135 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/CodeSnippets.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/CodeSnippets.cs @@ -367,6 +367,20 @@ partial class Test public static string BasicParametersAndModifiers() => BasicParametersAndModifiers(typeof(T).ToString()); + /// + /// Declaration with [In, Out] style attributes on a by-value parameter. + /// + public static string ByValueParameterWithModifier(string typeName, string attributeName) => @$" +using System.Runtime.InteropServices; +partial class Test +{{ + [GeneratedDllImport(""DoesNotExist"")] + public static partial void Method( + [{attributeName}] {typeName} p); +}}"; + + public static string ByValueParameterWithModifier(string attributeName) => ByValueParameterWithModifier(typeof(T).ToString(), attributeName); + /// /// Declaration with parameters with MarshalAs. /// diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/CompileFails.cs b/DllImportGenerator/DllImportGenerator.UnitTests/CompileFails.cs index 41fe64dc27c8..61f98f45fff8 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/CompileFails.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/CompileFails.cs @@ -39,6 +39,22 @@ public static IEnumerable CodeSnippetsToCompile() // * UnmanagedType.CustomMarshaler, MarshalTypeRef, MarshalType, MarshalCookie yield return new object[] { CodeSnippets.MarshalAsCustomMarshalerOnTypes, 16, 0 }; + // Unsupported [In, Out] attributes usage + // Blittable array + yield return new object[] { CodeSnippets.ByValueParameterWithModifier("Out"), 1, 0 }; + yield return new object[] { CodeSnippets.ByValueParameterWithModifier("In, Out"), 1, 0 }; + + // By ref with [In, Out] attributes + yield return new object[] { CodeSnippets.ByValueParameterWithModifier("in int", "In"), 1, 0 }; + yield return new object[] { CodeSnippets.ByValueParameterWithModifier("ref int", "In"), 1, 0 }; + yield return new object[] { CodeSnippets.ByValueParameterWithModifier("ref int", "In, Out"), 1, 0 }; + yield return new object[] { CodeSnippets.ByValueParameterWithModifier("out int", "Out"), 1, 0 }; + + // By value non-array with [In, Out] attributes + yield return new object[] { CodeSnippets.ByValueParameterWithModifier("In"), 1, 0 }; + yield return new object[] { CodeSnippets.ByValueParameterWithModifier("Out"), 1, 0 }; + yield return new object[] { CodeSnippets.ByValueParameterWithModifier("In, Out"), 1, 0 }; + // Unsupported named arguments // * BestFitMapping, ThrowOnUnmappableChar yield return new object[] { CodeSnippets.AllDllImportNamedArguments, 2, 0 }; diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs b/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs index 932104664930..1db8e4b0ead8 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs @@ -91,6 +91,11 @@ public static IEnumerable CodeSnippetsToCompile() yield return new[] { CodeSnippets.ArrayParameterWithNestedMarshalInfo(UnmanagedType.LPUTF8Str) }; yield return new[] { CodeSnippets.ArrayParameterWithNestedMarshalInfo(UnmanagedType.LPStr) }; + // [In, Out] attributes + // By value non-blittable array + yield return new[] { CodeSnippets.ByValueParameterWithModifier("Out") }; + yield return new[] { CodeSnippets.ByValueParameterWithModifier("In, Out") }; + // Enums yield return new[] { CodeSnippets.EnumParameters }; diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs index 2a7e54ac1b15..8e7291f9158b 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs @@ -101,92 +101,116 @@ public override IEnumerable Generate(TypePositionInfo info, Stu yield return statement; } - // new Span(managedIdentifier).CopyTo(new Span(nativeIdentifier, managedIdentifier.Length)); - yield return ExpressionStatement( - InvocationExpression( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - ObjectCreationExpression( - GenericName(Identifier(TypeNames.System_Span), - TypeArgumentList( - SingletonSeparatedList( - GetElementTypeSyntax(info))))) - .WithArgumentList( - ArgumentList(SingletonSeparatedList( - Argument(IdentifierName(managedIdentifer))))), - IdentifierName("CopyTo"))) - .WithArgumentList( - ArgumentList( - SingletonSeparatedList( + // new Span(nativeIdentifier, managedIdentifier.Length) + var nativeSpan = ObjectCreationExpression( + GenericName(TypeNames.System_Span) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList(spanElementTypeSyntax)))) + .WithArgumentList( + ArgumentList( + SeparatedList( + new []{ Argument( - ObjectCreationExpression( - GenericName(TypeNames.System_Span) - .WithTypeArgumentList( - TypeArgumentList( - SingletonSeparatedList(spanElementTypeSyntax)))) - .WithArgumentList( - ArgumentList( - SeparatedList( - new []{ - Argument( - CastExpression( - PointerType(spanElementTypeSyntax), - IdentifierName(nativeIdentifier))), - Argument( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(managedIdentifer), - IdentifierName("Length")))})))))))); - } - break; - case StubCodeContext.Stage.Unmarshal: - if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In)) - { + CastExpression( + PointerType(spanElementTypeSyntax), + IdentifierName(nativeIdentifier))), + Argument( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(managedIdentifer), + IdentifierName("Length"))) + }))); + + // new Span(managedIdentifier).CopyTo(); yield return IfStatement( - BinaryExpression(SyntaxKind.NotEqualsExpression, - IdentifierName(nativeIdentifier), - LiteralExpression(SyntaxKind.NullLiteralExpression)), - Block( - // = new []; - ExpressionStatement( - AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, - IdentifierName(managedIdentifer), - ArrayCreationExpression( - ArrayType(GetElementTypeSyntax(info), - SingletonList(ArrayRankSpecifier( - SingletonSeparatedList(_numElementsExpr))))))), - // new Span(nativeIdentifier, managedIdentifier.Length).CopyTo(managedIdentifier); + BinaryExpression(SyntaxKind.NotEqualsExpression, + IdentifierName(managedIdentifer), + LiteralExpression(SyntaxKind.NullLiteralExpression)), ExpressionStatement( InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, - ObjectCreationExpression( - GenericName(Identifier(TypeNames.System_Span), - TypeArgumentList( - SingletonSeparatedList( - spanElementTypeSyntax)))) - .WithArgumentList( - ArgumentList( - SeparatedList( - new[]{ - Argument( - CastExpression( - PointerType(spanElementTypeSyntax), - IdentifierName(nativeIdentifier))), - Argument( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(managedIdentifer), - IdentifierName("Length")))}))), + ObjectCreationExpression( + GenericName(Identifier(TypeNames.System_Span), + TypeArgumentList( + SingletonSeparatedList( + spanElementTypeSyntax)))) + .WithArgumentList( + ArgumentList(SingletonSeparatedList( + Argument(IdentifierName(managedIdentifer))))), IdentifierName("CopyTo"))) .WithArgumentList( ArgumentList( SingletonSeparatedList( - Argument(IdentifierName(managedIdentifer))))))), - ElseClause( - ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, + Argument(nativeSpan)))))); + } + break; + case StubCodeContext.Stage.Unmarshal: + if (info.IsManagedReturnPosition + || (info.IsByRef && info.RefKind != RefKind.In) + || info.ByValueContentsMarshalKind.HasFlag(ByValueContentsMarshalKind.Out)) + { + // new Span(nativeIdentifier, managedIdentifier.Length).CopyTo(managedIdentifier); + var unmarshalContentsStatement = + ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ObjectCreationExpression( + GenericName(Identifier(TypeNames.System_Span), + TypeArgumentList( + SingletonSeparatedList( + spanElementTypeSyntax)))) + .WithArgumentList( + ArgumentList( + SeparatedList( + new[]{ + Argument(CastExpression( + PointerType(spanElementTypeSyntax), + IdentifierName(nativeIdentifier))), + Argument( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(managedIdentifer), + IdentifierName("Length"))) + }))), + IdentifierName("CopyTo"))) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument(IdentifierName(managedIdentifer)))))); + + if (info.IsManagedReturnPosition || info.IsByRef) + { + yield return IfStatement( + BinaryExpression(SyntaxKind.NotEqualsExpression, + IdentifierName(nativeIdentifier), + LiteralExpression(SyntaxKind.NullLiteralExpression)), + Block( + // = new []; + ExpressionStatement( + AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, + IdentifierName(managedIdentifer), + ArrayCreationExpression( + ArrayType(GetElementTypeSyntax(info), + SingletonList(ArrayRankSpecifier( + SingletonSeparatedList(_numElementsExpr))))))), + unmarshalContentsStatement), + ElseClause( + ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, + IdentifierName(managedIdentifer), + LiteralExpression(SyntaxKind.NullLiteralExpression))))); + } + else + { + yield return IfStatement( + BinaryExpression(SyntaxKind.NotEqualsExpression, IdentifierName(managedIdentifer), - LiteralExpression(SyntaxKind.NullLiteralExpression))))); + LiteralExpression(SyntaxKind.NullLiteralExpression)), + unmarshalContentsStatement); + } + } break; case StubCodeContext.Stage.Cleanup: @@ -242,6 +266,11 @@ protected override ExpressionSyntax GenerateFreeExpression(TypePositionInfo info ParseTypeName("System.IntPtr"), IdentifierName(context.GetIdentifiers(info).native)))))); } + + public override bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) + { + return !context.PinningSupported && marshalKind.HasFlag(ByValueContentsMarshalKind.Out); + } } } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/BlittableMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/BlittableMarshaller.cs index bca6a7db5863..df59478466fa 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/BlittableMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/BlittableMarshaller.cs @@ -99,6 +99,8 @@ public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) { return info.IsByRef && !info.IsManagedReturnPosition && !context.PinningSupported; } + + public bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; } } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/BoolMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/BoolMarshaller.cs index ea9fbea51a55..b659ccbda4b7 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/BoolMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/BoolMarshaller.cs @@ -100,6 +100,8 @@ public IEnumerable Generate(TypePositionInfo info, StubCodeCont } public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true; + + public bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; } /// diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/CharMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/CharMarshaller.cs index 43026da60abc..b3279390da77 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/CharMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/CharMarshaller.cs @@ -84,5 +84,7 @@ public IEnumerable Generate(TypePositionInfo info, StubCodeCont } public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true; + + public bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; } } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/ConditionalStackallocMarshallingGenerator.cs b/DllImportGenerator/DllImportGenerator/Marshalling/ConditionalStackallocMarshallingGenerator.cs index 7bc2a943e45b..f071d153d9bb 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/ConditionalStackallocMarshallingGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/ConditionalStackallocMarshallingGenerator.cs @@ -239,5 +239,8 @@ protected virtual ExpressionSyntax GenerateNullCheckExpression( /// public abstract bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context); + + /// + public abstract bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context); } } \ No newline at end of file diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/CustomNativeTypeMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/CustomNativeTypeMarshaller.cs index 4278cd6a9c9f..c2f70b702b8f 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/CustomNativeTypeMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/CustomNativeTypeMarshaller.cs @@ -266,5 +266,7 @@ public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) } return true; } + + public bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; } } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/DelegateMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/DelegateMarshaller.cs index 7145ceb2e991..b874ba0d1c6b 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/DelegateMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/DelegateMarshaller.cs @@ -106,5 +106,7 @@ public IEnumerable Generate(TypePositionInfo info, StubCodeCont } public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true; + + public bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; } } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/Forwarder.cs b/DllImportGenerator/DllImportGenerator/Marshalling/Forwarder.cs index 00f307e9a324..4921ae92ec64 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/Forwarder.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/Forwarder.cs @@ -32,5 +32,7 @@ public IEnumerable Generate(TypePositionInfo info, StubCodeCont } public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => false; + + public bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => true; } } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/HResultExceptionMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/HResultExceptionMarshaller.cs index 0cc935e08974..4b56c8a31072 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/HResultExceptionMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/HResultExceptionMarshaller.cs @@ -41,6 +41,8 @@ public IEnumerable Generate(TypePositionInfo info, StubCodeCont } public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => false; + + public bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; } } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallerHelpers.cs b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallerHelpers.cs index 2de9b1ecc320..fd2b8c6200b3 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallerHelpers.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallerHelpers.cs @@ -1,3 +1,4 @@ +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; @@ -60,6 +61,18 @@ public static LocalDeclarationStatementSyntax DeclareWithDefault(TypeSyntax type LiteralExpression(SyntaxKind.DefaultLiteralExpression)))))); } + public static RefKind GetRefKindForByValueContentsKind(this ByValueContentsMarshalKind byValue) + { + return byValue switch + { + ByValueContentsMarshalKind.Default => RefKind.None, + ByValueContentsMarshalKind.In => RefKind.In, + ByValueContentsMarshalKind.InOut => RefKind.Ref, + ByValueContentsMarshalKind.Out => RefKind.Out, + _ => throw new System.ArgumentOutOfRangeException(nameof(byValue)) + }; + } + public static class StringMarshaller { public static ExpressionSyntax AllocationExpression(CharEncoding encoding, string managedIdentifier) diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs index 16428f1a482f..9982ab0afb87 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs @@ -61,6 +61,15 @@ internal interface IMarshallingGenerator /// of may not be valid. /// bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context); + + /// + /// Returns if the given ByValueContentsMarshalKind is supported in the current marshalling context. + /// A supported marshal kind has a different behavior than the default behavior. + /// + /// The marshal kind. + /// The marshalling context. + /// + bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context); } /// @@ -113,7 +122,7 @@ internal class MarshallingGenerators public static readonly HResultExceptionMarshaller HResultException = new HResultExceptionMarshaller(); /// - /// Create an instance to marshalling the supplied type. + /// Create an instance for marshalling the supplied type in the given position. /// /// Type details /// Metadata about the stub the type is associated with @@ -122,6 +131,47 @@ public static IMarshallingGenerator Create( TypePositionInfo info, StubCodeContext context, AnalyzerConfigOptions options) + { + return ValidateByValueMarshalKind(context, info, CreateCore(info, context, options)); + } + + private static IMarshallingGenerator ValidateByValueMarshalKind(StubCodeContext context, TypePositionInfo info, IMarshallingGenerator generator) + { + if (info.IsByRef && info.ByValueContentsMarshalKind != ByValueContentsMarshalKind.Default) + { + throw new MarshallingNotSupportedException(info, context) + { + NotSupportedDetails = Resources.InOutAttributeByRefNotSupported + }; + } + else if (info.ByValueContentsMarshalKind == ByValueContentsMarshalKind.In) + { + throw new MarshallingNotSupportedException(info, context) + { + NotSupportedDetails = Resources.InAttributeNotSupportedWithoutOut + }; + } + else if (info.ByValueContentsMarshalKind != ByValueContentsMarshalKind.Default + && !generator.SupportsByValueMarshalKind(info.ByValueContentsMarshalKind, context)) + { + throw new MarshallingNotSupportedException(info, context) + { + NotSupportedDetails = Resources.InOutAttributeMarshalerNotSupported + }; + } + return generator; + } + + /// + /// Create an instance to marshalling the supplied type. + /// + /// Type details + /// Metadata about the stub the type is associated with + /// A instance. + private static IMarshallingGenerator CreateCore( + TypePositionInfo info, + StubCodeContext context, + AnalyzerConfigOptions options) { if (options.GenerateForwarders()) { diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs index 95b7fa170766..40d793c73101 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; @@ -64,6 +65,7 @@ public override ArgumentSyntax AsArgument(TypePositionInfo info, StubCodeContext public override IEnumerable Generate(TypePositionInfo info, StubCodeContext context) { var (managedIdentifer, nativeIdentifier) = context.GetIdentifiers(info); + RefKind elementRefKind = info.IsByRef ? info.RefKind : info.ByValueContentsMarshalKind.GetRefKindForByValueContentsKind(); bool cacheManagedValue = ShouldCacheManagedValue(info, context); string managedLocal = !cacheManagedValue ? managedIdentifer : managedIdentifer + ArrayMarshallingCodeContext.LocalManagedIdentifierSuffix; @@ -95,6 +97,8 @@ public override IEnumerable Generate(TypePositionInfo info, Stu yield return statement; } + var arraySubContext = new ArrayMarshallingCodeContext(context.CurrentStage, IndexerIdentifier, context, appendLocalManagedIdentifierSuffix: cacheManagedValue); + TypeSyntax spanElementTypeSyntax = GetNativeElementTypeSyntax(info); if (spanElementTypeSyntax is PointerTypeSyntax) { @@ -102,8 +106,14 @@ public override IEnumerable Generate(TypePositionInfo info, Stu spanElementTypeSyntax = ParseTypeName("System.IntPtr"); } + if (info is { IsByRef: false, ByValueContentsMarshalKind: ByValueContentsMarshalKind.Out }) + { + // We don't marshal values from managed to native for [Out] by value arrays, + // we only allocate the buffer. + yield break; + } + // Iterate through the elements of the array to marshal them - var arraySubContext = new ArrayMarshallingCodeContext(context.CurrentStage, IndexerIdentifier, context, appendLocalManagedIdentifierSuffix: cacheManagedValue); yield return IfStatement(BinaryExpression(SyntaxKind.NotEqualsExpression, IdentifierName(managedLocal), LiteralExpression(SyntaxKind.NullLiteralExpression)), @@ -135,13 +145,46 @@ public override IEnumerable Generate(TypePositionInfo info, Stu ArgumentList())), MarshallerHelpers.GetForLoop(managedLocal, IndexerIdentifier) .WithStatement(Block( - List(_elementMarshaller.Generate(info with { ManagedType = GetElementTypeSymbol(info) }, arraySubContext)))))); + List(_elementMarshaller.Generate( + info with { ManagedType = GetElementTypeSymbol(info), RefKind = elementRefKind }, + arraySubContext)))))); } break; case StubCodeContext.Stage.Unmarshal: - if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In)) + if (info.IsManagedReturnPosition + || (info.IsByRef && info.RefKind != RefKind.In) + || info.ByValueContentsMarshalKind.HasFlag(ByValueContentsMarshalKind.Out)) { var arraySubContext = new ArrayMarshallingCodeContext(context.CurrentStage, IndexerIdentifier, context, appendLocalManagedIdentifierSuffix: cacheManagedValue); + // Iterate through the elements of the native array to unmarshal them + StatementSyntax unmarshalContentsStatement = + MarshallerHelpers.GetForLoop(managedLocal, IndexerIdentifier) + .WithStatement(Block( + List(_elementMarshaller.Generate( + info with { ManagedType = GetElementTypeSymbol(info), RefKind = elementRefKind }, + arraySubContext)))); + + if (!info.IsByRef) + { + if (info.ByValueContentsMarshalKind.HasFlag(ByValueContentsMarshalKind.Out)) + { + yield return IfStatement( + BinaryExpression(SyntaxKind.NotEqualsExpression, + IdentifierName(managedLocal), + LiteralExpression(SyntaxKind.NullLiteralExpression)), + unmarshalContentsStatement); + + if (cacheManagedValue) + { + yield return ExpressionStatement( + AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, + IdentifierName(managedIdentifer), + IdentifierName(managedLocal)) + ); + } + yield break; + } + } yield return IfStatement( BinaryExpression(SyntaxKind.NotEqualsExpression, @@ -156,12 +199,8 @@ public override IEnumerable Generate(TypePositionInfo info, Stu ArrayType(GetElementTypeSymbol(info).AsTypeSyntax(), SingletonList(ArrayRankSpecifier( SingletonSeparatedList(_numElementsExpr))))))), - // Iterate through the elements of the native array to unmarshal them - MarshallerHelpers.GetForLoop(managedLocal, IndexerIdentifier) - .WithStatement(Block( - List(_elementMarshaller.Generate( - info with { ManagedType = GetElementTypeSymbol(info) }, - arraySubContext))))), + unmarshalContentsStatement + ), ElseClause( ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName(managedLocal), @@ -180,7 +219,7 @@ public override IEnumerable Generate(TypePositionInfo info, Stu case StubCodeContext.Stage.Cleanup: { var arraySubContext = new ArrayMarshallingCodeContext(context.CurrentStage, IndexerIdentifier, context, appendLocalManagedIdentifierSuffix: cacheManagedValue); - var elementCleanup = List(_elementMarshaller.Generate(info with { ManagedType = GetElementTypeSymbol(info) }, arraySubContext)); + var elementCleanup = List(_elementMarshaller.Generate(info with { ManagedType = GetElementTypeSymbol(info), RefKind = elementRefKind }, arraySubContext)); if (elementCleanup.Count != 0) { // Iterate through the elements of the native array to clean up any unmanaged resources. @@ -256,6 +295,11 @@ protected override ExpressionSyntax GenerateFreeExpression(TypePositionInfo info IdentifierName(context.GetIdentifiers(info).native)))))); } + public override bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) + { + return marshalKind.HasFlag(ByValueContentsMarshalKind.Out); + } + protected override ExpressionSyntax GenerateNullCheckExpression(TypePositionInfo info, StubCodeContext context) { string managedIdentifier = context.GetIdentifiers(info).managed; diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/SafeHandleMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/SafeHandleMarshaller.cs index 76bd09276131..5ff9b53c66cd 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/SafeHandleMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/SafeHandleMarshaller.cs @@ -232,5 +232,7 @@ public IEnumerable Generate(TypePositionInfo info, StubCodeCont } public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true; + + public bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; } } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.Ansi.cs b/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.Ansi.cs index 0c3756791d8e..b12dcfcd0231 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.Ansi.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.Ansi.cs @@ -147,6 +147,8 @@ public override IEnumerable Generate(TypePositionInfo info, Stu } public override bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true; + + public override bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; // This marshaller only uses the conditional allocaction base for setup and cleanup. // It always allocates for ANSI (Windows) and relies on the UTF-8 (non-Windows) string marshaller for allocation/marshalling. diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.PlatformDefined.cs b/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.PlatformDefined.cs index aa3d6ff83452..a9b665f88b09 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.PlatformDefined.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.PlatformDefined.cs @@ -109,6 +109,8 @@ public override IEnumerable Generate(TypePositionInfo info, Stu } public override bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true; + + public override bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; // This marshaller only uses the conditional allocaction base for setup and cleanup. // It relies on the UTF-16 (Windows) and UTF-8 (non-Windows) string marshallers for allocation/marshalling. diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.Utf16.cs b/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.Utf16.cs index 69e7fa146f3a..ffe989fddd2e 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.Utf16.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.Utf16.cs @@ -133,6 +133,8 @@ public override IEnumerable Generate(TypePositionInfo info, Stu public override bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true; + public override bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; + protected override ExpressionSyntax GenerateAllocationExpression( TypePositionInfo info, StubCodeContext context, diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.Utf8.cs b/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.Utf8.cs index 88172c237824..a4c52bffa9a2 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.Utf8.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/StringMarshaller.Utf8.cs @@ -101,6 +101,8 @@ public override IEnumerable Generate(TypePositionInfo info, Stu public override bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true; + public override bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false; + protected override ExpressionSyntax GenerateAllocationExpression( TypePositionInfo info, StubCodeContext context, diff --git a/DllImportGenerator/DllImportGenerator/Resources.Designer.cs b/DllImportGenerator/DllImportGenerator/Resources.Designer.cs index d7e9b3f4bc20..5a033df756ec 100644 --- a/DllImportGenerator/DllImportGenerator/Resources.Designer.cs +++ b/DllImportGenerator/DllImportGenerator/Resources.Designer.cs @@ -283,6 +283,32 @@ internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFal } } + /// + /// Looks up a localized string similar to The '[In]' attribute is not supported unless the '[Out]' attribute is also used. The behavior of the '[In]' attribute without the '[Out]' attribute is the same as the default behavior.. + /// + internal static string InAttributeNotSupportedWithoutOut { + get { + return ResourceManager.GetString("InAttributeNotSupportedWithoutOut", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The '[In]' and '[Out]' attributes are unsupported on parameters passed by reference. Use the 'in', 'ref', or 'out' keywords instead.. + /// + internal static string InOutAttributeByRefNotSupported { + get { + return ResourceManager.GetString("InOutAttributeByRefNotSupported", resourceCulture); + } + } + /// + /// Looks up a localized string similar to The '[In]' and '[Out]' attributes on this parameter are unsupported on this parameter.. + /// + internal static string InOutAttributeMarshalerNotSupported { + get { + return ResourceManager.GetString("InOutAttributeMarshalerNotSupported", resourceCulture); + } + } + /// /// Looks up a localized string similar to Marshalling char with 'CharSet.{0}' is not supported. Instead, manually convert the char type to the desired byte representation and pass to the source-generated P/Invoke.. /// @@ -373,6 +399,24 @@ internal static string NativeTypeMustHaveRequiredShapeMessage { } } + /// + /// Looks up a localized string similar to The '[Out]' attribute is only supported on array parameters.. + /// + internal static string OutByValueNotSupportedDescription { + get { + return ResourceManager.GetString("OutByValueNotSupportedDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The '[Out]' attribute is not supported on the '{0}' parameter.. + /// + internal static string OutByValueNotSupportedMessage { + get { + return ResourceManager.GetString("OutByValueNotSupportedMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to The 'Value' property must not be a 'ref' or 'readonly ref' property.. /// diff --git a/DllImportGenerator/DllImportGenerator/Resources.resx b/DllImportGenerator/DllImportGenerator/Resources.resx index a524a25381e8..4fe08e2f600c 100644 --- a/DllImportGenerator/DllImportGenerator/Resources.resx +++ b/DllImportGenerator/DllImportGenerator/Resources.resx @@ -192,6 +192,15 @@ Type '{0}' has a 'GetPinnableReference' method but its native type does not support marshalling in scenarios where pinning is impossible + + The '[In]' attribute is not supported unless the '[Out]' attribute is also used. The behavior of the '[In]' attribute without the '[Out]' attribute is the same as the default behavior. + + + The '[In]' and '[Out]' attributes are unsupported on parameters passed by reference. Use the 'in', 'ref', or 'out' keywords instead. + + + The provided '[In]' and '[Out]' attributes on this parameter are unsupported on this parameter. + Marshalling char with 'CharSet.{0}' is not supported. Instead, manually convert the char type to the desired byte representation and pass to the source-generated P/Invoke. @@ -222,6 +231,12 @@ 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 '[Out]' attribute is only supported on array parameters. + + + The '[Out]' attribute is not supported on the '{0}' parameter. + The 'Value' property must not be a 'ref' or 'readonly ref' property. diff --git a/DllImportGenerator/DllImportGenerator/TypeNames.cs b/DllImportGenerator/DllImportGenerator/TypeNames.cs index a504b3b2bf43..de5670fba75e 100644 --- a/DllImportGenerator/DllImportGenerator/TypeNames.cs +++ b/DllImportGenerator/DllImportGenerator/TypeNames.cs @@ -39,6 +39,10 @@ public static string MarshalEx(AnalyzerConfigOptions options) public const string System_Runtime_InteropServices_SafeHandle = "System.Runtime.InteropServices.SafeHandle"; + public const string System_Runtime_InteropServices_OutAttribute = "System.Runtime.InteropServices.OutAttribute"; + + public const string System_Runtime_InteropServices_InAttribute = "System.Runtime.InteropServices.InAttribute"; + public const string System_Runtime_CompilerServices_SkipLocalsInitAttribute = "System.Runtime.CompilerServices.SkipLocalsInitAttribute"; } } diff --git a/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs b/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs index 92e909b62ff3..10c707d9c68f 100644 --- a/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs +++ b/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs @@ -16,6 +16,35 @@ internal sealed record DefaultMarshallingInfo ( CharEncoding CharEncoding ); + /// + /// Describes how to marshal the contents of a value in comparison to the value itself. + /// Only makes sense for array-like types. For example, an "out" array doesn't change the + /// pointer to the array value, but it marshals the contents of the native array back to the + /// contents of the managed array. + /// + [Flags] + internal enum ByValueContentsMarshalKind + { + /// + /// Marshal contents from managed to native only. + /// This is the default behavior. + /// + Default = 0x0, + /// + /// Marshal contents from managed to native only. + /// This is the default behavior. + /// + In = 0x1, + /// + /// Marshal contents from native to managed only. + /// + Out = 0x2, + /// + /// Marshal contents both to and from native. + /// + InOut = In | Out + } + /// /// Positional type information involved in unmanaged/managed scenarios. /// @@ -43,6 +72,8 @@ private TypePositionInfo() public bool IsByRef => RefKind != RefKind.None; + public ByValueContentsMarshalKind ByValueContentsMarshalKind { get; init; } + public bool IsManagedReturnPosition { get => this.ManagedIndex == ReturnIndex; } public bool IsNativeReturnPosition { get => this.NativeIndex == ReturnIndex; } @@ -60,7 +91,8 @@ public static TypePositionInfo CreateForParameter(IParameterSymbol paramSymbol, InstanceIdentifier = paramSymbol.Name, RefKind = paramSymbol.RefKind, RefKindSyntax = RefKindToSyntax(paramSymbol.RefKind), - MarshallingAttributeInfo = marshallingInfo + MarshallingAttributeInfo = marshallingInfo, + ByValueContentsMarshalKind = GetByValueContentsMarshalKind(paramSymbol.GetAttributes(), compilation) }; return typeInfo; @@ -297,6 +329,28 @@ static bool TryCreateTypeBasedMarshallingInfo(ITypeSymbol type, DefaultMarshalli } } + private static ByValueContentsMarshalKind GetByValueContentsMarshalKind(IEnumerable attributes, Compilation compilation) + { + INamedTypeSymbol outAttributeType = compilation.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_OutAttribute)!; + INamedTypeSymbol inAttributeType = compilation.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_InAttribute)!; + + ByValueContentsMarshalKind marshalKind = ByValueContentsMarshalKind.Default; + + foreach (var attr in attributes) + { + if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, outAttributeType)) + { + marshalKind |= ByValueContentsMarshalKind.Out; + } + else if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, inAttributeType)) + { + marshalKind |= ByValueContentsMarshalKind.In; + } + } + + return marshalKind; + } + private static SyntaxKind RefKindToSyntax(RefKind refKind) { return refKind switch diff --git a/DllImportGenerator/TestAssets/NativeExports/Arrays.cs b/DllImportGenerator/TestAssets/NativeExports/Arrays.cs index c48e1241cc07..4dc800685151 100644 --- a/DllImportGenerator/TestAssets/NativeExports/Arrays.cs +++ b/DllImportGenerator/TestAssets/NativeExports/Arrays.cs @@ -67,7 +67,34 @@ public static void Duplicate(int** values, int numValues) return retVal; } - + + [UnmanagedCallersOnly(EntryPoint = "fill_range_array")] + [DNNE.C99DeclCode("struct int_struct_wrapper;")] + public static byte FillRange([DNNE.C99Type("struct int_struct_wrapper*")] IntStructWrapperNative* numValues, int length, int start) + { + if (numValues == null) + { + return 0; + } + + for (int i = 0; i < length; i++, start++) + { + numValues[i] = new IntStructWrapperNative { value = start }; + } + + return 1; + } + + [UnmanagedCallersOnly(EntryPoint = "double_values")] + [DNNE.C99DeclCode("struct int_struct_wrapper { int value; };")] + public static void DoubleValues([DNNE.C99Type("struct int_struct_wrapper*")] IntStructWrapperNative* numValues, int length) + { + for (int i = 0; i < length; i++) + { + numValues[i].value *= 2; + } + } + [UnmanagedCallersOnly(EntryPoint = "sum_string_lengths")] public static int SumStringLengths(ushort** strArray) { diff --git a/DllImportGenerator/TestAssets/SharedTypes/NonBlittable.cs b/DllImportGenerator/TestAssets/SharedTypes/NonBlittable.cs index 86289c66e0d9..823f1da0334f 100644 --- a/DllImportGenerator/TestAssets/SharedTypes/NonBlittable.cs +++ b/DllImportGenerator/TestAssets/SharedTypes/NonBlittable.cs @@ -200,4 +200,21 @@ public void FreeNative() public const int StackBufferSize = 0x100; } + + [NativeMarshalling(typeof(IntStructWrapperNative))] + public struct IntStructWrapper + { + public int Value; + } + + public struct IntStructWrapperNative + { + public int value; + public IntStructWrapperNative(IntStructWrapper managed) + { + value = managed.Value; + } + + public IntStructWrapper ToManaged() => new IntStructWrapper { Value = value }; + } } diff --git a/DllImportGenerator/designs/Compatibility.md b/DllImportGenerator/designs/Compatibility.md index 7579d77fff19..417feaedeaf5 100644 --- a/DllImportGenerator/designs/Compatibility.md +++ b/DllImportGenerator/designs/Compatibility.md @@ -62,6 +62,10 @@ In the source-generated marshalling, arrays will be allocated on the stack (inst [`LCIDConversionAttribute`](`https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.lcidconversionattribute`) will not be supported for methods marked with `GeneratedDllImportAttribute`. +### `[In, Out]` Attributes + +In the source generated marshalling, the `[In]` and `[Out]` attributes will only be supported on parameters passed by value. For by-ref parameters, users should use the `in`, `ref`, or `out` keywords respectively. Additionally, they will only be supported in scenarios where applying them would result in behavior different from the default, such as applying `[Out]` or `[In, Out]` to a by-value non-blittable array parameter. This is in contrast to the built-in system which will allow them in all cases even when they have no effect. + ## Verison 0 This version is the built-in IL Stub generation system that is triggered whenever a method marked with `DllImport` is invoked. \ No newline at end of file