Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ private static MemberDeclarationSyntax PrintForwarderStub(ContainingSyntax userD
.AddAttributeLists(
AttributeList(
SingletonSeparatedList(
CreateForwarderDllImport(pinvokeData))));
CreateDllImportAttribute(pinvokeData))));

MemberDeclarationSyntax toPrint = stub.ContainingSyntaxContext.WrapMemberInContainingSyntaxWithUnsafeModifier(stubMethod);

Expand All @@ -438,37 +438,24 @@ private static LocalFunctionStatementSyntax CreateTargetDllImportAsLocalStatemen
{
Debug.Assert(!options.GenerateForwarders, "GenerateForwarders should have already been handled to use a forwarder stub");

// StringMarshalling.Utf16 is the only value that has a supported analogue in DllImportAttribute.CharSet. If it's not Utf16, consider StringMarshalling unset
Copy link
Member

Choose a reason for hiding this comment

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

Unsure if this is worth quibbling about, but CharSet.Ansi on non-Windows is Utf8 if I recall correctly. Based on certain reg keys on Windows it can also be Utf8. I don't know if this makes a difference in this case, but it is something to note.

Copy link
Member

Choose a reason for hiding this comment

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

If it's not Utf16, consider StringMarshalling unset

Shouldn't it have errored in this case? I know we have a test around down-level forwarding of StringMarshalling.Utf8 or StringMarshalling.Custom.

public async Task StringMarshallingForwardingNotSupported_ReportsDiagnostic()

Or am I missing something about dotnet/runtime-only down-level versus down-level support?

Copy link
Member Author

Choose a reason for hiding this comment

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

It should error in down-level forwarding of UTF-8 and StringMarshalling.Custom, but this code path is taken in the normal path of LibraryImport in net7+, so we don't want to unconditionally warn here. Ideally this should warn when we hit this path for down-level support with partial marshalling, but we don't right now either.

var stringMarshallingMask = libraryImportData.StringMarshalling == StringMarshalling.Utf16 ? InteropAttributeMember.All : ~InteropAttributeMember.StringMarshalling;
var dllImportData = libraryImportData with
{
// If setLastError was set in LibraryImport, we will call the Marshal API to handle that, the DllImport should not.
IsUserDefined = libraryImportData.IsUserDefined & ~InteropAttributeMember.SetLastError & stringMarshallingMask,
EntryPoint = libraryImportData.EntryPoint ?? stubMethodName
};

(ParameterListSyntax parameterList, TypeSyntax returnType, AttributeListSyntax returnTypeAttributes) = stubGenerator.GenerateTargetMethodSignatureData();
LocalFunctionStatementSyntax localDllImport = LocalFunctionStatement(returnType, stubTargetName)
.AddModifiers(
Token(SyntaxKind.StaticKeyword),
Token(SyntaxKind.ExternKeyword),
Token(SyntaxKind.UnsafeKeyword))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
.WithAttributeLists(
SingletonList(AttributeList(
SingletonSeparatedList(
Attribute(
NameSyntaxes.DllImportAttribute,
AttributeArgumentList(
SeparatedList(
new[]
{
AttributeArgument(LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal(libraryImportData.ModuleName))),
AttributeArgument(
NameEquals(nameof(DllImportAttribute.EntryPoint)),
null,
LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal(libraryImportData.EntryPoint ?? stubMethodName))),
AttributeArgument(
NameEquals(nameof(DllImportAttribute.ExactSpelling)),
null,
LiteralExpression(SyntaxKind.TrueLiteralExpression))
}
)))))))
.WithAttributeLists(SingletonList(AttributeList(SingletonSeparatedList(
CreateDllImportAttribute(dllImportData)))))
.WithParameterList(parameterList);
if (returnTypeAttributes is not null)
{
Expand All @@ -477,7 +464,7 @@ private static LocalFunctionStatementSyntax CreateTargetDllImportAsLocalStatemen
return localDllImport;
}

