Skip to content
Merged
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
108 changes: 86 additions & 22 deletions DllImportGenerator/DllImportGenerator.Test/CodeSnippets.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace DllImportGenerator.Test
{
Expand Down Expand Up @@ -197,22 +192,6 @@ partial class Test
ThrowOnUnmappableChar = !IsTrue)]
public static partial void Method();
}
";

/// <summary>
/// Declaration with basic parameters.
/// </summary>
public static readonly string BasicParametersAndModifiers = @"
using System;
using System.Runtime.InteropServices;
partial class Test
{
[GeneratedDllImport(""DoesNotExist"")]
public static partial void Method1(string s, IntPtr i, UIntPtr u);

[GeneratedDllImport(""DoesNotExist"")]
public static partial void Method2(in string s, ref IntPtr i, out UIntPtr u);
}
";

/// <summary>
Expand Down Expand Up @@ -301,5 +280,90 @@ partial class Test
public static partial void Method3();
}
";

/// <summary>
/// Declaration with parameters.
/// </summary>
public static string BasicParametersAndModifiers(string typeName) => @$"
using System.Runtime.InteropServices;
partial class Test
{{
[GeneratedDllImport(""DoesNotExist"")]
public static partial {typeName} Method(
{typeName} p,
in {typeName} pIn,
ref {typeName} pRef,
out {typeName} pOut);
}}";

public static string BasicParametersAndModifiers<T>() => BasicParametersAndModifiers(typeof(T).ToString());

/// <summary>
/// Declaration with parameters with MarshalAs.
/// </summary>
public static string MarshalAsParametersAndModifiers(string typeName, UnmanagedType unmanagedType) => @$"
using System.Runtime.InteropServices;
partial class Test
{{
[GeneratedDllImport(""DoesNotExist"")]
[return: MarshalAs(UnmanagedType.{unmanagedType})]
public static partial {typeName} Method(
[MarshalAs(UnmanagedType.{unmanagedType})] {typeName} p,
[MarshalAs(UnmanagedType.{unmanagedType})] in {typeName} pIn,
[MarshalAs(UnmanagedType.{unmanagedType})] ref {typeName} pRef,
[MarshalAs(UnmanagedType.{unmanagedType})] out {typeName} pOut);
}}
";

public static string MarshalAsParametersAndModifiers<T>(UnmanagedType unmanagedType) => MarshalAsParametersAndModifiers(typeof(T).ToString(), unmanagedType);

/// <summary>
/// Declaration with enum parameters.
/// </summary>
public static string EnumParameters => @$"
using System.Runtime.InteropServices;
using NS;

namespace NS
{{
enum MyEnum {{ A, B, C }}
}}

partial class Test
{{
[GeneratedDllImport(""DoesNotExist"")]
public static partial MyEnum Method(
MyEnum p,
in MyEnum pIn,
ref MyEnum pRef,
out MyEnum pOut);
}}";

/// <summary>
/// Declaration with PreserveSig = false.
/// </summary>
public static string PreserveSigFalse(string typeName) => @$"
using System.Runtime.InteropServices;
partial class Test
{{
[GeneratedDllImport(""DoesNotExist"", PreserveSig = false)]
public static partial {typeName} Method1();

[GeneratedDllImport(""DoesNotExist"", PreserveSig = false)]
public static partial {typeName} Method2({typeName} p);
}}";

public static string PreserveSigFalse<T>() => PreserveSigFalse(typeof(T).ToString());

/// <summary>
/// Declaration with PreserveSig = false and void return.
/// </summary>
public static readonly string PreserveSigFalseVoidReturn = @$"
using System.Runtime.InteropServices;
partial class Test
{{
[GeneratedDllImport(""DoesNotExist"", PreserveSig = false)]
public static partial void Method();
}}";
}
}
68 changes: 67 additions & 1 deletion DllImportGenerator/DllImportGenerator.Test/Compiles.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Microsoft.CodeAnalysis;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Xunit;

