From 110ac8160fd4b0f5b3bb67b7e15c86e7e6c00051 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 20 Nov 2020 17:09:29 -0800 Subject: [PATCH 01/10] Add support for marshalling arrays of types with simple custom marshalling and gracefully fail when an array of a type with complex custom marshalling (Value property) is introduced. --- .../ArrayTests.cs | 37 ++++++++++++++++- .../CodeSnippets.cs | 40 +++++++++++++++++++ .../DllImportGenerator.UnitTests/Compiles.cs | 2 + .../Marshalling/MarshallingGenerator.cs | 16 ++++++-- .../MarshallingAttributeInfo.cs | 22 +++++----- .../DllImportGenerator/Resources.Designer.cs | 9 +++++ .../DllImportGenerator/Resources.resx | 3 ++ .../DllImportGenerator/TypePositionInfo.cs | 40 ++++++++++++++----- .../TestAssets/NativeExports/Arrays.cs | 14 +++++++ 9 files changed, 155 insertions(+), 28 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator.IntegrationTests/ArrayTests.cs b/DllImportGenerator/DllImportGenerator.IntegrationTests/ArrayTests.cs index 14874d436432..c1d2e2e13856 100644 --- a/DllImportGenerator/DllImportGenerator.IntegrationTests/ArrayTests.cs +++ b/DllImportGenerator/DllImportGenerator.IntegrationTests/ArrayTests.cs @@ -1,4 +1,5 @@ -using System; +using SharedTypes; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; @@ -37,6 +38,10 @@ public partial class Arrays [GeneratedDllImport(nameof(NativeExportsNE), 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(nameof(NativeExportsNE), EntryPoint = "and_all_members")] + [return:MarshalAs(UnmanagedType.U1)] + public static partial bool AndAllMembers(BoolStruct[] pArray, int length); } } @@ -151,6 +156,36 @@ public void DynamicSizedArrayWithConstantComponent() Assert.Equal(array.Concat(new [] { newValue }), newArray); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ArrayWithSimpleNonBlittableTypeMarshalling(bool result) + { + var boolValues = new[] + { + new BoolStruct + { + b1 = true, + b2 = true, + b3 = true, + }, + new BoolStruct + { + b1 = true, + b2 = true, + b3 = true, + }, + new BoolStruct + { + b1 = true, + b2 = true, + b3 = result, + }, + }; + + Assert.Equal(result, NativeExportsNE.Arrays.AndAllMembers(boolValues, boolValues.Length)); + } + private static string ReverseChars(string value) { if (value == null) diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/CodeSnippets.cs b/DllImportGenerator/DllImportGenerator.UnitTests/CodeSnippets.cs index 3933c1528ae9..214537e518e1 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/CodeSnippets.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/CodeSnippets.cs @@ -864,6 +864,46 @@ struct Native private int i; public S ToManaged() => new S { b = i != 0 }; } +"; + + public static string ArrayMarshallingWithCustomStructElementWithValueProperty => ArrayParametersAndModifiers("IntStructWrapper") + @" +[NativeMarshalling(typeof(IntStructWrapperNative))] +public struct IntStructWrapper +{ + public int Value; +} + +public struct IntStructWrapperNative +{ + public IntStructWrapperNative(IntStructWrapper managed) + { + Value = managed.Value; + } + + public int Value { get; set; } + + public IntStructWrapper ToManaged() => new IntStructWrapper { Value = Value }; +} +"; + + public static string ArrayMarshallingWithCustomStructElement => ArrayParametersAndModifiers("IntStructWrapper") + @" +[NativeMarshalling(typeof(IntStructWrapperNative))] +public struct IntStructWrapper +{ + public int Value; +} + +public struct IntStructWrapperNative +{ + private int value; + + public IntStructWrapperNative(IntStructWrapper managed) + { + value = managed.Value; + } + + public IntStructWrapper ToManaged() => new IntStructWrapper { Value = value }; +} "; } } diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs b/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs index 8737deda3224..9ff4ba27b4e0 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs @@ -159,11 +159,13 @@ public static IEnumerable CodeSnippetsToCompile_NoDiagnostics() yield return new[] { CodeSnippets.CustomStructMarshallingPinnableParametersAndModifiers }; yield return new[] { CodeSnippets.CustomStructMarshallingNativeTypePinnable }; yield return new[] { CodeSnippets.CustomStructMarshallingMarshalUsingParametersAndModifiers }; + yield return new[] { CodeSnippets.ArrayMarshallingWithCustomStructElement }; } public static IEnumerable CodeSnippetsToCompile_WithDiagnostics() { yield return new[] { CodeSnippets.AllSupportedDllImportNamedArguments }; + yield return new[] { CodeSnippets.ArrayMarshallingWithCustomStructElementWithValueProperty }; } [Theory] diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs index d4c2409e61d8..f0a5bffe0e3d 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs @@ -182,15 +182,15 @@ public static IMarshallingGenerator Create( } return SafeHandle; - case { ManagedType: IArrayTypeSymbol { IsSZArray: true, ElementType: ITypeSymbol elementType }, MarshallingAttributeInfo: NoMarshallingInfo }: - return CreateArrayMarshaller(info, context, elementType, NoMarshallingInfo.Instance); + case { ManagedType: IArrayTypeSymbol { IsSZArray: true, ElementType: ITypeSymbol elementType }, MarshallingAttributeInfo: ArrayMarshallingInfo(MarshallingInfo elementMarshallingInfo) }: + return CreateArrayMarshaller(info, context, elementType, elementMarshallingInfo); case { ManagedType: IArrayTypeSymbol { IsSZArray: true, ElementType: ITypeSymbol elementType }, MarshallingAttributeInfo: ArrayMarshalAsInfo marshalAsInfo }: if (marshalAsInfo.UnmanagedArrayType != UnmanagedArrayType.LPArray) { throw new MarshallingNotSupportedException(info, context); } - return CreateArrayMarshaller(info, context, elementType, marshalAsInfo.CreateArraySubTypeMarshalAsInfo()); + return CreateArrayMarshaller(info, context, elementType, marshalAsInfo.ElementMarshallingInfo); // Marshalling in new model. // Must go before the cases that do not explicitly check for marshalling info to support @@ -364,7 +364,7 @@ private static ExpressionSyntax GetNumElementsExpressionFromMarshallingInfo(Type private static IMarshallingGenerator CreateArrayMarshaller(TypePositionInfo info, StubCodeContext context, ITypeSymbol elementType, MarshallingInfo elementMarshallingInfo) { - var elementMarshaller = Create(TypePositionInfo.CreateForType(elementType, elementMarshallingInfo), context); + var elementMarshaller = Create(TypePositionInfo.CreateForType(elementType, elementMarshallingInfo), new ArrayMarshallingCodeContext(StubCodeContext.Stage.Setup, string.Empty, context)); ExpressionSyntax numElementsExpression = LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0)); if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In)) { @@ -379,6 +379,14 @@ private static IMarshallingGenerator CreateArrayMarshaller(TypePositionInfo info private static IMarshallingGenerator CreateCustomNativeTypeMarshaller(TypePositionInfo info, StubCodeContext context, NativeMarshallingAttributeInfo marshalInfo) { + if (marshalInfo.ValuePropertyType is not null && !context.CanUseAdditionalTemporaryState) + { + throw new MarshallingNotSupportedException(info, context) + { + NotSupportedDetails = Resources.ValuePropertyMarshallingRequiresAdditionalState + }; + } + // 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) diff --git a/DllImportGenerator/DllImportGenerator/MarshallingAttributeInfo.cs b/DllImportGenerator/DllImportGenerator/MarshallingAttributeInfo.cs index 40998a3ba64f..891eed70a4ac 100644 --- a/DllImportGenerator/DllImportGenerator/MarshallingAttributeInfo.cs +++ b/DllImportGenerator/DllImportGenerator/MarshallingAttributeInfo.cs @@ -60,20 +60,11 @@ enum UnmanagedArrayType /// internal sealed record ArrayMarshalAsInfo( UnmanagedArrayType UnmanagedArrayType, - UnmanagedType UnmanagedArraySubType, int ArraySizeConst, short ArraySizeParamIndex, - CharEncoding CharEncoding) : MarshalAsInfo((UnmanagedType)UnmanagedArrayType, CharEncoding) - { - public MarshallingInfo CreateArraySubTypeMarshalAsInfo() - { - if (UnmanagedArraySubType == (UnmanagedType)UnspecifiedData) - { - return NoMarshallingInfo.Instance; - } - return new MarshalAsInfo(UnmanagedArraySubType, CharEncoding); - } - + CharEncoding CharEncoding, + MarshallingInfo ElementMarshallingInfo) : MarshalAsInfo((UnmanagedType)UnmanagedArrayType, CharEncoding) + { public const short UnspecifiedData = -1; } @@ -113,5 +104,10 @@ internal sealed record GeneratedNativeMarshallingAttributeInfo( /// The type of the element is a SafeHandle-derived type with no marshalling attributes. /// internal sealed record SafeHandleMarshallingInfo : MarshallingInfo; - + + + /// + /// Default marshalling for arrays + /// + internal sealed record ArrayMarshallingInfo(MarshallingInfo ElementMarshallingInfo) : MarshallingInfo; } diff --git a/DllImportGenerator/DllImportGenerator/Resources.Designer.cs b/DllImportGenerator/DllImportGenerator/Resources.Designer.cs index d64b2f4e427a..d9d3a33bb60f 100644 --- a/DllImportGenerator/DllImportGenerator/Resources.Designer.cs +++ b/DllImportGenerator/DllImportGenerator/Resources.Designer.cs @@ -483,6 +483,15 @@ internal static string TypeNotSupportedTitle { } } + /// + /// Looks up a localized string similar to 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.. + /// + internal static string ValuePropertyMarshallingRequiresAdditionalState { + get { + return ResourceManager.GetString("ValuePropertyMarshallingRequiresAdditionalState", 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.. /// diff --git a/DllImportGenerator/DllImportGenerator/Resources.resx b/DllImportGenerator/DllImportGenerator/Resources.resx index 64b0a29098a3..76d748a41467 100644 --- a/DllImportGenerator/DllImportGenerator/Resources.resx +++ b/DllImportGenerator/DllImportGenerator/Resources.resx @@ -264,6 +264,9 @@ 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. diff --git a/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs b/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs index bb9cfde5b81b..1c83a417d15f 100644 --- a/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs +++ b/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs @@ -105,7 +105,7 @@ private static MarshallingInfo GetMarshallingInfo(ITypeSymbol type, IEnumerable< if (SymbolEqualityComparer.Default.Equals(compilation.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_MarshalAsAttribute), attributeClass)) { // https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshalasattribute - return CreateMarshalAsInfo(attrData, defaultInfo, diagnostics); + return CreateMarshalAsInfo(type, attrData, defaultInfo, compilation, diagnostics); } else if (SymbolEqualityComparer.Default.Equals(compilation.GetTypeByMetadataName(TypeNames.MarshalUsingAttribute), attributeClass)) { @@ -135,7 +135,7 @@ private static MarshallingInfo GetMarshallingInfo(ITypeSymbol type, IEnumerable< // If the type doesn't have custom attributes that dictate marshalling, // then consider the type itself. - if (TryCreateTypeBasedMarshallingInfo(type, compilation, out MarshallingInfo infoMaybe)) + if (TryCreateTypeBasedMarshallingInfo(type, defaultInfo, compilation, diagnostics, out MarshallingInfo infoMaybe)) { return infoMaybe; } @@ -151,7 +151,7 @@ private static MarshallingInfo GetMarshallingInfo(ITypeSymbol type, IEnumerable< return NoMarshallingInfo.Instance; - static MarshalAsInfo CreateMarshalAsInfo(AttributeData attrData, DefaultMarshallingInfo defaultInfo, GeneratorDiagnostics diagnostics) + static MarshalAsInfo CreateMarshalAsInfo(ITypeSymbol type, AttributeData attrData, DefaultMarshallingInfo defaultInfo, Compilation compilation, GeneratorDiagnostics diagnostics) { object unmanagedTypeObj = attrData.ConstructorArguments[0].Value!; UnmanagedType unmanagedType = unmanagedTypeObj is short @@ -208,15 +208,29 @@ static MarshalAsInfo CreateMarshalAsInfo(AttributeData attrData, DefaultMarshall } } - return isArrayType - ? new ArrayMarshalAsInfo( + if (isArrayType) + { + MarshallingInfo elementMarshallingInfo = NoMarshallingInfo.Instance; + + if (unmanagedArraySubType != (UnmanagedType)ArrayMarshalAsInfo.UnspecifiedData) + { + elementMarshallingInfo = new MarshalAsInfo(unmanagedArraySubType, defaultInfo.CharEncoding); + } + else if (type is IArrayTypeSymbol { ElementType: ITypeSymbol elementType }) + { + elementMarshallingInfo = GetMarshallingInfo(elementType, Array.Empty(), defaultInfo, compilation, diagnostics); + } + + return new ArrayMarshalAsInfo( UnmanagedArrayType: (UnmanagedArrayType)unmanagedType, - UnmanagedArraySubType: unmanagedArraySubType, ArraySizeConst: arraySizeConst, ArraySizeParamIndex: arraySizeParamIndex, - CharEncoding: defaultInfo.CharEncoding - ) - : new MarshalAsInfo(unmanagedType, defaultInfo.CharEncoding); + CharEncoding: defaultInfo.CharEncoding, + ElementMarshallingInfo: elementMarshallingInfo + ); + } + + return new MarshalAsInfo(unmanagedType, defaultInfo.CharEncoding); } static NativeMarshallingAttributeInfo CreateNativeMarshallingInfo(ITypeSymbol type, Compilation compilation, AttributeData attrData, bool allowGetPinnableReference) @@ -262,7 +276,7 @@ static NativeMarshallingAttributeInfo CreateNativeMarshallingInfo(ITypeSymbol ty NativeTypePinnable: ManualTypeMarshallingHelper.FindGetPinnableReference(nativeType) is not null); } - static bool TryCreateTypeBasedMarshallingInfo(ITypeSymbol type, Compilation compilation, out MarshallingInfo marshallingInfo) + static bool TryCreateTypeBasedMarshallingInfo(ITypeSymbol type, DefaultMarshallingInfo defaultInfo, Compilation compilation, GeneratorDiagnostics diagnostics, out MarshallingInfo marshallingInfo) { var conversion = compilation.ClassifyCommonConversion(type, compilation.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_SafeHandle)!); if (conversion.Exists @@ -273,6 +287,12 @@ static bool TryCreateTypeBasedMarshallingInfo(ITypeSymbol type, Compilation comp marshallingInfo = new SafeHandleMarshallingInfo(); return true; } + + if (type is IArrayTypeSymbol { ElementType: ITypeSymbol elementType }) + { + marshallingInfo = new ArrayMarshallingInfo(GetMarshallingInfo(elementType, Array.Empty(), defaultInfo, compilation, diagnostics)); + return true; + } marshallingInfo = NoMarshallingInfo.Instance; return false; } diff --git a/DllImportGenerator/TestAssets/NativeExports/Arrays.cs b/DllImportGenerator/TestAssets/NativeExports/Arrays.cs index 97d09ef53e66..c48e1241cc07 100644 --- a/DllImportGenerator/TestAssets/NativeExports/Arrays.cs +++ b/DllImportGenerator/TestAssets/NativeExports/Arrays.cs @@ -1,3 +1,4 @@ +using SharedTypes; using System; using System.Collections.Generic; using System.Runtime.InteropServices; @@ -120,5 +121,18 @@ public static void Append(int** values, int numOriginalValues, int newValue) newArray[numOriginalValues] = newValue; *values = newArray; } + + [UnmanagedCallersOnly(EntryPoint = "and_all_members")] + [DNNE.C99DeclCode("struct bool_struct;")] + public static byte AndAllMembers([DNNE.C99Type("struct bool_struct*")] BoolStructNative* pArray, int length) + { + bool result = true; + for (int i = 0; i < length; i++) + { + BoolStruct managed = pArray[i].ToManaged(); + result &= managed.b1 && managed.b2 && managed.b3; + } + return result ? 1 : 0; + } } } \ No newline at end of file From f643041e6fcf71372188f860c9a8c174b7aec217 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 23 Nov 2020 11:45:10 -0800 Subject: [PATCH 02/10] PR feedback. --- .../CompileFails.cs | 3 +++ .../DllImportGenerator.UnitTests/Compiles.cs | 1 - .../ArrayMarshallingCodeContext.cs | 9 +++++++ .../Marshalling/MarshallingGenerator.cs | 24 ++++++++++--------- .../DllImportGenerator/StubCodeContext.cs | 16 +++++++++++++ 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/CompileFails.cs b/DllImportGenerator/DllImportGenerator.UnitTests/CompileFails.cs index 7500d50464ce..6ea35c0ebd24 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/CompileFails.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/CompileFails.cs @@ -74,6 +74,9 @@ public static IEnumerable CodeSnippetsToCompile() yield return new object[] { CodeSnippets.CustomStructMarshallingManagedToNativeOnlyReturnValue, 1, 0 }; yield return new object[] { CodeSnippets.CustomStructMarshallingNativeToManagedOnlyInParameter, 1, 0 }; yield return new object[] { CodeSnippets.CustomStructMarshallingStackallocOnlyRefParameter, 1, 0 }; + + // Custom type marshalling in arrays (complex case with Value property) + yield return new object[] { CodeSnippets.ArrayMarshallingWithCustomStructElementWithValueProperty, 5, 0 }; } [Theory] diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs b/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs index 9ff4ba27b4e0..54c220cbcc94 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs @@ -165,7 +165,6 @@ public static IEnumerable CodeSnippetsToCompile_NoDiagnostics() public static IEnumerable CodeSnippetsToCompile_WithDiagnostics() { yield return new[] { CodeSnippets.AllSupportedDllImportNamedArguments }; - yield return new[] { CodeSnippets.ArrayMarshallingWithCustomStructElementWithValueProperty }; } [Theory] diff --git a/DllImportGenerator/DllImportGenerator/ArrayMarshallingCodeContext.cs b/DllImportGenerator/DllImportGenerator/ArrayMarshallingCodeContext.cs index 705d5d7b418b..d286bab0795d 100644 --- a/DllImportGenerator/DllImportGenerator/ArrayMarshallingCodeContext.cs +++ b/DllImportGenerator/DllImportGenerator/ArrayMarshallingCodeContext.cs @@ -19,6 +19,15 @@ internal sealed class ArrayMarshallingCodeContext : StubCodeContext public override bool StackSpaceUsable => false; + /// + /// Additional variables other than the {managedIdentifier} and {nativeIdentifier} variables + /// can be added to the stub to track additional state for the marshaller in the stub. + /// + /// + /// Currently, array scenarios do not support declaring additional temporary variables to support + /// marshalling. This can be accomplished in the future with some additional infrastructure to support + /// declaring arrays additional arrays in the stub to support the temporary state. + /// public override bool CanUseAdditionalTemporaryState => false; public ArrayMarshallingCodeContext(Stage currentStage, string indexerIdentifier, StubCodeContext parentContext) diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs index f0a5bffe0e3d..5389ba8abff6 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs @@ -182,16 +182,6 @@ public static IMarshallingGenerator Create( } return SafeHandle; - case { ManagedType: IArrayTypeSymbol { IsSZArray: true, ElementType: ITypeSymbol elementType }, MarshallingAttributeInfo: ArrayMarshallingInfo(MarshallingInfo elementMarshallingInfo) }: - return CreateArrayMarshaller(info, context, elementType, elementMarshallingInfo); - - case { ManagedType: IArrayTypeSymbol { IsSZArray: true, ElementType: ITypeSymbol elementType }, MarshallingAttributeInfo: ArrayMarshalAsInfo marshalAsInfo }: - if (marshalAsInfo.UnmanagedArrayType != UnmanagedArrayType.LPArray) - { - throw new MarshallingNotSupportedException(info, context); - } - return CreateArrayMarshaller(info, context, elementType, marshalAsInfo.ElementMarshallingInfo); - // Marshalling in new model. // Must go before the cases that do not explicitly check for marshalling info to support // the user overridding the default marshalling rules with a MarshalUsing attribute. @@ -205,11 +195,15 @@ public static IMarshallingGenerator Create( case { MarshallingAttributeInfo: GeneratedNativeMarshallingAttributeInfo(string nativeTypeName) }: return Forwarder; + // Cases that just match on type must come after the checks for the new marshalling model. case { ManagedType: { SpecialType: SpecialType.System_Char } }: return CreateCharMarshaller(info, context); case { ManagedType: { SpecialType: SpecialType.System_String } }: return CreateStringMarshaller(info, context); + + case { ManagedType: IArrayTypeSymbol { IsSZArray: true, ElementType: ITypeSymbol elementType } }: + return CreateArrayMarshaller(info, context, elementType); case { ManagedType: { SpecialType: SpecialType.System_Void } }: return Forwarder; @@ -362,8 +356,16 @@ private static ExpressionSyntax GetNumElementsExpressionFromMarshallingInfo(Type return numElementsExpression; } - private static IMarshallingGenerator CreateArrayMarshaller(TypePositionInfo info, StubCodeContext context, ITypeSymbol elementType, MarshallingInfo elementMarshallingInfo) + private static IMarshallingGenerator CreateArrayMarshaller(TypePositionInfo info, StubCodeContext context, ITypeSymbol elementType) { + var elementMarshallingInfo = info.MarshallingAttributeInfo switch + { + ArrayMarshalAsInfo(UnmanagedType.LPArray, _) marshalAs => marshalAs.ElementMarshallingInfo, + ArrayMarshallingInfo marshalInfo => marshalInfo.ElementMarshallingInfo, + NoMarshallingInfo _ => NoMarshallingInfo.Instance, + _ => throw new MarshallingNotSupportedException(info, context) + }; + var elementMarshaller = Create(TypePositionInfo.CreateForType(elementType, elementMarshallingInfo), new ArrayMarshallingCodeContext(StubCodeContext.Stage.Setup, string.Empty, context)); ExpressionSyntax numElementsExpression = LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0)); if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In)) diff --git a/DllImportGenerator/DllImportGenerator/StubCodeContext.cs b/DllImportGenerator/DllImportGenerator/StubCodeContext.cs index ea84f868b16a..3ddef8d5e331 100644 --- a/DllImportGenerator/DllImportGenerator/StubCodeContext.cs +++ b/DllImportGenerator/DllImportGenerator/StubCodeContext.cs @@ -62,10 +62,26 @@ public enum Stage public Stage CurrentStage { get; protected set; } = Stage.Invalid; + /// + /// A fixed statement can be used on an individual value and the pointer + /// can be passed to native code. + /// public abstract bool PinningSupported { get; } + /// + /// Memory can be allocated via the stackalloc keyword and will live through + /// the full native context of the call. + /// public abstract bool StackSpaceUsable { get; } + /// + /// Additional variables other than the {managedIdentifier} and {nativeIdentifier} variables + /// can be added to the stub to track additional state for the marshaller in the stub. + /// + /// + /// In scenarios where the stub is defined within a single function, additional local variables + /// can be defined. + /// public abstract bool CanUseAdditionalTemporaryState { get; } protected const string GeneratedNativeIdentifierSuffix = "_gen_native"; From d1773bf09b792f5b62c5dc6b2e782331f86c3adb Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 23 Nov 2020 11:51:13 -0800 Subject: [PATCH 03/10] Invert if condition. --- .../DllImportGenerator/TypePositionInfo.cs | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs b/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs index 1c83a417d15f..92e909b62ff3 100644 --- a/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs +++ b/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs @@ -208,29 +208,28 @@ static MarshalAsInfo CreateMarshalAsInfo(ITypeSymbol type, AttributeData attrDat } } - if (isArrayType) + if (!isArrayType) { - MarshallingInfo elementMarshallingInfo = NoMarshallingInfo.Instance; - - if (unmanagedArraySubType != (UnmanagedType)ArrayMarshalAsInfo.UnspecifiedData) - { - elementMarshallingInfo = new MarshalAsInfo(unmanagedArraySubType, defaultInfo.CharEncoding); - } - else if (type is IArrayTypeSymbol { ElementType: ITypeSymbol elementType }) - { - elementMarshallingInfo = GetMarshallingInfo(elementType, Array.Empty(), defaultInfo, compilation, diagnostics); - } + return new MarshalAsInfo(unmanagedType, defaultInfo.CharEncoding); + } - return new ArrayMarshalAsInfo( - UnmanagedArrayType: (UnmanagedArrayType)unmanagedType, - ArraySizeConst: arraySizeConst, - ArraySizeParamIndex: arraySizeParamIndex, - CharEncoding: defaultInfo.CharEncoding, - ElementMarshallingInfo: elementMarshallingInfo - ); + MarshallingInfo elementMarshallingInfo = NoMarshallingInfo.Instance; + if (unmanagedArraySubType != (UnmanagedType)ArrayMarshalAsInfo.UnspecifiedData) + { + elementMarshallingInfo = new MarshalAsInfo(unmanagedArraySubType, defaultInfo.CharEncoding); + } + else if (type is IArrayTypeSymbol { ElementType: ITypeSymbol elementType }) + { + elementMarshallingInfo = GetMarshallingInfo(elementType, Array.Empty(), defaultInfo, compilation, diagnostics); } - return new MarshalAsInfo(unmanagedType, defaultInfo.CharEncoding); + return new ArrayMarshalAsInfo( + UnmanagedArrayType: (UnmanagedArrayType)unmanagedType, + ArraySizeConst: arraySizeConst, + ArraySizeParamIndex: arraySizeParamIndex, + CharEncoding: defaultInfo.CharEncoding, + ElementMarshallingInfo: elementMarshallingInfo + ); } static NativeMarshallingAttributeInfo CreateNativeMarshallingInfo(ITypeSymbol type, Compilation compilation, AttributeData attrData, bool allowGetPinnableReference) From f077d123e1f0b62d033b079142838e9a564d1498 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 23 Nov 2020 16:51:15 -0800 Subject: [PATCH 04/10] Zero allocated memory before starting marshalling so the user can identify when an element is empty due to not being marshalled. --- .../NonBlittableArrayMarshaller.cs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs index 6fe2811efcbd..5e502f49644d 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs @@ -18,7 +18,7 @@ internal class NonBlittableArrayMarshaller : ConditionalStackallocMarshallingGen private const string IndexerIdentifier = "__i"; - private IMarshallingGenerator _elementMarshaller; + private readonly IMarshallingGenerator _elementMarshaller; private readonly ExpressionSyntax _numElementsExpr; public NonBlittableArrayMarshaller(IMarshallingGenerator elementMarshaller, ExpressionSyntax numElementsExpr) @@ -83,6 +83,39 @@ public override IEnumerable Generate(TypePositionInfo info, Stu yield return statement; } + TypeSyntax spanElementTypeSyntax = GetNativeElementTypeSyntax(info); + if (spanElementTypeSyntax is PointerTypeSyntax) + { + // Pointers cannot be passed to generics, so use IntPtr for this case. + spanElementTypeSyntax = ParseTypeName("System.IntPtr"); + } + + // new Span(, .Length).Clear(); + yield return ExpressionStatement( + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + ObjectCreationExpression( + GenericName(TypeNames.System_Span) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList(spanElementTypeSyntax)))) + .WithArgumentList( + ArgumentList( + SeparatedList( + new []{ + Argument( + CastExpression( + spanElementTypeSyntax, + IdentifierName(nativeIdentifier))), + Argument( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(managedIdentifer), + IdentifierName("Length"))) + }))), + IdentifierName("Clear")), + ArgumentList())); + // Iterate through the elements of the array to marshal them var arraySubContext = new ArrayMarshallingCodeContext(context.CurrentStage, IndexerIdentifier, context); yield return IfStatement(BinaryExpression(SyntaxKind.NotEqualsExpression, From 5135d6d039db7d8bf709197bbf68a750aa21de2f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 23 Nov 2020 16:51:36 -0800 Subject: [PATCH 05/10] Fix pointer type native types in Spans. --- .../Marshalling/BlittableArrayMarshaller.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs index 31638278ac03..71fde5d9bbe2 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs @@ -75,6 +75,13 @@ public override IEnumerable Generate(TypePositionInfo info, Stu } yield break; } + + TypeSyntax spanElementTypeSyntax = GetElementTypeSyntax(info); + if (spanElementTypeSyntax is PointerTypeSyntax) + { + // Pointers cannot be passed to generics, so use IntPtr for this case. + spanElementTypeSyntax = ParseTypeName("System.IntPtr"); + } switch (context.CurrentStage) { @@ -116,14 +123,15 @@ public override IEnumerable Generate(TypePositionInfo info, Stu GenericName(TypeNames.System_Span) .WithTypeArgumentList( TypeArgumentList( - SingletonSeparatedList( - GetElementTypeSyntax(info))))) + SingletonSeparatedList(spanElementTypeSyntax)))) .WithArgumentList( ArgumentList( SeparatedList( new []{ Argument( - IdentifierName(nativeIdentifier)), + CastExpression( + spanElementTypeSyntax, + IdentifierName(nativeIdentifier))), Argument( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, @@ -156,13 +164,15 @@ public override IEnumerable Generate(TypePositionInfo info, Stu GenericName(Identifier(TypeNames.System_Span), TypeArgumentList( SingletonSeparatedList( - GetElementTypeSyntax(info))))) + spanElementTypeSyntax)))) .WithArgumentList( ArgumentList( SeparatedList( new[]{ Argument( - IdentifierName(nativeIdentifier)), + CastExpression( + spanElementTypeSyntax, + IdentifierName(nativeIdentifier))), Argument( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, From da466a871dd991ceba96f1498a6320aa2a3298e5 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 23 Nov 2020 17:00:15 -0800 Subject: [PATCH 06/10] Fix typo in casts. Fix path when null. Signed-off-by: Jeremy Koritzinsky --- .../Marshalling/BlittableArrayMarshaller.cs | 4 +- .../NonBlittableArrayMarshaller.cs | 54 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs index 71fde5d9bbe2..86ebc192d04f 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/BlittableArrayMarshaller.cs @@ -130,7 +130,7 @@ public override IEnumerable Generate(TypePositionInfo info, Stu new []{ Argument( CastExpression( - spanElementTypeSyntax, + PointerType(spanElementTypeSyntax), IdentifierName(nativeIdentifier))), Argument( MemberAccessExpression( @@ -171,7 +171,7 @@ public override IEnumerable Generate(TypePositionInfo info, Stu new[]{ Argument( CastExpression( - spanElementTypeSyntax, + PointerType(spanElementTypeSyntax), IdentifierName(nativeIdentifier))), Argument( MemberAccessExpression( diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs index 5e502f49644d..5ec93ec26e48 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs @@ -90,40 +90,40 @@ public override IEnumerable Generate(TypePositionInfo info, Stu spanElementTypeSyntax = ParseTypeName("System.IntPtr"); } - // new Span(, .Length).Clear(); - yield return ExpressionStatement( - InvocationExpression( - MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - ObjectCreationExpression( - GenericName(TypeNames.System_Span) - .WithTypeArgumentList( - TypeArgumentList( - SingletonSeparatedList(spanElementTypeSyntax)))) - .WithArgumentList( - ArgumentList( - SeparatedList( - new []{ - Argument( - CastExpression( - spanElementTypeSyntax, - IdentifierName(nativeIdentifier))), - Argument( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(managedIdentifer), - IdentifierName("Length"))) - }))), - IdentifierName("Clear")), - ArgumentList())); - // Iterate through the elements of the array to marshal them var arraySubContext = new ArrayMarshallingCodeContext(context.CurrentStage, IndexerIdentifier, context); yield return IfStatement(BinaryExpression(SyntaxKind.NotEqualsExpression, IdentifierName(managedIdentifer), LiteralExpression(SyntaxKind.NullLiteralExpression)), + Block( + // new Span(, .Length).Clear(); + ExpressionStatement( + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + 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"))) + }))), + IdentifierName("Clear")), + ArgumentList())), MarshallerHelpers.GetForLoop(managedIdentifer, IndexerIdentifier) .WithStatement(Block( - List(_elementMarshaller.Generate(info with { ManagedType = GetElementTypeSymbol(info) }, arraySubContext))))); + List(_elementMarshaller.Generate(info with { ManagedType = GetElementTypeSymbol(info) }, arraySubContext)))))); } break; case StubCodeContext.Stage.Unmarshal: From 1e215c26b22a88f9b6cf7a660814a2dfc9025695 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 7 Dec 2020 15:48:25 -0800 Subject: [PATCH 07/10] Clarify comment. Signed-off-by: Jeremy Koritzinsky --- .../DllImportGenerator/Marshalling/MarshallingGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs index 7e3e3b6385c2..d2dd817bd3ec 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs @@ -195,7 +195,8 @@ public static IMarshallingGenerator Create( case { MarshallingAttributeInfo: GeneratedNativeMarshallingAttributeInfo(string nativeTypeName) }: return Forwarder; - // Cases that just match on type must come after the checks for the new marshalling model. + // Cases that just match on type must come after the checks that match only on marshalling attribute info. + // The checks below do not account for generic marshalling overrides like [MarshalUsing], so those checks must come first. case { ManagedType: { SpecialType: SpecialType.System_Char } }: return CreateCharMarshaller(info, context); From a509cc1dfaee88b4747298a72551cd431b0ff823 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 9 Dec 2020 15:48:13 -0800 Subject: [PATCH 08/10] Fix DLLImport name. --- .../DllImportGenerator.IntegrationTests/ArrayTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DllImportGenerator/DllImportGenerator.IntegrationTests/ArrayTests.cs b/DllImportGenerator/DllImportGenerator.IntegrationTests/ArrayTests.cs index c381fed262c8..7a7b738e71dc 100644 --- a/DllImportGenerator/DllImportGenerator.IntegrationTests/ArrayTests.cs +++ b/DllImportGenerator/DllImportGenerator.IntegrationTests/ArrayTests.cs @@ -39,7 +39,7 @@ 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(nameof(NativeExportsNE), EntryPoint = "and_all_members")] + [GeneratedDllImport(NativeExportsNE_Binary, EntryPoint = "and_all_members")] [return:MarshalAs(UnmanagedType.U1)] public static partial bool AndAllMembers(BoolStruct[] pArray, int length); } From 1514daa99503fd6cd135e5927db0225666ceb49f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 9 Dec 2020 16:59:08 -0800 Subject: [PATCH 09/10] Use local in marshal loop. --- .../Marshalling/NonBlittableArrayMarshaller.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs index 081a9a770d7a..95b7fa170766 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/NonBlittableArrayMarshaller.cs @@ -133,7 +133,7 @@ public override IEnumerable Generate(TypePositionInfo info, Stu }))), IdentifierName("Clear")), ArgumentList())), - MarshallerHelpers.GetForLoop(managedIdentifer, IndexerIdentifier) + MarshallerHelpers.GetForLoop(managedLocal, IndexerIdentifier) .WithStatement(Block( List(_elementMarshaller.Generate(info with { ManagedType = GetElementTypeSymbol(info) }, arraySubContext)))))); } From f0d66c67640388db7da10aeb152b409a6ff17ec0 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 9 Dec 2020 17:27:19 -0800 Subject: [PATCH 10/10] Update compat doc. --- DllImportGenerator/designs/Compatibility.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DllImportGenerator/designs/Compatibility.md b/DllImportGenerator/designs/Compatibility.md index cea5adb2d918..7579d77fff19 100644 --- a/DllImportGenerator/designs/Compatibility.md +++ b/DllImportGenerator/designs/Compatibility.md @@ -54,6 +54,8 @@ Specifying array-specific marshalling members on the `MarshalAsAttribute` such a Only single-dimensional arrays are supported for source generated marshalling. +Jagged arrays (arrays of arrays) are technically unsupported as was the case in the built-in marshalling system, but currently are not explicitly blocked by the source generator since they are not blocked at an architectural level, which was the case in the built-in system. + In the source-generated marshalling, arrays will be allocated on the stack (instead of through [`AllocCoTaskMem`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshal.alloccotaskmem)) if they are passed by value or by read-only reference if they contain at most 256 bytes of data. The built-in system does not support this optimization for arrays. ### `LCIDConversion` support