Skip to content

Commit c32b1f5

Browse files
authored
Add StringMarshallingCustomType to GeneratedDllImportAttribute (#65855)
1 parent 1857d04 commit c32b1f5

File tree

18 files changed

+472
-65
lines changed

18 files changed

+472
-65
lines changed

src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
<TargetFramework>netstandard2.0</TargetFramework>
66
<EnableDllImportGenerator>true</EnableDllImportGenerator>
77
<!--
8-
DLLIMPORTGEN003: DllImportGenerator Target Framework Not Supported.
8+
DLLIMPORTGEN004: DllImportGenerator Target Framework Not Supported.
99
-->
10-
<NoWarn>$(NoWarn);DLLIMPORTGEN003</NoWarn>
10+
<NoWarn>$(NoWarn);DLLIMPORTGEN004</NoWarn>
1111
</PropertyGroup>
1212
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
1313
<DefineConstants>FEATURE_GC_STRESS;$(DefineConstants)</DefineConstants>

src/libraries/Common/src/System/Runtime/InteropServices/GeneratedDllImportAttribute.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ sealed class GeneratedDllImportAttribute : Attribute
2222
public string? EntryPoint { get; set; }
2323
public bool SetLastError { get; set; }
2424
public StringMarshalling StringMarshalling { get; set; }
25+
public Type? StringMarshallingCustomType { get; set; }
2526

2627
public GeneratedDllImportAttribute(string dllName)
2728
{

src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.cs

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ private static TargetFramework DetermineTargetFramework(Compilation compilation,
357357
bool setLastError = false;
358358

359359
StringMarshalling stringMarshalling = StringMarshalling.Custom;
360+
INamedTypeSymbol? stringMarshallingCustomType = null;
360361

361362
// All other data on attribute is defined as NamedArguments.
362363
foreach (KeyValuePair<string, TypedConstant> namedArg in attrData.NamedArguments)
@@ -369,16 +370,6 @@ private static TargetFramework DetermineTargetFramework(Compilation compilation,
369370
// Return null here to indicate invalid attribute data.
370371
Debug.WriteLine($"An unknown member '{namedArg.Key}' was found on {attrData.AttributeClass}");
371372
return null;
372-
case nameof(GeneratedDllImportData.StringMarshalling):
373-
userDefinedValues |= DllImportMember.StringMarshalling;
374-
// TypedConstant's Value property only contains primitive values.
375-
if (namedArg.Value.Value is not int)
376-
{
377-
return null;
378-
}
379-
// A boxed primitive can be unboxed to an enum with the same underlying type.
380-
stringMarshalling = (StringMarshalling)namedArg.Value.Value!;
381-
break;
382373
case nameof(GeneratedDllImportData.EntryPoint):
383374
userDefinedValues |= DllImportMember.EntryPoint;
384375
if (namedArg.Value.Value is not string)
@@ -395,6 +386,24 @@ private static TargetFramework DetermineTargetFramework(Compilation compilation,
395386
}
396387
setLastError = (bool)namedArg.Value.Value!;
397388
break;
389+
case nameof(GeneratedDllImportData.StringMarshalling):
390+
userDefinedValues |= DllImportMember.StringMarshalling;
391+
// TypedConstant's Value property only contains primitive values.
392+
if (namedArg.Value.Value is not int)
393+
{
394+
return null;
395+
}
396+
// A boxed primitive can be unboxed to an enum with the same underlying type.
397+
stringMarshalling = (StringMarshalling)namedArg.Value.Value!;
398+
break;
399+
case nameof(GeneratedDllImportData.StringMarshallingCustomType):
400+
userDefinedValues |= DllImportMember.StringMarshallingCustomType;
401+
if (namedArg.Value.Value is not INamedTypeSymbol)
402+
{
403+
return null;
404+
}
405+
stringMarshallingCustomType = (INamedTypeSymbol)namedArg.Value.Value;
406+
break;
398407
}
399408
}
400409

@@ -406,9 +415,10 @@ private static TargetFramework DetermineTargetFramework(Compilation compilation,
406415
return new GeneratedDllImportData(attrData.ConstructorArguments[0].Value!.ToString())
407416
{
408417
IsUserDefined = userDefinedValues,
409-
StringMarshalling = stringMarshalling,
410418
EntryPoint = entryPoint,
411419
SetLastError = setLastError,
420+
StringMarshalling = stringMarshalling,
421+
StringMarshallingCustomType = stringMarshallingCustomType,
412422
};
413423
}
414424

@@ -462,6 +472,23 @@ private static IncrementalStubGenerationContext CalculateStubInformation(IMethod
462472
stubDllImportData = new GeneratedDllImportData("INVALID_CSHARP_SYNTAX");
463473
}
464474

475+
if (stubDllImportData.IsUserDefined.HasFlag(DllImportMember.StringMarshalling))
476+
{
477+
// User specified StringMarshalling.Custom without specifying StringMarshallingCustomType
478+
if (stubDllImportData.StringMarshalling == StringMarshalling.Custom && stubDllImportData.StringMarshallingCustomType is null)
479+
{
480+
generatorDiagnostics.ReportInvalidStringMarshallingConfiguration(
481+
generatedDllImportAttr, symbol.Name, Resources.InvalidStringMarshallingConfigurationMissingCustomType);
482+
}
483+
484+
// User specified something other than StringMarshalling.Custom while specifying StringMarshallingCustomType
485+
if (stubDllImportData.StringMarshalling != StringMarshalling.Custom && stubDllImportData.StringMarshallingCustomType is not null)
486+
{
487+
generatorDiagnostics.ReportInvalidStringMarshallingConfiguration(
488+
generatedDllImportAttr, symbol.Name, Resources.InvalidStringMarshallingConfigurationNotCustom);
489+
}
490+
}
491+
465492
if (lcidConversionAttr is not null)
466493
{
467494
// Using LCIDConversion with GeneratedDllImport is not supported
@@ -543,13 +570,22 @@ private MemberDeclarationSyntax PrintForwarderStub(MethodDeclarationSyntax userD
543570
&& targetDllImportData.StringMarshalling != StringMarshalling.Utf16)
544571
{
545572
diagnostics.ReportCannotForwardToDllImport(
573+
userDeclaredMethod,
546574
$"{nameof(TypeNames.GeneratedDllImportAttribute)}{Type.Delimiter}{nameof(StringMarshalling)}",
547-
$"{nameof(StringMarshalling)}{Type.Delimiter}{targetDllImportData.StringMarshalling}",
548-
userDeclaredMethod);
575+
$"{nameof(StringMarshalling)}{Type.Delimiter}{targetDllImportData.StringMarshalling}");
549576

550577
targetDllImportData = targetDllImportData with { IsUserDefined = targetDllImportData.IsUserDefined & ~DllImportMember.StringMarshalling };
551578
}
552579

580+
if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.StringMarshallingCustomType))
581+
{
582+
diagnostics.ReportCannotForwardToDllImport(
583+
userDeclaredMethod,
584+
$"{nameof(TypeNames.GeneratedDllImportAttribute)}{Type.Delimiter}{nameof(DllImportMember.StringMarshallingCustomType)}");
585+
586+
targetDllImportData = targetDllImportData with { IsUserDefined = targetDllImportData.IsUserDefined & ~DllImportMember.StringMarshallingCustomType };
587+
}
588+
553589
SyntaxTokenList modifiers = StripTriviaFromModifiers(userDeclaredMethod.Modifiers);
554590
modifiers = AddToModifiers(modifiers, SyntaxKind.ExternKeyword);
555591
// Create stub function

