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

Improve WinRT interop support #384

Merged
merged 6 commits into from
Sep 1, 2021
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
31 changes: 31 additions & 0 deletions Microsoft.Windows.CsWin32.sln
Original file line number Diff line number Diff line change
Expand Up @@ -39,32 +39,62 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenerationSandbox.Tests", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpellChecker", "test\SpellChecker\SpellChecker.csproj", "{744BE74F-8C4A-49E8-9683-52D987224285}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinRTInteropTest", "test\WinRTInteropTest\WinRTInteropTest.csproj", "{0E067B66-C2EC-4106-87D2-5310CFCDC5B8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|NonWindows = Debug|NonWindows
Release|Any CPU = Release|Any CPU
Release|NonWindows = Release|NonWindows
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E3E96466-44B6-41AF-BBC8-9D30183ED8A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E3E96466-44B6-41AF-BBC8-9D30183ED8A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3E96466-44B6-41AF-BBC8-9D30183ED8A9}.Debug|NonWindows.ActiveCfg = Debug|Any CPU
{E3E96466-44B6-41AF-BBC8-9D30183ED8A9}.Debug|NonWindows.Build.0 = Debug|Any CPU
{E3E96466-44B6-41AF-BBC8-9D30183ED8A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3E96466-44B6-41AF-BBC8-9D30183ED8A9}.Release|Any CPU.Build.0 = Release|Any CPU
{E3E96466-44B6-41AF-BBC8-9D30183ED8A9}.Release|NonWindows.ActiveCfg = Release|Any CPU
{E3E96466-44B6-41AF-BBC8-9D30183ED8A9}.Release|NonWindows.Build.0 = Release|Any CPU
{0129FE6E-3480-408A-BF40-9E6343CDB06C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0129FE6E-3480-408A-BF40-9E6343CDB06C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0129FE6E-3480-408A-BF40-9E6343CDB06C}.Debug|NonWindows.ActiveCfg = Debug|Any CPU
{0129FE6E-3480-408A-BF40-9E6343CDB06C}.Debug|NonWindows.Build.0 = Debug|Any CPU
{0129FE6E-3480-408A-BF40-9E6343CDB06C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0129FE6E-3480-408A-BF40-9E6343CDB06C}.Release|Any CPU.Build.0 = Release|Any CPU
{0129FE6E-3480-408A-BF40-9E6343CDB06C}.Release|NonWindows.ActiveCfg = Release|Any CPU
{0129FE6E-3480-408A-BF40-9E6343CDB06C}.Release|NonWindows.Build.0 = Release|Any CPU
{6638957D-09ED-47C1-86B9-5D2DFD0FE625}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6638957D-09ED-47C1-86B9-5D2DFD0FE625}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6638957D-09ED-47C1-86B9-5D2DFD0FE625}.Debug|NonWindows.ActiveCfg = Debug|Any CPU
{6638957D-09ED-47C1-86B9-5D2DFD0FE625}.Debug|NonWindows.Build.0 = Debug|Any CPU
{6638957D-09ED-47C1-86B9-5D2DFD0FE625}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6638957D-09ED-47C1-86B9-5D2DFD0FE625}.Release|Any CPU.Build.0 = Release|Any CPU
{6638957D-09ED-47C1-86B9-5D2DFD0FE625}.Release|NonWindows.ActiveCfg = Release|Any CPU
{6638957D-09ED-47C1-86B9-5D2DFD0FE625}.Release|NonWindows.Build.0 = Release|Any CPU
{7E8A5179-F94C-410F-8BBE-FDAAA95A19C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E8A5179-F94C-410F-8BBE-FDAAA95A19C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E8A5179-F94C-410F-8BBE-FDAAA95A19C3}.Debug|NonWindows.ActiveCfg = Debug|Any CPU
{7E8A5179-F94C-410F-8BBE-FDAAA95A19C3}.Debug|NonWindows.Build.0 = Debug|Any CPU
{7E8A5179-F94C-410F-8BBE-FDAAA95A19C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E8A5179-F94C-410F-8BBE-FDAAA95A19C3}.Release|Any CPU.Build.0 = Release|Any CPU
{7E8A5179-F94C-410F-8BBE-FDAAA95A19C3}.Release|NonWindows.ActiveCfg = Release|Any CPU
{7E8A5179-F94C-410F-8BBE-FDAAA95A19C3}.Release|NonWindows.Build.0 = Release|Any CPU
{744BE74F-8C4A-49E8-9683-52D987224285}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{744BE74F-8C4A-49E8-9683-52D987224285}.Debug|Any CPU.Build.0 = Debug|Any CPU
{744BE74F-8C4A-49E8-9683-52D987224285}.Debug|NonWindows.ActiveCfg = Debug|Any CPU
{744BE74F-8C4A-49E8-9683-52D987224285}.Debug|NonWindows.Build.0 = Debug|Any CPU
{744BE74F-8C4A-49E8-9683-52D987224285}.Release|Any CPU.ActiveCfg = Release|Any CPU
{744BE74F-8C4A-49E8-9683-52D987224285}.Release|Any CPU.Build.0 = Release|Any CPU
{744BE74F-8C4A-49E8-9683-52D987224285}.Release|NonWindows.ActiveCfg = Release|Any CPU
{744BE74F-8C4A-49E8-9683-52D987224285}.Release|NonWindows.Build.0 = Release|Any CPU
{0E067B66-C2EC-4106-87D2-5310CFCDC5B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E067B66-C2EC-4106-87D2-5310CFCDC5B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E067B66-C2EC-4106-87D2-5310CFCDC5B8}.Debug|NonWindows.ActiveCfg = Debug|Any CPU
{0E067B66-C2EC-4106-87D2-5310CFCDC5B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E067B66-C2EC-4106-87D2-5310CFCDC5B8}.Release|Any CPU.Build.0 = Release|Any CPU
{0E067B66-C2EC-4106-87D2-5310CFCDC5B8}.Release|NonWindows.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -75,6 +105,7 @@ Global
{6638957D-09ED-47C1-86B9-5D2DFD0FE625} = {9E154A29-1796-4B85-BD81-B6A385D8FF71}
{7E8A5179-F94C-410F-8BBE-FDAAA95A19C3} = {36CCE840-6FE5-4DB9-A8D5-8CF3CB6D342A}
{744BE74F-8C4A-49E8-9683-52D987224285} = {36CCE840-6FE5-4DB9-A8D5-8CF3CB6D342A}
{0E067B66-C2EC-4106-87D2-5310CFCDC5B8} = {36CCE840-6FE5-4DB9-A8D5-8CF3CB6D342A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E3944F6A-384B-4B0F-B93F-3BD513DC57BD}
Expand Down
2 changes: 2 additions & 0 deletions azure-pipelines/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
vmImage: Ubuntu 18.04
variables:
TestFilter: "&WindowsOnly!=true"
Platform: NonWindows
steps:
- checkout: self
clean: true
Expand All @@ -37,6 +38,7 @@ jobs:
vmImage: macOS-10.15
variables:
TestFilter: "&WindowsOnly!=true"
Platform: NonWindows
steps:
- checkout: self
clean: true
Expand Down
78 changes: 67 additions & 11 deletions src/Microsoft.Windows.CsWin32/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public class Generator : IDisposable
internal const string RAIIFreeAttribute = "RAIIFreeAttribute";
internal const string GlobalNamespacePrefix = "global::";
internal const string GlobalWin32NamespaceAlias = "win32";
internal const string WinRTCustomMarshalerClass = "WinRTCustomMarshaler";
internal const string WinRTCustomMarshalerNamespace = "Windows.Win32.CsWin32.InteropServices";
internal const string WinRTCustomMarshalerFullName = WinRTCustomMarshalerNamespace + "." + WinRTCustomMarshalerClass;

