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(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);
}
}

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 };
}
";
}
}
2 changes: 2 additions & 0 deletions DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,13 @@ public static IEnumerable<object[]> 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<object[]> CodeSnippetsToCompile_WithDiagnostics()
{
yield return new[] { CodeSnippets.AllSupportedDllImportNamedArguments };
yield return new[] { CodeSnippets.ArrayMarshallingWithCustomStructElementWithValueProperty };
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
}

[Theory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) }:
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -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))
{
Expand All @@ -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)
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
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 @@ -264,6 +264,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
40 changes: 30 additions & 10 deletions DllImportGenerator/DllImportGenerator/TypePositionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down Expand Up @@ -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;
}
Expand All @@ -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
Expand Down Expand Up @@ -208,15 +208,29 @@ static MarshalAsInfo CreateMarshalAsInfo(AttributeData attrData, DefaultMarshall
}
}

return isArrayType
? new ArrayMarshalAsInfo(
if (isArrayType)
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
{
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<AttributeData>(), 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)
Expand Down Expand Up @@ -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
Expand All @@ -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<AttributeData>(), defaultInfo, compilation, diagnostics));
return true;
}
marshallingInfo = NoMarshallingInfo.Instance;
return false;
}
Expand Down
14 changes: 14 additions & 0 deletions DllImportGenerator/TestAssets/NativeExports/Arrays.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using SharedTypes;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -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;")]
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
public static byte AndAllMembers([DNNE.C99Type("struct bool_struct*")] BoolStructNative* pArray, int length)
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
{
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;
}
}
}