private static AttributeSyntax CreateForwarderDllImport(LibraryImportData target)
private static AttributeSyntax CreateDllImportAttribute(LibraryImportData target)
{
var newAttributeArgs = new List<AttributeArgumentSyntax>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,21 @@ partial class Test
public static string BasicParametersAndModifiersWithStringMarshalling<T>(StringMarshalling value, string preDeclaration = "") =>
BasicParametersAndModifiersWithStringMarshalling(typeof(T).ToString(), value, preDeclaration);

/// <summary>
/// Declaration with parameters with <see cref="StringMarshalling"/> set.
/// </summary>
public static string BasicParameterWithStringMarshalling(string returnType, string typeName1, string typeName2, StringMarshalling value, string preDeclaration = "") => $$"""
using System.Runtime.InteropServices;
{{preDeclaration}}
partial class Test
{
[{|#0:LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.{{value}})|}]
public static partial {{returnType}} {|#1:Method|}(
{{typeName1}} {|#2:p|},
{{typeName2}} {|#4:p2|});
}
""";

/// <summary>
/// Declaration with parameters with <see cref="StringMarshallingCustomType"/> set.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,48 @@ public static IEnumerable<object[]> CodeSnippetsToValidateFallbackForwarder()
}
}

public static IEnumerable<object[]> CodeSnippetsToValidateCharSetPresent()
{
// Confirm that Charset=... is present
{
string code = CodeSnippets.BasicParametersAndModifiersWithStringMarshalling(
"System.Text.StringBuilder",
StringMarshalling.Utf16,
CodeSnippets.LibraryImportAttributeDeclaration);
yield return new object[] { ID(), code, TestTargetFramework.Net6 };
yield return new object[] { ID(), code, TestTargetFramework.Core };
yield return new object[] { ID(), code, TestTargetFramework.Standard };
yield return new object[] { ID(), code, TestTargetFramework.Framework };
}
{
string code = CodeSnippets.BasicParameterWithStringMarshalling(
"int",
"System.Text.StringBuilder",
"Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid",
StringMarshalling.Utf16,
CodeSnippets.LibraryImportAttributeDeclaration);
yield return new object[] { ID(), code, TestTargetFramework.Net6 };
yield return new object[] { ID(), code, TestTargetFramework.Core };
yield return new object[] { ID(), code, TestTargetFramework.Standard };
yield return new object[] { ID(), code, TestTargetFramework.Framework };
}
}

[Theory]
[MemberData(nameof(CodeSnippetsToValidateCharSetPresent))]
[OuterLoop("Uses the network for downlevel ref packs")]
public async Task ValidateSnippetsCharSetPresent(string id, string source, TestTargetFramework targetFramework)
{
TestUtils.Use(id);
var test = new CharSetIsForwardedTest(targetFramework)
{
TestCode = source,
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
};

await test.RunAsync();
}

[Theory]
[MemberData(nameof(CodeSnippetsToValidateFallbackForwarder))]
[OuterLoop("Uses the network for downlevel ref packs")]
Expand All @@ -546,6 +588,25 @@ public async Task ValidateSnippetsFallbackForwarder(string id, string source, Te
await test.RunAsync();
}

class CharSetIsForwardedTest : VerifyCS.Test
{
public CharSetIsForwardedTest(TestTargetFramework targetFramework)
: base(targetFramework)
{
}

protected override void VerifyFinalCompilation(Compilation compilation)
{
SyntaxTree generatedCode = compilation.SyntaxTrees.Last();
SemanticModel model = compilation.GetSemanticModel(generatedCode);
var hasCharSet = generatedCode.GetRoot()
.DescendantNodes().OfType<AttributeSyntax>()
.Where(att => att.Name.ToString().EndsWith(nameof(DllImportAttribute)))
.All(att => att.ArgumentList?.Arguments.Any(a => a.NameEquals?.Name.ToString().StartsWith(nameof(DllImportAttribute.CharSet)) == true) == true);
Assert.True(hasCharSet);
}
}

class FallbackForwarderTest : VerifyCS.Test
{
private readonly bool _expectFallbackForwarder;
Expand Down Expand Up @@ -724,7 +785,7 @@ public class Basic { }
await test.RunAsync();
}


Copy link
Member

Choose a reason for hiding this comment

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

Suggested change

[OuterLoop("Uses the network for downlevel ref packs")]
[InlineData(TestTargetFramework.Standard)]
[InlineData(TestTargetFramework.Framework)]
Expand Down