Skip to content

Commit

Permalink
Refactor DllImportGenerator project for easier extensibility (dotnet/…
Browse files Browse the repository at this point in the history
  • Loading branch information
jkoritzinsky authored Sep 10, 2021
1 parent d8775ee commit 2449c46
Show file tree
Hide file tree
Showing 50 changed files with 1,912 additions and 1,148 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
}
});

var stubOptions = context.AnalyzerConfigOptionsProvider.Select((options, ct) => new DllImportGeneratorOptions(options.GlobalOptions));

var stubEnvironment = compilationAndTargetFramework
.Combine(context.AnalyzerConfigOptionsProvider)
.Combine(stubOptions)
.Select(
static (data, ct) =>
new StubEnvironment(
data.Left.compilation,
data.Left.isSupported,
data.Left.targetFrameworkVersion,
data.Right.GlobalOptions,
data.Left.compilation.SourceModule.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute))
data.Left.compilation.SourceModule.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute),
data.Right)
);

var methodSourceAndDiagnostics = methodsToGenerate
Expand All @@ -133,12 +135,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
}
)
.WithComparer(Comparers.CalculatedContextWithSyntax)
.Combine(context.AnalyzerConfigOptionsProvider)
.Combine(stubOptions)
.Select(
(data, ct) =>
{
IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.GenerateSingleStub, data));
return GenerateSource(data.Left.StubContext, data.Left.Syntax, data.Right.GlobalOptions);
return GenerateSource(data.Left.StubContext, data.Left.Syntax, data.Right);
}
)
.WithComparer(Comparers.GeneratedSyntax)
Expand Down Expand Up @@ -444,24 +446,196 @@ private static IncrementalStubGenerationContext CalculateStubInformation(MethodD
private (MemberDeclarationSyntax, ImmutableArray<Diagnostic>) GenerateSource(
IncrementalStubGenerationContext dllImportStub,
MethodDeclarationSyntax originalSyntax,
AnalyzerConfigOptions options)
DllImportGeneratorOptions options)
{
var diagnostics = new GeneratorDiagnostics();

// Generate stub code
var stubGenerator = new StubCodeGenerator(
dllImportStub.DllImportData,
var stubGenerator = new PInvokeStubCodeGenerator(
dllImportStub.StubContext.ElementTypeInformation,
options,
(elementInfo, ex) => diagnostics.ReportMarshallingNotSupported(originalSyntax, elementInfo, ex.NotSupportedDetails));
dllImportStub.DllImportData.SetLastError && !options.GenerateForwarders,
(elementInfo, ex) => diagnostics.ReportMarshallingNotSupported(originalSyntax, elementInfo, ex.NotSupportedDetails),
dllImportStub.StubContext.GeneratorFactory);

ImmutableArray<AttributeSyntax> forwardedAttributes = dllImportStub.ForwardedAttributes;

var code = stubGenerator.GenerateBody(originalSyntax.Identifier.Text, forwardedAttributes: forwardedAttributes.Length != 0 ? AttributeList(SeparatedList(forwardedAttributes)) : null);
const string innerPInvokeName = "__PInvoke__";

var code = stubGenerator.GeneratePInvokeBody(innerPInvokeName);

var dllImport = CreateTargetFunctionAsLocalStatement(
stubGenerator,
dllImportStub.StubContext.Options,
dllImportStub.DllImportData,
innerPInvokeName,
originalSyntax.Identifier.Text);

if (!forwardedAttributes.IsEmpty)
{
dllImport = dllImport.AddAttributeLists(AttributeList(SeparatedList(forwardedAttributes)));
}

code = code.AddStatements(dllImport);

return (PrintGeneratedSource(originalSyntax, dllImportStub.StubContext, code), dllImportStub.Diagnostics.AddRange(diagnostics.Diagnostics));
}


private static LocalFunctionStatementSyntax CreateTargetFunctionAsLocalStatement(
PInvokeStubCodeGenerator stubGenerator,
DllImportGeneratorOptions options,
GeneratedDllImportData dllImportData,
string stubTargetName,
string stubMethodName)
{
var (parameterList, returnType, returnTypeAttributes) = stubGenerator.GenerateTargetMethodSignatureData();
var localDllImport = LocalFunctionStatement(returnType, stubTargetName)
.AddModifiers(
Token(SyntaxKind.ExternKeyword),
Token(SyntaxKind.StaticKeyword),
Token(SyntaxKind.UnsafeKeyword))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
.WithAttributeLists(
SingletonList(AttributeList(
SingletonSeparatedList(
CreateDllImportAttributeForTarget(
GetTargetDllImportDataFromStubData(
dllImportData,
stubMethodName,
options.GenerateForwarders))))))
.WithParameterList(parameterList);
if (returnTypeAttributes is not null)
{
localDllImport = localDllImport.AddAttributeLists(returnTypeAttributes.WithTarget(AttributeTargetSpecifier(Token(SyntaxKind.ReturnKeyword))));
}
return localDllImport;
}

private static AttributeSyntax CreateDllImportAttributeForTarget(GeneratedDllImportData targetDllImportData)
{
var newAttributeArgs = new List<AttributeArgumentSyntax>
{
AttributeArgument(LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal(targetDllImportData.ModuleName))),
AttributeArgument(
NameEquals(nameof(DllImportAttribute.EntryPoint)),
null,
CreateStringExpressionSyntax(targetDllImportData.EntryPoint!))
};