src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportStubContext.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,12 @@ private static (ImmutableArray<TypePositionInfo>, IMarshallingGeneratorFactory)
163163
_ => CharEncoding.Undefined, // [Compat] Do not assume a specific value
164164
};
165165
}
166+
else if (dllImportData.IsUserDefined.HasFlag(DllImportMember.StringMarshallingCustomType))
167+
{
168+
defaultEncoding = CharEncoding.Custom;
169+
}
166170

167-
var defaultInfo = new DefaultMarshallingInfo(defaultEncoding);
171+
var defaultInfo = new DefaultMarshallingInfo(defaultEncoding, dllImportData.StringMarshallingCustomType);
168172

169173
var marshallingAttributeParser = new MarshallingAttributeInfoParser(env.Compilation, diagnostics, defaultInfo, method);
170174

src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/GeneratedDllImportData.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Runtime.InteropServices;
6+
using Microsoft.CodeAnalysis;
67

78
namespace Microsoft.Interop
89
{
@@ -16,6 +17,7 @@ public enum DllImportMember
1617
EntryPoint = 1 << 0,
1718
SetLastError = 1 << 1,
1819
StringMarshalling = 1 << 2,
20+
StringMarshallingCustomType = 1 << 3,
1921
All = ~None
2022
}
2123