internal static readonly SyntaxAnnotation IsManagedTypeAnnotation = new SyntaxAnnotation("IsManagedType");
internal static readonly SyntaxAnnotation IsSafeHandleTypeAnnotation = new SyntaxAnnotation("IsSafeHandleType");
Expand Down Expand Up @@ -281,6 +284,7 @@ public class Generator : IDisposable
private readonly bool generateDefaultDllImportSearchPathsAttribute;
private readonly GeneratedCode committedCode = new();
private readonly GeneratedCode volatileCode;
private bool needsWinRTCustomMarshaler;

/// <summary>
/// Initializes a new instance of the <see cref="Generator"/> class.
Expand Down Expand Up @@ -967,6 +971,28 @@ nsContents.Key is object
}
});

if (this.needsWinRTCustomMarshaler)
{
string? marshalerText = this.FetchTemplateText(WinRTCustomMarshalerClass);
if (marshalerText == null)
{
throw new GenerationFailedException($"Failed to get template for \"{WinRTCustomMarshalerClass}\".");
}

var marshalerContents = SyntaxFactory.ParseSyntaxTree(marshalerText);
if (marshalerContents == null)
{
throw new GenerationFailedException($"Failed adding \"{WinRTCustomMarshalerClass}\".");
}

var compilationUnit = ((CompilationUnitSyntax)marshalerContents.GetRoot())
.WithLeadingTrivia(ParseLeadingTrivia(AutoGeneratedHeader));

normalizedResults.Add(
string.Format(CultureInfo.InvariantCulture, FilenamePattern, WinRTCustomMarshalerClass),
compilationUnit);
}

