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

[DllImportGenerator] Initialize by-value out array to default #61431

Merged
merged 2 commits into from
Nov 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is due to the fact that a char array with CharSet.Unicode used to be pinnacle as char is blittable with CharSet.Unicode

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I figured that was it once I saw the behaviour, but it was not something I realized immediately 😕


### `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