Skip to content

Commit

Permalink
Collect DllImport details through Roslyn semantic model (#36)
Browse files Browse the repository at this point in the history
* Collect DllImport details through Roslyn semantic model

Create an assembly that provides the GeneratedDllImportAttribute
  which allows use of the semantic model.

* Collect MarshalAsAttribute data

* Collect MarshalAsAttribute data
Convert P/Invoke centric data structures to handle positional
  type information for managed/unmanaged scenarios.
  • Loading branch information
AaronRobinsonMSFT authored Aug 3, 2020
1 parent 48b8361 commit 21e634e
Show file tree
Hide file tree
Showing 13 changed files with 647 additions and 214 deletions.
8 changes: 8 additions & 0 deletions DllImportGenerator/Ancillary.Interop/Ancillary.Interop.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<LangVersion>8.0</LangVersion>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#nullable enable

namespace System.Runtime.InteropServices
{
// [TODO] Remove once the attribute has been added to the BCL
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class GeneratedDllImportAttribute : Attribute
{
public bool BestFitMapping;
public CallingConvention CallingConvention;
public CharSet CharSet;
public string? EntryPoint;
public bool ExactSpelling;
public bool PreserveSig;
public bool SetLastError;
public bool ThrowOnUnmappableChar;

public GeneratedDllImportAttribute(string dllName)
{
this.Value = dllName;
}

public string Value { get; private set; }
}
}
1 change: 1 addition & 0 deletions DllImportGenerator/Demo/Demo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Ancillary.Interop\Ancillary.Interop.csproj" />
<ProjectReference Include="..\DllImportGenerator\DllImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

Expand Down
90 changes: 90 additions & 0 deletions DllImportGenerator/DllImportGenerator.Test/CodeSnippets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,50 @@ partial class Test
[GeneratedDllImport(""DoesNotExist"", EntryPoint=""UserDefinedEntryPoint"")]
public static partial void NotAnExport();
}
";

/// <summary>
/// Declaration with all DllImport named arguments.
/// </summary>
public static readonly string AllDllImportNamedArguments = @"
using System.Runtime.InteropServices;
partial class Test
{
[GeneratedDllImport(""DoesNotExist"",
BestFitMapping = false,
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Unicode,
EntryPoint = ""UserDefinedEntryPoint"",
ExactSpelling = true,
PreserveSig = false,
SetLastError = true,
ThrowOnUnmappableChar = true)]
public static partial void Method();
}
";

/// <summary>
/// Declaration using various methods to compute constants in C#.
/// </summary>
public static readonly string UseCSharpFeaturesForConstants = @"
using System.Runtime.InteropServices;
partial class Test
{
private const bool IsTrue = true;
private const bool IsFalse = false;
private const string EntryPointName = nameof(Test) + nameof(IsFalse);
[GeneratedDllImport(nameof(Test),
BestFitMapping = 0 != 1,
CallingConvention = (CallingConvention)1,
CharSet = (CharSet)2,
EntryPoint = EntryPointName,
ExactSpelling = IsTrue,
PreserveSig = IsFalse,
SetLastError = !IsFalse,
ThrowOnUnmappableChar = !IsTrue)]
public static partial void Method();
}
";

/// <summary>
Expand Down Expand Up @@ -181,6 +225,52 @@ partial class Test
[GeneratedDllImport(""DoesNotExist"")]
public static partial void Method(int t = 0);
}
";