return normalizedResults;
}

Expand All @@ -979,7 +1005,7 @@ nsContents.Key is object
}

// TODO: fill in more properties to match the original
return MarshalAs(marshalAs.Value);
return MarshalAs(marshalAs.Value, marshalAs.MarshalCookie, marshalAs.MarshalType);
}

internal static TypeSyntax MakeSpanOfT(TypeSyntax typeArgument) => GenericName("Span").AddTypeArgumentListArguments(typeArgument);
Expand Down Expand Up @@ -1184,6 +1210,9 @@ internal void RequestInteropType(TypeDefinitionHandle typeDefHandle)
new SyntaxAnnotation(NamespaceContainerAnnotation, shortNamespace));
sotteson1 marked this conversation as resolved.
Show resolved Hide resolved
}

this.needsWinRTCustomMarshaler |= typeDeclaration.DescendantNodes().OfType<AttributeSyntax>()
.Any(a => a.Name.ToString() == "MarshalAs" && a.ToString().Contains(WinRTCustomMarshalerFullName));

this.volatileCode.AddInteropType(typeDefHandle, typeDeclaration);
}
});
Expand Down Expand Up @@ -1767,14 +1796,31 @@ private static AttributeSyntax UnmanagedFunctionPointer(CallingConvention callin
IdentifierName(Enum.GetName(typeof(CallingConvention), callingConvention)!))));
}

private static AttributeSyntax MarshalAs(UnmanagedType unmanagedType)
private static AttributeSyntax MarshalAs(UnmanagedType unmanagedType, string marshalCookie, string marshalType)
{
return Attribute(IdentifierName("MarshalAs"))
.AddArgumentListArguments(AttributeArgument(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nameof(UnmanagedType)),
IdentifierName(Enum.GetName(typeof(UnmanagedType), unmanagedType)!))));
var marshalAs =
Attribute(IdentifierName("MarshalAs"))
.AddArgumentListArguments(AttributeArgument(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(nameof(UnmanagedType)),
IdentifierName(Enum.GetName(typeof(UnmanagedType), unmanagedType)!))));

if (!string.IsNullOrEmpty(marshalCookie))
{
marshalAs = marshalAs.AddArgumentListArguments(
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(marshalCookie)))
.WithNameEquals(NameEquals(nameof(MarshalAsAttribute.MarshalCookie))));
}

if (!string.IsNullOrEmpty(marshalType))
{
marshalAs = marshalAs.AddArgumentListArguments(
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(marshalType)))
.WithNameEquals(NameEquals(nameof(MarshalAsAttribute.MarshalType))));
}

return marshalAs;
}

private static AttributeSyntax DebuggerBrowsable(DebuggerBrowsableState state)
Expand Down Expand Up @@ -2150,17 +2196,27 @@ private MemberDeclarationSyntax FetchTemplate(string name)
return result;
}

private bool TryFetchTemplate(string name, [NotNullWhen(true)] out MemberDeclarationSyntax? member)
private string? FetchTemplateText(string name)
{
using Stream? templateStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{ThisAssembly.RootNamespace}.templates.{name.Replace('/', '.')}.cs");
if (templateStream is null)
{
return null;
}

using StreamReader sr = new(templateStream);
return sr.ReadToEnd().Replace("\r\n", "\n").Replace("\t", string.Empty);
}