Expand All @@ -16,10 +18,74 @@ public static IEnumerable<object[]> CodeSnippetsToCompile()
yield return new[] { CodeSnippets.NestedTypes };
yield return new[] { CodeSnippets.UserDefinedEntryPoint };
yield return new[] { CodeSnippets.AllDllImportNamedArguments };
yield return new[] { CodeSnippets.BasicParametersAndModifiers };
yield return new[] { CodeSnippets.DefaultParameters };
yield return new[] { CodeSnippets.UseCSharpFeaturesForConstants };
yield return new[] { CodeSnippets.MarshalAsAttributeOnTypes };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<byte>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<sbyte>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<short>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<ushort>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<int>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<uint>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<long>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<ulong>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<float>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<double>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<bool>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<char>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<string>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<IntPtr>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<UIntPtr>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<byte[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<sbyte[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<short[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<ushort[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<int[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<uint[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<long[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<ulong[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<float[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<double[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<bool[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<char[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<string[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<IntPtr[]>() };
yield return new[] { CodeSnippets.BasicParametersAndModifiers<UIntPtr[]>() };
yield return new[] { CodeSnippets.MarshalAsParametersAndModifiers<bool>(UnmanagedType.Bool) };
yield return new[] { CodeSnippets.MarshalAsParametersAndModifiers<bool>(UnmanagedType.VariantBool) };
yield return new[] { CodeSnippets.MarshalAsParametersAndModifiers<bool>(UnmanagedType.I1) };
yield return new[] { CodeSnippets.EnumParameters };
yield return new[] { CodeSnippets.PreserveSigFalseVoidReturn };
yield return new[] { CodeSnippets.PreserveSigFalse<byte>() };
yield return new[] { CodeSnippets.PreserveSigFalse<sbyte>() };
yield return new[] { CodeSnippets.PreserveSigFalse<short>() };
yield return new[] { CodeSnippets.PreserveSigFalse<ushort>() };
yield return new[] { CodeSnippets.PreserveSigFalse<int>() };
yield return new[] { CodeSnippets.PreserveSigFalse<uint>() };
yield return new[] { CodeSnippets.PreserveSigFalse<long>() };
yield return new[] { CodeSnippets.PreserveSigFalse<ulong>() };
yield return new[] { CodeSnippets.PreserveSigFalse<float>() };
yield return new[] { CodeSnippets.PreserveSigFalse<double>() };
yield return new[] { CodeSnippets.PreserveSigFalse<bool>() };
yield return new[] { CodeSnippets.PreserveSigFalse<char>() };
yield return new[] { CodeSnippets.PreserveSigFalse<string>() };
yield return new[] { CodeSnippets.PreserveSigFalse<IntPtr>() };
yield return new[] { CodeSnippets.PreserveSigFalse<UIntPtr>() };
yield return new[] { CodeSnippets.PreserveSigFalse<byte[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<sbyte[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<short[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<ushort[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<int[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<uint[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<long[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<ulong[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<float[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<double[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<bool[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<char[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<string[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<IntPtr[]>() };
yield return new[] { CodeSnippets.PreserveSigFalse<UIntPtr[]>() };
}

[Theory]
Expand Down
7 changes: 4 additions & 3 deletions DllImportGenerator/DllImportGenerator.Test/TestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,16 @@ public static void AssertPreSourceGeneratorCompilation(Compilation comp)
/// </summary>
/// <param name="source">Source to compile</param>
/// <param name="outputKind">Output type</param>
/// <param name="allowUnsafe">Whether or not use of the unsafe keyword should be allowed</param>
/// <returns>The resulting compilation</returns>
public static async Task<Compilation> CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary)
public static async Task<Compilation> CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true)
{
var (mdRefs, ancillary) = GetReferenceAssemblies();

return CSharpCompilation.Create("compilation",
new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) },
(await mdRefs.ResolveAsync(LanguageNames.CSharp, CancellationToken.None)).Add(ancillary),
new CSharpCompilationOptions(outputKind));
new CSharpCompilationOptions(outputKind, allowUnsafe: allowUnsafe));
}

public static (ReferenceAssemblies, MetadataReference) GetReferenceAssemblies()
Expand Down
95 changes: 28 additions & 67 deletions DllImportGenerator/DllImportGenerator/DllImportGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Microsoft.Interop
{
Expand Down Expand Up @@ -53,7 +55,7 @@ public void Execute(SourceGeneratorContext context)
Debug.Assert(!(dllImportAttr is null) && !(dllImportData is null));

// Create the stub.
var dllImportStub = DllImportStub.Create(methodSymbolInfo, dllImportData, context.CancellationToken);
var dllImportStub = DllImportStub.Create(methodSymbolInfo, dllImportData, context.Compilation, context.CancellationToken);

// Report any diagnostics from the stub generation step.
foreach (var diag in dllImportStub.Diagnostics)
Expand All @@ -79,83 +81,42 @@ private void PrintGeneratedSource(
DllImportStub stub,
AttributeSyntax dllImportAttr)
{
const string SingleDepth = " ";
var currentIndent = string.Empty;

// Declare namespace
if (!(stub.StubTypeNamespace is null))
{
builder.AppendLine($@"namespace {stub.StubTypeNamespace}
{{");
currentIndent += SingleDepth;
}

// Print type declarations
var typeIndentStack = new Stack<string>();
foreach (var typeDecl in stub.StubContainingTypesDecl)
{
builder.AppendLine($@"{currentIndent}{typeDecl}
{currentIndent}{{");

typeIndentStack.Push(currentIndent);
currentIndent += SingleDepth;
}

// Begin declare function
builder.Append(
$@"{currentIndent}{userDeclaredMethod.Modifiers} {stub.StubReturnType} {userDeclaredMethod.Identifier}(");

char delim = ' ';
foreach (var param in stub.StubParameters)
{
builder.Append($"{delim}{param.Type} {param.Name}");
delim = ',';
}
// Create stub function
var stubMethod = MethodDeclaration(stub.StubReturnType, userDeclaredMethod.Identifier)
.WithModifiers(userDeclaredMethod.Modifiers)
.WithParameterList(ParameterList(SeparatedList(stub.StubParameters)))
.WithBody(stub.StubCode);

// End declare function
builder.AppendLine(
$@")
{currentIndent}{{");

// Insert lines into function
foreach (var line in stub.StubCode)
{
builder.AppendLine($@"{currentIndent}{SingleDepth}{line}");
}
// Create the DllImport declaration.
// [TODO] Don't include PreserveSig=false once that is handled by the generated stub
var dllImport = stub.DllImportDeclaration.AddAttributeLists(
AttributeList(
SingletonSeparatedList<AttributeSyntax>(dllImportAttr)));

builder.AppendLine(
$@"{ currentIndent}}}
// Stub should have at least one containing type
Debug.Assert(stub.StubContainingTypes.Any());

{currentIndent}[{dllImportAttr}]");
// Add stub function and DllImport declaration to the first (innermost) containing
MemberDeclarationSyntax containingType = stub.StubContainingTypes.First()
.AddMembers(stubMethod, dllImport);

// Create the DllImport declaration.
builder.Append($"{currentIndent}extern private static {stub.DllImportReturnType} {stub.DllImportMethodName}");
if (!stub.DllImportParameters.Any())
{
builder.AppendLine("();");
}
else
// Add type to the remaining containing types (skipping the first which was handled above)
foreach (var typeDecl in stub.StubContainingTypes.Skip(1))
{
delim = '(';
foreach (var paramPair in stub.DllImportParameters)
{
builder.Append($"{delim}{paramPair.Type} {paramPair.Name}");
delim = ',';
}
builder.AppendLine(");");
containingType = typeDecl.WithMembers(
SingletonList<MemberDeclarationSyntax>(containingType));
}

// Print closing type declarations
while (typeIndentStack.Count > 0)
{
builder.AppendLine($@"{typeIndentStack.Pop()}}}");
}
MemberDeclarationSyntax toPrint = containingType;

// Close namespace
// Add type to the containing namespace
if (!(stub.StubTypeNamespace is null))
{
builder.AppendLine("}");
toPrint = NamespaceDeclaration(IdentifierName(stub.StubTypeNamespace))
.AddMembers(toPrint);
}

builder.AppendLine(toPrint.NormalizeWhitespace().ToString());
}

private static bool IsGeneratedDllImportAttribute(AttributeSyntax attrSyntaxMaybe)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>GENERATE_FORWARDER</DefineConstants>
<LangVersion>Preview</LangVersion>

<!-- Uncomment to generate stub code that simply forwards parameters to p/invoke (i.e. no marshalling) -->
<!--<DefineConstants>GENERATE_FORWARDER</DefineConstants>-->
</PropertyGroup>

<PropertyGroup>
Expand Down
Loading