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