private bool TryFetchTemplate(string name, [NotNullWhen(true)] out MemberDeclarationSyntax? member)
{
string? template = this.FetchTemplateText(name);
if (template == null)
{
member = null;
return false;
}

using StreamReader sr = new(templateStream);
string template = sr.ReadToEnd().Replace("\r\n", "\n").Replace("\t", string.Empty);
member = ParseMemberDeclaration(template) ?? throw new GenerationFailedException($"Unable to parse a type from a template: {name}");
member = this.ElevateVisibility(member);
return true;
Expand Down
25 changes: 25 additions & 0 deletions src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ internal record HandleTypeHandleInfo : TypeHandleInfo
{
private readonly MetadataReader reader;

// We just want to see that the identifier starts with I, followed by another upper case letter,
// followed by a lower case letter. All the WinRT interfaces will match this, and none of the WinRT
// objects will match it
private static readonly System.Text.RegularExpressions.Regex InterfaceNameMatcher = new System.Text.RegularExpressions.Regex("^I[A-Z][a-z]");

internal HandleTypeHandleInfo(MetadataReader reader, EntityHandle handle, byte? rawTypeKind = null)
{
this.reader = reader;
Expand Down Expand Up @@ -104,6 +109,26 @@ internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs
syntax = inputs.AllowMarshaling && !isNonCOMConformingInterface ? syntax.WithAdditionalAnnotations(Generator.IsManagedTypeAnnotation) : PointerType(syntax);
}

if (nameSyntax is QualifiedNameSyntax qualifiedName)
{
var ns = qualifiedName.Left.ToString();

// Look for WinRT namespaces
if (ns.StartsWith("Windows.Foundation") || ns.StartsWith("Windows.UI") || ns.StartsWith("Windows.Graphics") || ns.StartsWith("Windows.System"))
{
// We only want to marshal WinRT objects, not interfaces. We don't have a good way of knowing
// whether it's an interface or an object. "isInterface" comes back as false for a WinRT interface,
// so that doesn't help. Looking at the name should be good enough, but if we needed to, the
// Win32 projection could give us an attribute to make sure
var objName = qualifiedName.Right.ToString();
bool isInterfaceName = InterfaceNameMatcher.IsMatch(objName);
if (!isInterfaceName)
{
return new TypeSyntaxAndMarshaling(syntax, new MarshalAsAttribute(UnmanagedType.CustomMarshaler) { MarshalCookie = nameSyntax.ToString(), MarshalType = Generator.WinRTCustomMarshalerFullName });
}
}
}

return new TypeSyntaxAndMarshaling(syntax);
}

Expand Down
64 changes: 64 additions & 0 deletions src/Microsoft.Windows.CsWin32/templates/WinRTCustomMarshaler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
namespace Windows.Win32.CsWin32.InteropServices
{
internal class WinRTCustomMarshaler : global::System.Runtime.InteropServices.ICustomMarshaler
{
private string winrtClassName;
private bool lookedForFromAbi;
private global::System.Reflection.MethodInfo fromAbi;

private WinRTCustomMarshaler(string cookie)
{
this.winrtClassName = cookie;
}

/// <summary>
/// Gets an instance of the marshaler given a cookie
/// </summary>
/// <param name="cookie">Cookie used to create marshaler</param>
/// <returns>Marshaler</returns>
public static global::System.Runtime.InteropServices.ICustomMarshaler GetInstance(string cookie)
{
return new WinRTCustomMarshaler(cookie);
}

void global::System.Runtime.InteropServices.ICustomMarshaler.CleanUpManagedData(object ManagedObj)
{
}

void global::System.Runtime.InteropServices.ICustomMarshaler.CleanUpNativeData(global::System.IntPtr pNativeData)
{
global::System.Runtime.InteropServices.Marshal.Release(pNativeData);
}

int global::System.Runtime.InteropServices.ICustomMarshaler.GetNativeDataSize()
{
throw new global::System.NotImplementedException();
}

global::System.IntPtr global::System.Runtime.InteropServices.ICustomMarshaler.MarshalManagedToNative(object ManagedObj)
{
throw new global::System.NotImplementedException();
}

object global::System.Runtime.InteropServices.ICustomMarshaler.MarshalNativeToManaged(global::System.IntPtr pNativeData)
{
if (!this.lookedForFromAbi)
{
var assembly = typeof(global::Windows.Foundation.IMemoryBuffer).Assembly;
var type = global::System.Type.GetType($"{this.winrtClassName}, {assembly.FullName}");

this.fromAbi = type.GetMethod("FromAbi");
this.lookedForFromAbi = true;
}

if (this.fromAbi != null)
{
return this.fromAbi.Invoke(null, new object[] { pNativeData });
}
else
{
return global::System.Runtime.InteropServices.Marshal.GetObjectForIUnknown(pNativeData);
}
}
}
}
Loading