@@ -32,8 +34,9 @@ internal sealed record GeneratedDllImportData(string ModuleName)
3234
/// Value set by the user on the original declaration.
3335
/// </summary>
3436
public DllImportMember IsUserDefined { get; init; }
35-
public StringMarshalling StringMarshalling { get; init; }
3637
public string? EntryPoint { get; init; }
3738
public bool SetLastError { get; init; }
39+
public StringMarshalling StringMarshalling { get; init; }
40+
public INamedTypeSymbol? StringMarshallingCustomType { get; init; }
3841
}
3942
}

src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/GeneratorDiagnostics.cs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,25 @@ public class GeneratorDiagnostics : IGeneratorDiagnostics
1919
public class Ids
2020
{
2121
public const string Prefix = "DLLIMPORTGEN";
22-
public const string TypeNotSupported = Prefix + "001";
23-
public const string ConfigurationNotSupported = Prefix + "002";
24-
public const string TargetFrameworkNotSupported = Prefix + "003";
25-
public const string CannotForwardToDllImport = Prefix + "004";
22+
public const string InvalidGeneratedDllImportAttributeUsage = Prefix + "001";
23+
public const string TypeNotSupported = Prefix + "002";
24+
public const string ConfigurationNotSupported = Prefix + "003";
25+
public const string TargetFrameworkNotSupported = Prefix + "004";
26+
public const string CannotForwardToDllImport = Prefix + "005";
2627
}
2728

2829
private const string Category = "SourceGeneration";
2930

31+
public static readonly DiagnosticDescriptor InvalidStringMarshallingConfiguration =
32+
new DiagnosticDescriptor(
33+
Ids.InvalidGeneratedDllImportAttributeUsage,
34+
GetResourceString(nameof(Resources.InvalidLibraryImportAttributeUsageTitle)),
35+
GetResourceString(nameof(Resources.InvalidStringMarshallingConfigurationMessage)),
36+
Category,
37+
DiagnosticSeverity.Error,
38+
isEnabledByDefault: true,
39+
description: GetResourceString(nameof(Resources.InvalidStringMarshallingConfigurationDescription)));
40+
3041
public static readonly DiagnosticDescriptor ParameterTypeNotSupported =
3142
new DiagnosticDescriptor(
3243
Ids.TypeNotSupported,
@@ -141,6 +152,24 @@ public class Ids
141152

142153
public IEnumerable<Diagnostic> Diagnostics => _diagnostics;
143154

155+
/// <summary>
156+
/// Report diagnostic for invalid configuration for string marshalling.
157+
/// </summary>
158+
/// <param name="attributeData">Attribute specifying the invalid configuration</param>
159+
/// <param name="methodName">Name of the method</param>
160+
/// <param name="detailsMessage">Specific reason the configuration is invalid</param>
161+
public void ReportInvalidStringMarshallingConfiguration(
162+
AttributeData attributeData,
163+
string methodName,
164+
string detailsMessage)
165+
{
166+
_diagnostics.Add(
167+
attributeData.CreateDiagnostic(
168+
GeneratorDiagnostics.InvalidStringMarshallingConfiguration,
169+
methodName,
170+
detailsMessage));
171+
}
172+
144173
/// <summary>
145174
/// Report diagnostic for configuration that is not supported by the DLL import source generator
146175
/// </summary>
@@ -287,17 +316,16 @@ public void ReportTargetFrameworkNotSupported(Version minimumSupportedVersion)
287316
/// <summary>
288317
/// Report diagnostic for configuration that cannot be forwarded to <see cref="DllImportAttribute" />
289318
/// </summary>
319+
/// <param name="method">Method with the configuration that cannot be forwarded</param>
290320
/// <param name="name">Configuration name</param>
291321
/// <param name="value">Configuration value</param>
292-
/// <param name="method">Method with the arguments that cannot be forwarded</param>
293-
public void ReportCannotForwardToDllImport(string name, string value, MethodDeclarationSyntax method)
322+
public void ReportCannotForwardToDllImport(MethodDeclarationSyntax method, string name, string? value = null)
294323
{
295324
_diagnostics.Add(
296325
Diagnostic.Create(
297326
CannotForwardToDllImport,
298327
Location.Create(method.SyntaxTree, method.Identifier.Span),
299-
name,
300-
value));
328+
value is null ? name : $"{name}={value}"));
301329
}
302330

