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

Refactor DllImportGenerator project for easier extensibility #1119

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
ffe2f22
Move marshalling generators and the marshalling generator infra into …
jkoritzinsky May 12, 2021
aa619bf
Fix build.
jkoritzinsky May 12, 2021
363cb2e
Use decorator pattern instead of inheritance to handle customizations…
jkoritzinsky May 13, 2021
3d2b9dc
Remove default parameter value.
jkoritzinsky May 13, 2021
458ccab
Convert Microsoft.Interop.SourceGeneration to a source package.
jkoritzinsky May 17, 2021
a31ac5f
Fix visibility and resource weirdness.
jkoritzinsky May 18, 2021
22a372e
Add a non-boilerplate description for Ancillary.Interop to satisfy pa…
jkoritzinsky May 18, 2021
e18a5e0
Merge branch 'feature/DllImportGenerator' of github.com:dotnet/runtim…
jkoritzinsky May 18, 2021
d2050fd
Revert "Fix visibility and resource weirdness."
jkoritzinsky May 21, 2021
0a3354b
Revert "Convert Microsoft.Interop.SourceGeneration to a source package."
jkoritzinsky May 21, 2021
7df4da2
Update CodeAnalysisAnalyzers version.
jkoritzinsky May 24, 2021
2af6835
Reference CodeAnalysis.Analyzers from the shared code package.
jkoritzinsky May 24, 2021
6211e32
Merge branch 'feature/DllImportGenerator' of github.com:dotnet/runtim…
jkoritzinsky Jun 11, 2021
28615eb
Merge branch 'feature/DllImportGenerator' of github.com:dotnet/runtim…
jkoritzinsky Jun 16, 2021
a11f0f7
Refactor out the new marshalling model into its own IMarshallingGener…
jkoritzinsky Jun 17, 2021
60f298c
Merge branch 'feature/DllImportGenerator' of github.com:dotnet/runtim…
jkoritzinsky Sep 8, 2021
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 @@ -6,6 +6,7 @@
<RootNamespace>System.Runtime.InteropServices</RootNamespace>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Description>APIs required for usage of the DllImportGenerator and related tools.</Description>
</PropertyGroup>

</Project>
1 change: 1 addition & 0 deletions DllImportGenerator/Benchmarks/Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<ItemGroup>
<ProjectReference Include="..\Ancillary.Interop\Ancillary.Interop.csproj" />
<ProjectReference Include="..\DllImportGenerator\DllImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\TestAssets\NativeExports\NativeExports.csproj" />
<ProjectReference Include="..\TestAssets\SharedTypes\SharedTypes.csproj" />
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions DllImportGenerator/Demo/Demo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<ItemGroup>
<ProjectReference Include="..\Ancillary.Interop\Ancillary.Interop.csproj" />
<ProjectReference Include="..\DllImportGenerator\DllImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\TestAssets\NativeExports\NativeExports.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<ItemGroup>
<ProjectReference Include="..\Ancillary.Interop\Ancillary.Interop.csproj" />
<ProjectReference Include="..\DllImportGenerator\DllImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\TestAssets\NativeExports\NativeExports.csproj" />
<ProjectReference Include="..\TestAssets\SharedTypes\SharedTypes.csproj" />
</ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions DllImportGenerator/DllImportGenerator.sln
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "Benchmarks\Be
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DllImportGenerator.Vsix", "DllImportGenerator.Vsix\DllImportGenerator.Vsix.csproj", "{F9215CDD-7B47-4C17-9166-0BE5066CA6BB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Interop.SourceGeneration", "Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj", "{03FAE24C-728D-419C-B6EC-5C7AE8C45705}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -79,6 +81,10 @@ Global
{F9215CDD-7B47-4C17-9166-0BE5066CA6BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F9215CDD-7B47-4C17-9166-0BE5066CA6BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F9215CDD-7B47-4C17-9166-0BE5066CA6BB}.Release|Any CPU.Build.0 = Release|Any CPU
{03FAE24C-728D-419C-B6EC-5C7AE8C45705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{03FAE24C-728D-419C-B6EC-5C7AE8C45705}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03FAE24C-728D-419C-B6EC-5C7AE8C45705}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03FAE24C-728D-419C-B6EC-5C7AE8C45705}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
196 changes: 185 additions & 11 deletions DllImportGenerator/DllImportGenerator/DllImportGenerator.cs
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
16 changes: 13 additions & 3 deletions DllImportGenerator/DllImportGenerator/DllImportGenerator.csproj
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