Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support arrays of types with simple custom marshalling #379

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using SharedTypes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -37,6 +38,10 @@ 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 = "and_all_members")]
[return:MarshalAs(UnmanagedType.U1)]
public static partial bool AndAllMembers(BoolStruct[] pArray, int length);
}
}

Expand Down Expand Up @@ -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)
Expand Down
40 changes: 40 additions & 0 deletions DllImportGenerator/DllImportGenerator.UnitTests/CodeSnippets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}
";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ public static IEnumerable<object[]> 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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ public static IEnumerable<object[]> CodeSnippetsToCompile()
yield return new[] { CodeSnippets.CustomStructMarshallingPinnableParametersAndModifiers };
yield return new[] { CodeSnippets.CustomStructMarshallingNativeTypePinnable };
yield return new[] { CodeSnippets.CustomStructMarshallingMarshalUsingParametersAndModifiers };
yield return new[] { CodeSnippets.ArrayMarshallingWithCustomStructElement };
}

[Theory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ internal sealed class ArrayMarshallingCodeContext : StubCodeContext

public override bool StackSpaceUsable => false;

/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public override bool CanUseAdditionalTemporaryState => false;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ public override IEnumerable<StatementSyntax> 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)
{
Expand Down Expand Up @@ -116,14 +123,15 @@ public override IEnumerable<StatementSyntax> Generate(TypePositionInfo info, Stu
GenericName(TypeNames.System_Span)
.WithTypeArgumentList(
TypeArgumentList(
SingletonSeparatedList(
GetElementTypeSyntax(info)))))
SingletonSeparatedList(spanElementTypeSyntax))))
.WithArgumentList(
ArgumentList(
SeparatedList(
new []{
Argument(
IdentifierName(nativeIdentifier)),
CastExpression(
PointerType(spanElementTypeSyntax),
IdentifierName(nativeIdentifier))),
Argument(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
Expand Down Expand Up @@ -156,13 +164,15 @@ public override IEnumerable<StatementSyntax> Generate(TypePositionInfo info, Stu
GenericName(Identifier(TypeNames.System_Span),
TypeArgumentList(
SingletonSeparatedList(
GetElementTypeSyntax(info)))))
spanElementTypeSyntax))))
.WithArgumentList(
ArgumentList(
SeparatedList(
new[]{
Argument(
IdentifierName(nativeIdentifier)),
CastExpression(
PointerType(spanElementTypeSyntax),
IdentifierName(nativeIdentifier))),
Argument(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,16 +182,6 @@ 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: ArrayMarshalAsInfo marshalAsInfo }:
if (marshalAsInfo.UnmanagedArrayType != UnmanagedArrayType.LPArray)
{
throw new MarshallingNotSupportedException(info, context);
}
return CreateArrayMarshaller(info, context, elementType, marshalAsInfo.CreateArraySubTypeMarshalAsInfo());

// 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.
Expand All @@ -205,11 +195,16 @@ public static IMarshallingGenerator Create(
case { MarshallingAttributeInfo: GeneratedNativeMarshallingAttributeInfo(string nativeTypeName) }:
return Forwarder;

// 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);

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;
Expand Down Expand Up @@ -362,9 +357,17 @@ 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 elementMarshaller = Create(TypePositionInfo.CreateForType(elementType, elementMarshallingInfo), context);
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, false));
ExpressionSyntax numElementsExpression = LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0));
if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In))
{
Expand All @@ -379,6 +382,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)
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
{
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,47 @@ public override IEnumerable<StatementSyntax> 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");
}

// 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)),
Block(
// new Span<T>(<nativeIdentifier>, <managedIdentifier>.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(managedLocal, 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:
Expand Down
22 changes: 9 additions & 13 deletions DllImportGenerator/DllImportGenerator/MarshallingAttributeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,11 @@ enum UnmanagedArrayType
/// </summary>
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;
}

Expand Down Expand Up @@ -113,5 +104,10 @@ internal sealed record GeneratedNativeMarshallingAttributeInfo(
/// The type of the element is a SafeHandle-derived type with no marshalling attributes.
/// </summary>
internal sealed record SafeHandleMarshallingInfo : MarshallingInfo;



/// <summary>
/// Default marshalling for arrays
/// </summary>
internal sealed record ArrayMarshallingInfo(MarshallingInfo ElementMarshallingInfo) : MarshallingInfo;
}
9 changes: 9 additions & 0 deletions DllImportGenerator/DllImportGenerator/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions DllImportGenerator/DllImportGenerator/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,9 @@
<data name="TypeNotSupportedTitle" xml:space="preserve">
<value>Specified type is not supported by source-generated P/Invokes</value>
</data>
<data name="ValuePropertyMarshallingRequiresAdditionalState" xml:space="preserve">
<value>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.</value>
</data>
<data name="ValuePropertyMustHaveGetterDescription" xml:space="preserve">
<value>The native type's 'Value' property must have a getter to support marshalling from managed to native.</value>
</data>
Expand Down
Loading