303331
private static LocalizableResourceString GetResourceString(string resourceName)

src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs

Lines changed: 47 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,10 @@
136136
<value>Native type '{0}' has a constructor taking a caller-allocated buffer, but does not support marshalling in scenarios where using a caller-allocated buffer is impossible</value>
137137
</data>
138138
<data name="CannotForwardToDllImportDescription" xml:space="preserve">
139-
<value>The generated 'DllImportAttribute' will not have a value corresponding to '{0}={1}'.</value>
140-
<comment>{0} and {1} are the name and value of an attribute argument</comment>
139+
<value>The generated 'DllImportAttribute' will not have a value corresponding to '{0}'.</value>
141140
</data>
142141
<data name="CannotForwardToDllImportMessage" xml:space="preserve">
143-
<value>'{0}={1}' has no equivalent in 'DllImportAtttribute' and will not be forwarded</value>
144-
<comment>{0} and {1} are the name and value of an attribute argument</comment>
142+
<value>'{0}' has no equivalent in 'DllImportAtttribute' and will not be forwarded</value>
145143
</data>
146144
<data name="CannotForwardToDllImportTitle" xml:space="preserve">
147145
<value>Specified 'GeneratedDllImportAttribute' arguments cannot be forwarded to 'DllImportAttribute'</value>
@@ -240,6 +238,22 @@
240238
<data name="GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage" xml:space="preserve">
241239
<value>Type '{0}' has a 'GetPinnableReference' method but its native type does not support marshalling in scenarios where pinning is impossible</value>
242240
</data>
241+
<data name="InvalidLibraryImportAttributeUsageTitle" xml:space="preserve">
242+
<value>Invalid 'LibraryImportAttribute' usage</value>
243+
</data>
244+
<data name="InvalidStringMarshallingConfigurationDescription" xml:space="preserve">
245+
<value>The configuration of 'StringMarshalling' and 'StringMarshallingCustomType' is invalid.</value>
246+
</data>
247+
<data name="InvalidStringMarshallingConfigurationMessage" xml:space="preserve">
248+
<value>The configuration of 'StringMarshalling' and 'StringMarshallingCustomType' on method '{0}' is invalid. {1}</value>
249+
<comment>{1} is a message containing additional details about what is not valid</comment>
250+
</data>
251+
<data name="InvalidStringMarshallingConfigurationMissingCustomType" xml:space="preserve">
252+
<value>'StringMarshallingCustomType' must be specified when 'StringMarshalling' is set to 'StringMarshalling.Custom'.</value>
253+
</data>
254+
<data name="InvalidStringMarshallingConfigurationNotCustom" xml:space="preserve">
255+
<value>'StringMarshalling' should be set to 'StringMarshalling.Custom' when 'StringMarshallingCustomType' is specified.</value>
256+
</data>
243257
<data name="MarshallerGetPinnableReferenceRequiresValuePropertyDescription" xml:space="preserve">
244258
<value>The use cases for 'GetPinnableReference' are not applicable in any scenarios where a 'Value' property is not also required.</value>
245259
</data>

0 commit comments

Comments
 (0)