Skip to content

Commit

Permalink
[DllImportGenerator] Initialize by-value out array to default (#61431)
Browse files Browse the repository at this point in the history
  • Loading branch information
elinor-fung authored Nov 10, 2021
1 parent d15f391 commit 4f43e82
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 6 deletions.
2 changes: 2 additions & 0 deletions docs/design/libraries/DllImportGenerator/Compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ Only single-dimensional arrays are supported for source generated marshalling.

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.

In the built-in system, marshalling a `char` array by value with `CharSet.Unicode` would default to also marshalling data out. In the source-generated marshalling, the `char` array must be marked with the `[Out]` attribute for data to be marshalled out by value.

### `in` keyword

For some types - blittable or Unicode `char` - passed by read-only reference via the [`in` keyword](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/in-parameter-modifier), the built-in system simply pins the parameter. The generated marshalling code does the same, such that there is no behavioural difference. A consequence of this behaviour is that any modifications made by the invoked function will be reflected in the caller. It is left to the user to avoid the situation in which `in` is used for a parameter that will actually be modified by the invoked function.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,18 @@ public IEnumerable<StatementSyntax> GenerateMarshalStatements(TypePositionInfo i
if (!info.IsByRef && info.ByValueContentsMarshalKind == ByValueContentsMarshalKind.Out)
{
// If the parameter is marshalled by-value [Out], then we don't marshal the contents of the collection.
// We do clear the span, so that if the invoke target doesn't fill it, we aren't left with undefined content.
// <nativeIdentifier>.NativeValueStorage.Clear();
string nativeIdentifier = context.GetIdentifiers(info).native;
yield return ExpressionStatement(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nativeIdentifier),
IdentifierName(ManualTypeMarshallingHelper.NativeValueStoragePropertyName)),
IdentifierName("Clear"))));
yield break;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ public partial class Arrays
[GeneratedDllImport(NativeExportsNE_Binary, EntryPoint = "sum_char_array", CharSet = CharSet.Unicode)]
public static partial int SumChars(char[] chars, int numElements);

[GeneratedDllImport(NativeExportsNE_Binary, EntryPoint = "fill_char_array", CharSet = CharSet.Unicode)]
public static partial void FillChars([Out] char[] chars, int length, ushort start);

[GeneratedDllImport(NativeExportsNE_Binary, EntryPoint = "reverse_char_array", CharSet = CharSet.Unicode)]
public static partial void ReverseChars([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] ref char[] chars, int numElements);

Expand Down Expand Up @@ -253,12 +256,34 @@ public void DynamicSizedArrayWithConstantComponent()
[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));
{
var testArray = new IntStructWrapper[10];
int start = 5;

NativeExportsNE.Arrays.FillRangeArray(testArray, testArray.Length, start);
Assert.Equal(Enumerable.Range(start, testArray.Length), testArray.Select(wrapper => wrapper.Value));

// Any items not populated by the invoke target should be initialized to default
testArray = new IntStructWrapper[10];
int lengthToFill = testArray.Length / 2;
NativeExportsNE.Arrays.FillRangeArray(testArray, lengthToFill, start);
Assert.Equal(Enumerable.Range(start, lengthToFill), testArray[..lengthToFill].Select(wrapper => wrapper.Value));
Assert.All(testArray[lengthToFill..], wrapper => Assert.Equal(0, wrapper.Value));
}
{
var testArray = new char[10];
ushort start = 65;

NativeExportsNE.Arrays.FillChars(testArray, testArray.Length, start);
Assert.Equal(Enumerable.Range(start, testArray.Length), testArray.Select(c => (int)c));

// Any items not populated by the invoke target should be initialized to default
testArray = new char[10];
int lengthToFill = testArray.Length / 2;
NativeExportsNE.Arrays.FillChars(testArray, lengthToFill, start);
Assert.Equal(Enumerable.Range(start, lengthToFill), testArray[..lengthToFill].Select(c => (int)c));
Assert.All(testArray[lengthToFill..], c => Assert.Equal(0, c));
}
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ public static int SumChars(ushort* values, int numValues)
return sum;
}

[UnmanagedCallersOnly(EntryPoint = "fill_char_array")]
public static void FillChars(ushort* values, int length, ushort start)
{
if (values == null)
{
return;
}

ushort val = start;
for (int i = 0; i < length; i++)
{
values[i] = val++;
}
}

[UnmanagedCallersOnly(EntryPoint = "reverse_char_array")]
public static void ReverseChars(ushort** values, int numValues)
{
Expand Down

0 comments on commit 4f43e82

Please sign in to comment.