/// <summary>
/// Apply MarshalAsAttribute to parameters and return types.
/// </summary>
public static readonly string MarshalAsAttributeOnTypes = @"
using System;
using System.Runtime.InteropServices;
namespace NS
{
class MyCustomMarshaler : ICustomMarshaler
{
static ICustomMarshaler GetInstance(string pstrCookie)
=> new MyCustomMarshaler();
public void CleanUpManagedData(object ManagedObj)
=> throw new NotImplementedException();
public void CleanUpNativeData(IntPtr pNativeData)
=> throw new NotImplementedException();
public int GetNativeDataSize()
=> throw new NotImplementedException();
public IntPtr MarshalManagedToNative(object ManagedObj)
=> throw new NotImplementedException();
public object MarshalNativeToManaged(IntPtr pNativeData)
=> throw new NotImplementedException();
}
}
partial class Test
{
[GeneratedDllImport(""DoesNotExist"")]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static partial string Method1([MarshalAs(UnmanagedType.LPStr)]string t);
[GeneratedDllImport(""DoesNotExist"")]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static partial string Method2([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(NS.MyCustomMarshaler), MarshalCookie=""COOKIE1"")]string t);
[GeneratedDllImport(""DoesNotExist"")]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static partial string Method3([MarshalAs(UnmanagedType.CustomMarshaler, MarshalType = ""NS.MyCustomMarshaler"", MarshalCookie=""COOKIE2"")]string t);
}
";

/// <summary>
Expand Down
3 changes: 3 additions & 0 deletions DllImportGenerator/DllImportGenerator.Test/Compiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ public static IEnumerable<object[]> CodeSnippetsToCompile()
yield return new[] { CodeSnippets.NestedNamespace };
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 };
}

[Theory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Ancillary.Interop\Ancillary.Interop.csproj" />
<ProjectReference Include="..\DllImportGenerator\DllImportGenerator.csproj" />
</ItemGroup>

Expand Down
22 changes: 20 additions & 2 deletions DllImportGenerator/DllImportGenerator.Test/TestUtils.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Xunit;

namespace DllImportGenerator.Test
Expand Down Expand Up @@ -34,10 +36,26 @@ public static void AssertPreSourceGeneratorCompilation(Compilation comp)
/// <param name="outputKind">Output type</param>
/// <returns>The resulting compilation</returns>
public static Compilation CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary)
=> CSharpCompilation.Create("compilation",
{
var mdRefs = new List<MetadataReference>();

// Include the assembly containing the new attribute and all of its references.
// [TODO] Remove once the attribute has been added to the BCL
var attrAssem = typeof(GeneratedDllImportAttribute).GetTypeInfo().Assembly;
mdRefs.Add(MetadataReference.CreateFromFile(attrAssem.Location));
foreach (var assemName in attrAssem.GetReferencedAssemblies())
{
var assemRef = Assembly.Load(assemName);
mdRefs.Add(MetadataReference.CreateFromFile(assemRef.Location));
}

// Add a CoreLib reference
mdRefs.Add(MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location));
return CSharpCompilation.Create("compilation",
new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) },
new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) },
mdRefs,
new CSharpCompilationOptions(outputKind));
}

/// <summary>
/// Run the supplied generators on the compilation.
Expand Down
8 changes: 7 additions & 1 deletion DllImportGenerator/DllImportGenerator.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DllImportGenerator", "DllIm
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DllImportGenerator.Test", "DllImportGenerator.Test\DllImportGenerator.Test.csproj", "{B8CA13C4-F41B-4ABD-A9F3-63A02C53B96E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo", "Demo\Demo.csproj", "{E478EE77-E072-4A42-B453-EBFDA7728717}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo", "Demo\Demo.csproj", "{E478EE77-E072-4A42-B453-EBFDA7728717}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ancillary.Interop", "Ancillary.Interop\Ancillary.Interop.csproj", "{E59F0B6A-1137-4179-A91D-33464A775DEB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -27,6 +29,10 @@ Global
{E478EE77-E072-4A42-B453-EBFDA7728717}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E478EE77-E072-4A42-B453-EBFDA7728717}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E478EE77-E072-4A42-B453-EBFDA7728717}.Release|Any CPU.Build.0 = Release|Any CPU
{E59F0B6A-1137-4179-A91D-33464A775DEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E59F0B6A-1137-4179-A91D-33464A775DEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E59F0B6A-1137-4179-A91D-33464A775DEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E59F0B6A-1137-4179-A91D-33464A775DEB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Loading

0 comments on commit 21e634e

Please sign in to comment.