Skip to content
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
4 changes: 4 additions & 0 deletions docs/design/libraries/DllImportGenerator/Compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ Jagged arrays (arrays of arrays) are technically unsupported as was the case in

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

### `LCIDConversion` support

[`LCIDConversionAttribute`](`https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.lcidconversionattribute`) will not be supported for methods marked with `GeneratedDllImportAttribute`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,29 @@ public Utf16CharMarshaller()

public ArgumentSyntax AsArgument(TypePositionInfo info, StubCodeContext context)
{
string identifier = context.GetIdentifiers(info).native;
if (info.IsByRef)
(string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(info);
if (!info.IsByRef)
{
// (ushort)<managedIdentifier>
return Argument(
CastExpression(
AsNativeType(info),
IdentifierName(managedIdentifier)));
}
else if (IsPinningPathSupported(info, context))
{
// (ushort*)<pinned>
return Argument(
PrefixUnaryExpression(
SyntaxKind.AddressOfExpression,
IdentifierName(identifier)));
CastExpression(
PointerType(AsNativeType(info)),
IdentifierName(PinnedIdentifier(info.InstanceIdentifier))));
}

return Argument(IdentifierName(identifier));
// &<nativeIdentifier>
return Argument(
PrefixUnaryExpression(
SyntaxKind.AddressOfExpression,
IdentifierName(nativeIdentifier)));
}

public TypeSyntax AsNativeType(TypePositionInfo info)
Expand All @@ -52,12 +65,36 @@ public ParameterSyntax AsParameter(TypePositionInfo info)
public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeContext context)
{
(string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(info);

if (IsPinningPathSupported(info, context))
{
if (context.CurrentStage == StubCodeContext.Stage.Pin)
{
// fixed (char* <pinned> = &<managed>)
yield return FixedStatement(
VariableDeclaration(
PointerType(PredefinedType(Token(SyntaxKind.CharKeyword))),
SingletonSeparatedList(
VariableDeclarator(Identifier(PinnedIdentifier(info.InstanceIdentifier)))
.WithInitializer(EqualsValueClause(
PrefixUnaryExpression(
SyntaxKind.AddressOfExpression,
IdentifierName(Identifier(managedIdentifier)))
))
)
),
EmptyStatement()
);
}
yield break;
}

switch (context.CurrentStage)
{
case StubCodeContext.Stage.Setup:
break;
case StubCodeContext.Stage.Marshal:
if (info.RefKind != RefKind.Out)
if (info.IsByRef && info.RefKind != RefKind.Out)
{
yield return ExpressionStatement(
AssignmentExpression(
Expand Down Expand Up @@ -86,8 +123,20 @@ public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeCont
}
}

public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true;
public bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context)
{
return info.IsManagedReturnPosition || (info.IsByRef && !context.SingleFrameSpansNativeContext);
}

public bool SupportsByValueMarshalKind(ByValueContentsMarshalKind marshalKind, StubCodeContext context) => false;

private bool IsPinningPathSupported(TypePositionInfo info, StubCodeContext context)
{
return context.SingleFrameSpansNativeContext
&& !info.IsManagedReturnPosition
&& info.IsByRef;
}

private static string PinnedIdentifier(string identifier) => $"{identifier}__pinned";
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

using Xunit;
Expand Down Expand Up @@ -32,6 +34,9 @@ partial class NativeExportsNE
[GeneratedDllImport(NativeExportsNE_Binary, EntryPoint = "char_return_as_uint", CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.I2)]
public static partial char ReturnI2AsI2IgnoreCharSet([MarshalAs(UnmanagedType.I2)] char input);

[GeneratedDllImport(NativeExportsNE_Binary, EntryPoint = "char_reverse_buffer_ref", CharSet = CharSet.Unicode)]
public static partial void ReverseBuffer(ref char buffer, int len);
}

public class CharacterTests
Expand Down Expand Up @@ -70,7 +75,7 @@ public void ValidateUnicodeReturns(char expected, uint value)

result = initial;
NativeExportsNE.ReturnUIntAsUnicode_In(value, in result);
Assert.Equal(initial, result); // Should not be updated when using 'in'
Assert.Equal(expected, result); // Value is updated even when passed with 'in' keyword (matches built-in system)
}

[Theory]
Expand All @@ -81,5 +86,17 @@ public void ValidateIgnoreCharSet(char value, uint expectedUInt)
Assert.Equal(expected, NativeExportsNE.ReturnU2AsU2IgnoreCharSet(value));
Assert.Equal(expected, NativeExportsNE.ReturnI2AsI2IgnoreCharSet(value));
}

[Fact]
public void ValidateRefCharAsBuffer()
{
char[] chars = CharacterMappings().Select(o => (char)o[0]).ToArray();
char[] expected = new char[chars.Length];
Array.Copy(chars, expected, chars.Length);
Array.Reverse(expected);

NativeExportsNE.ReverseBuffer(ref MemoryMarshal.GetArrayDataReference(chars), chars.Length);
Assert.Equal(expected, chars);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

namespace NativeExports
Expand All @@ -24,5 +25,12 @@ public static void ReturnUIntAsRefUInt(uint input, uint* res)
{
*res = input;
}

[UnmanagedCallersOnly(EntryPoint = "char_reverse_buffer_ref")]
public static void ReverseBuffer(ushort *buffer, int len)
{
var span = new Span<ushort>(buffer, len);
span.Reverse();
}
}
}