if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.BestFitMapping))
{
var name = NameEquals(nameof(DllImportAttribute.BestFitMapping));
var value = CreateBoolExpressionSyntax(targetDllImportData.BestFitMapping);
newAttributeArgs.Add(AttributeArgument(name, null, value));
}
if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.CallingConvention))
{
var name = NameEquals(nameof(DllImportAttribute.CallingConvention));
var value = CreateEnumExpressionSyntax(targetDllImportData.CallingConvention);
newAttributeArgs.Add(AttributeArgument(name, null, value));
}
if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.CharSet))
{
var name = NameEquals(nameof(DllImportAttribute.CharSet));
var value = CreateEnumExpressionSyntax(targetDllImportData.CharSet);
newAttributeArgs.Add(AttributeArgument(name, null, value));
}
if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.ExactSpelling))
{
var name = NameEquals(nameof(DllImportAttribute.ExactSpelling));
var value = CreateBoolExpressionSyntax(targetDllImportData.ExactSpelling);
newAttributeArgs.Add(AttributeArgument(name, null, value));
}
if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.PreserveSig))
{
var name = NameEquals(nameof(DllImportAttribute.PreserveSig));
var value = CreateBoolExpressionSyntax(targetDllImportData.PreserveSig);
newAttributeArgs.Add(AttributeArgument(name, null, value));
}
if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.SetLastError))
{
var name = NameEquals(nameof(DllImportAttribute.SetLastError));
var value = CreateBoolExpressionSyntax(targetDllImportData.SetLastError);
newAttributeArgs.Add(AttributeArgument(name, null, value));
}
if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.ThrowOnUnmappableChar))
{
var name = NameEquals(nameof(DllImportAttribute.ThrowOnUnmappableChar));
var value = CreateBoolExpressionSyntax(targetDllImportData.ThrowOnUnmappableChar);
newAttributeArgs.Add(AttributeArgument(name, null, value));
}

// Create new attribute
return Attribute(
ParseName(typeof(DllImportAttribute).FullName),
AttributeArgumentList(SeparatedList(newAttributeArgs)));

static ExpressionSyntax CreateBoolExpressionSyntax(bool trueOrFalse)
{
return LiteralExpression(
trueOrFalse
? SyntaxKind.TrueLiteralExpression
: SyntaxKind.FalseLiteralExpression);
}

static ExpressionSyntax CreateStringExpressionSyntax(string str)
{
return LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal(str));
}

static ExpressionSyntax CreateEnumExpressionSyntax<T>(T value) where T : Enum
{
return MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(typeof(T).FullName),
IdentifierName(value.ToString()));
}
}

private static GeneratedDllImportData GetTargetDllImportDataFromStubData(GeneratedDllImportData dllImportData, string originalMethodName, bool forwardAll)
{
DllImportMember membersToForward = DllImportMember.All
// https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute.preservesig
// If PreserveSig=false (default is true), the P/Invoke stub checks/converts a returned HRESULT to an exception.
& ~DllImportMember.PreserveSig
// https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute.setlasterror
// If SetLastError=true (default is false), the P/Invoke stub gets/caches the last error after invoking the native function.
& ~DllImportMember.SetLastError;
if (forwardAll)
{
membersToForward = DllImportMember.All;
}

var targetDllImportData = new GeneratedDllImportData(dllImportData.ModuleName)
{
CharSet = dllImportData.CharSet,
BestFitMapping = dllImportData.BestFitMapping,
CallingConvention = dllImportData.CallingConvention,
EntryPoint = dllImportData.EntryPoint,
ExactSpelling = dllImportData.ExactSpelling,
SetLastError = dllImportData.SetLastError,
PreserveSig = dllImportData.PreserveSig,
ThrowOnUnmappableChar = dllImportData.ThrowOnUnmappableChar,
IsUserDefined = dllImportData.IsUserDefined & membersToForward
};

// If the EntryPoint property is not set, we will compute and
// add it based on existing semantics (i.e. method name).
//
// N.B. The export discovery logic is identical regardless of where
// the name is defined (i.e. method name vs EntryPoint property).
if (!targetDllImportData.IsUserDefined.HasFlag(DllImportMember.EntryPoint))
{
targetDllImportData = targetDllImportData with { EntryPoint = originalMethodName };
}

return targetDllImportData;
}

private static bool ShouldVisitNode(SyntaxNode syntaxNode)
{
// We only support C# method declarations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@
</ItemGroup>

<ItemGroup>
<None Remove="bin\Debug\netstandard2.0\\DllImportGenerator.dll" />
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
<None Include="Microsoft.Interop.DllImportGenerator.props" Pack="true" PackagePath="build" />
</ItemGroup>

<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
<None Include="Microsoft.Interop.DllImportGenerator.props" Pack="true" PackagePath="build" />
<ProjectReference Include="..\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj" />
</ItemGroup>

<ItemGroup>
Expand All @@ -58,4 +58,14 @@
</EmbeddedResource>
</ItemGroup>

<Target Name="IncludeProjectReferenceInPackage" BeforeTargets="_GetPackageFiles">
<MSBuild Projects="@(ProjectReference)" Targets="Build">
<Output TaskParameter="TargetOutputs" ItemName="ProjectReferenceOutput" />
</MSBuild>

<ItemGroup>
<None Include="@(ProjectReferenceOutput)" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
</Target>

</Project>
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Microsoft.Interop
{
record DllImportGeneratorOptions(bool GenerateForwarders, bool UseMarshalType, bool UseInternalUnsafeType)
{
public DllImportGeneratorOptions(AnalyzerConfigOptions options)
: this(options.GenerateForwarders(), options.UseMarshalType(), options.UseInternalUnsafeType())
{
}
}

public static class OptionsHelper
{
public const string UseMarshalTypeOption = "build_property.DllImportGenerator_UseMarshalType";
Expand Down
Loading

0 comments on commit 2449c46

Please sign in to comment.