From 1cbef0a4f931f1b61a02c900c446f82590155d3c Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Fri, 5 Dec 2025 00:27:01 -0800 Subject: [PATCH 01/15] Add support for reference assembly --- ...crosoft.Windows.CsWinRT.CsWinRTGen.targets | 205 ++++++++++++++++- nuget/Microsoft.Windows.CsWinRT.targets | 3 + src/Directory.Packages.props | 6 +- src/Projections/Directory.Build.props | 6 +- src/Projections/Directory.Build.targets | 6 + .../RunCsWinRTForwarderImplGenerator.cs | 209 ++++++++++++++++++ .../RunCsWinRTMergedProjectionGenerator.cs | 198 +++++++++++++++++ .../FunctionalTests/Directory.Build.props | 5 +- .../Generation/ImplGenerator.cs | 15 +- .../Builders/IgnoresAccessChecksToBuilder.cs | 2 + ...opTypeDefinitionBuilder.UserDefinedType.cs | 4 +- .../Helpers/GuidGenerator.cs | 10 - .../References/InteropReferences.cs | 9 + src/cswinrt.slnx | 1 + src/cswinrt/code_writers.h | 31 ++- src/cswinrt/main.cpp | 171 ++++++++------ src/cswinrt/settings.h | 1 + ...crosoft.UI.Xaml.Media.Animation.KeyTime.cs | 2 + ....UI.Xaml.Media.Animation.RepeatBehavior.cs | 2 + ...icrosoft.UI.Xaml.Media.Media3D.Matrix3D.cs | 2 + .../Microsoft.UI.Xaml.CornerRadius.cs | 2 + .../Microsoft.UI.Xaml.Duration.cs | 2 + .../Microsoft.UI.Xaml.GridLength.cs | 2 + ...Windows.UI.Xaml.Media.Animation.KeyTime.cs | 2 + ....UI.Xaml.Media.Animation.RepeatBehavior.cs | 2 + .../Windows.UI.Xaml.Media.Media3D.Matrix3D.cs | 2 + .../Windows.UI.Xaml.CornerRadius.cs | 2 + .../Windows.UI.Xaml.Duration.cs | 2 + .../Windows.UI.Xaml.GridLength.cs | 2 + 29 files changed, 803 insertions(+), 103 deletions(-) create mode 100644 src/Projections/Directory.Build.targets create mode 100644 src/RunCsWinRTGeneratorTask/RunCsWinRTForwarderImplGenerator.cs create mode 100644 src/RunCsWinRTGeneratorTask/RunCsWinRTMergedProjectionGenerator.cs diff --git a/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets b/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets index 1c3ad8a56..09084f99c 100644 --- a/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets +++ b/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets @@ -8,7 +8,9 @@ Copyright (C) Microsoft Corporation. All rights reserved. $(SolutionDir)RunCsWinRTGeneratorTask\bin\$(Configuration)\netstandard2.0\RunCsWinRTGeneratorTask.dll - $(SolutionDir)WinRT.Interop.Generator\bin\$(Configuration)\net10.0\ + $(SolutionDir)WinRT.Interop.Generator\bin\$(Configuration)\net10.0\ + $(SolutionDir)WinRT.Impl.Generator\bin\$(Configuration)\net10.0\ + $(SolutionDir)WinRT.Projection.Generator\bin\$(Configuration)\net10.0\ AnyCPU <_TargetPlatformVersionUsesCsWinRT3>true @@ -47,17 +49,31 @@ Copyright (C) Microsoft Corporation. All rights reserved. --> <_RunCsWinRTGeneratorPropertyInputsCachePath Condition="'$(_RunCsWinRTGeneratorPropertyInputsCachePath)' == ''">$(IntermediateOutputPath)$(MSBuildProjectName).cswinrtgen.cache <_RunCsWinRTGeneratorPropertyInputsCachePath>$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '$(_RunCsWinRTGeneratorPropertyInputsCachePath)')) + + + $(IntermediateOutputPath)\runtimes\ + <_CsWinRTGeneratorForwarderAssemblyPath>$(CsWinRTGeneratorForwarderAssemblyDirectory)$(AssemblyName).dll + <_CsWinRTGeneratorForwarderOutputAssemblyPath>runtimes\$(AssemblyName).dll + + + <_CsWinRTGeneratorMergedProjectionAssemblyPath>$(IntermediateOutputPath)WinRT.Projection.dll + <_CsWinRTGeneratorMergedProjectionOutputAssemblyPath>WinRT.Projection.dll + + $(DefineConstants);CSWINRT_REFERENCE_PROJECTION - + + <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTEffectiveToolsDirectory)" /> @@ -125,17 +156,17 @@ Copyright (C) Microsoft Corporation. All rights reserved. Name="_RunCsWinRTGenerator" DependsOnTargets="CoreCompile;$(GetTargetPathDependsOn);$(GetTargetPathWithTargetPlatformMonikerDependsOn);_ComputeRunCsWinRTGeneratorCache" BeforeTargets="GetTargetPath;GetTargetPathWithTargetPlatformMoniker;GenerateBuildDependencyFile;GeneratePublishDependencyFile;GetCopyToOutputDirectoryItems;GetCopyToPublishDirectoryItems" - Inputs="@(ReferencePath);@(IntermediateAssembly);$(_RunCsWinRTGeneratorPropertyInputsCachePath)" + Inputs="@(ReferencePathWithRefAssemblies);@(IntermediateAssembly);$(_RunCsWinRTGeneratorPropertyInputsCachePath)" Outputs="$(_CsWinRTGeneratorInteropAssemblyPath)" Condition="'$(CsWinRTGenerateInteropAssembly)' == 'true'"> + + + + + + + + + + + + + + $(AssemblyName) + .NETCoreApp + false + true + true + _RunCsWinRTForwarderImplGenerator + AnyCPU + + + + + + + + <_SourceItemsToCopyToOutputDirectory + Include="@(CsWinRTGeneratorForwarderAssemblyPath)" + TargetPath="$(_CsWinRTGeneratorForwarderOutputAssemblyPath)" /> + + + + + + + + + + + + + + + + $(TargetPlatformMoniker) + $(TargetPlatformIdentifier) + $(TargetFrameworkIdentifier) + $(TargetFrameworkVersion.TrimStart('vV')) + $([MSBuild]::NormalizePath('$(OutputPath)', '$(AssemblyName).dll')) + @(CopyUpToDateMarker) + @(CsWinRTInputs) + + + + + + + + + + <_ReferencePathsWithWinMDs Include="@(ReferencePathWithRefAssemblies)" Condition="'%(ReferencePathWithRefAssemblies.CsWinRTInputs)' != ''" /> + <_WinMDPathsList Include="$([MSBuild]::ValueOrDefault('%(_ReferencePathsWithWinMDs.CsWinRTInputs)', '').Split(';'))" + Condition="'%(_ReferencePathsWithWinMDs.CsWinRTInputs)' != ''" /> + <_WinMDPathsList Include="$(CsWinRTInteropMetadata)" /> + + + + + + + + + WinRT.Projection + .NETCoreApp + false + true + true + _RunCsWinRTMergedProjectionGenerator + AnyCPU + + + + + + + + + + + + <_SourceItemsToCopyToOutputDirectory + Include="@(CsWinRTGeneratorMergedProjectionPath)" + TargetPath="$(_CsWinRTGeneratorMergedProjectionOutputAssemblyPath)" /> + + + + + + + + \ No newline at end of file diff --git a/nuget/Microsoft.Windows.CsWinRT.targets b/nuget/Microsoft.Windows.CsWinRT.targets index ae18e9af7..bf5bba3f4 100644 --- a/nuget/Microsoft.Windows.CsWinRT.targets +++ b/nuget/Microsoft.Windows.CsWinRT.targets @@ -13,6 +13,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. false false true + false true + false + true + true diff --git a/src/Projections/Directory.Build.targets b/src/Projections/Directory.Build.targets new file mode 100644 index 000000000..6735ca596 --- /dev/null +++ b/src/Projections/Directory.Build.targets @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/RunCsWinRTGeneratorTask/RunCsWinRTForwarderImplGenerator.cs b/src/RunCsWinRTGeneratorTask/RunCsWinRTForwarderImplGenerator.cs new file mode 100644 index 000000000..8be44a574 --- /dev/null +++ b/src/RunCsWinRTGeneratorTask/RunCsWinRTForwarderImplGenerator.cs @@ -0,0 +1,209 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.NET.Build.Tasks; + +/// +/// The custom MSBuild task that invokes the 'cswinrtimplgen' tool. +/// +public sealed class RunCsWinRTForwarderImplGenerator : ToolTask +{ + /// + /// Gets or sets the paths to assembly files that are reference assemblies, representing + /// the entire surface area for compilation. These assemblies are the full set of assemblies + /// that will contribute to the interop .dll being generated. + /// + [Required] + public ITaskItem[]? ReferenceAssemblyPaths { get; set; } + + /// + /// Gets or sets the path to the output assembly that was produced by the build (for the current project). + /// + /// + /// This property is an array, but it should only ever receive a single item. + /// + [Required] + public ITaskItem[]? OutputAssemblyPath { get; set; } + + /// + /// Gets or sets the directory where the generated assembly will be placed. + /// + [Required] + public string? GeneratedAssemblyDirectory { get; set; } + + /// + /// Gets or sets the path to the assembly originator key file containing the key to sign the output assembly, if any. + /// + public string? AssemblyOriginatorKeyFile { get; set; } + + /// + /// Gets or sets the tools directory where the 'cswinrtimplgen' tool is located. + /// + [Required] + public string? CsWinRTToolsDirectory { get; set; } + + /// + /// Gets or sets the architecture of 'cswinrtimplgen' to use. + /// + /// + /// If not set, the architecture will be determined based on the current process architecture. + /// + public string? CsWinRTToolsArchitecture { get; set; } + + /// + /// Gets whether to treat warnings coming from 'cswinrtgen' as errors (regardless of the global 'TreatWarningsAsErrors' setting). + /// + public bool TreatWarningsAsErrors { get; set; } = false; + + /// + /// Gets or sets additional arguments to pass to the tool. + /// + public ITaskItem[]? AdditionalArguments { get; set; } + + /// + protected override string ToolName => "cswinrtimplgen.exe"; + + /// + /// Gets the effective item spec for the output assembly. + /// + private string EffectiveOutputAssemblyItemSpec => OutputAssemblyPath![0].ItemSpec; + + /// +#if NET10_0_OR_GREATER + [MemberNotNullWhen(true, nameof(ReferenceAssemblyPaths))] + [MemberNotNullWhen(true, nameof(OutputAssemblyPath))] + [MemberNotNullWhen(true, nameof(GeneratedAssemblyDirectory))] + [MemberNotNullWhen(true, nameof(CsWinRTToolsDirectory))] +#endif + protected override bool ValidateParameters() + { + if (!base.ValidateParameters()) + { + return false; + } + + if (ReferenceAssemblyPaths is not { Length: > 0 }) + { + Log.LogWarning("Invalid 'ReferenceAssemblyPaths' input(s)."); + + return false; + } + + if (OutputAssemblyPath is not { Length: 1 }) + { + Log.LogWarning("Invalid 'OutputAssemblyPath' input."); + + return false; + } + + if (GeneratedAssemblyDirectory is null || !Directory.Exists(GeneratedAssemblyDirectory)) + { + Log.LogWarning("Generated assembly directory '{0}' is invalid or does not exist.", GeneratedAssemblyDirectory); + + return false; + } + + if (CsWinRTToolsDirectory is null || !Directory.Exists(CsWinRTToolsDirectory)) + { + Log.LogWarning("Tools directory '{0}' is invalid or does not exist.", CsWinRTToolsDirectory); + + return false; + } + + if (CsWinRTToolsArchitecture is not null && + !CsWinRTToolsArchitecture.Equals("x86", StringComparison.OrdinalIgnoreCase) && + !CsWinRTToolsArchitecture.Equals("x64", StringComparison.OrdinalIgnoreCase) && + !CsWinRTToolsArchitecture.Equals("arm64", StringComparison.OrdinalIgnoreCase) && + !CsWinRTToolsArchitecture.Equals("AnyCPU", StringComparison.OrdinalIgnoreCase)) + { + Log.LogWarning("Tools architecture '{0}' is invalid (it must be 'x86', 'x64', 'arm64', or 'AnyCPU').", CsWinRTToolsArchitecture); + + return false; + } + + return true; + } + + /// + [SuppressMessage("Style", "IDE0072", Justification = "We always use 'x86' as a fallback for all other CPU architectures.")] + protected override string GenerateFullPathToTool() + { + string? effectiveArchitecture = CsWinRTToolsArchitecture; + + // Special case for when 'AnyCPU' is specified (mostly for testing scenarios). + // We just reuse the exact input directory and assume the architecture matches. + // This makes it easy to run the task against a local build of 'cswinrtgen'. + if (effectiveArchitecture?.Equals("AnyCPU", StringComparison.OrdinalIgnoreCase) is true) + { + return Path.Combine(CsWinRTToolsDirectory!, ToolName); + } + + // If the architecture is not specified, determine it based on the current process architecture + effectiveArchitecture ??= RuntimeInformation.ProcessArchitecture switch + { + Architecture.X64 => "x64", + Architecture.Arm64 => "arm64", + _ => "x86" + }; + + // The tool is inside an architecture-specific subfolder, as it's a native binary + string architectureDirectory = $"win-{effectiveArchitecture}"; + + return Path.Combine(CsWinRTToolsDirectory!, architectureDirectory, ToolName); + } + + /// + protected override string GenerateResponseFileCommands() + { + StringBuilder args = new(); + + IEnumerable referenceAssemblyPaths = ReferenceAssemblyPaths!.Select(static path => path.ItemSpec); + string referenceAssemblyPathsArg = string.Join(",", referenceAssemblyPaths); + + AppendResponseFileCommand(args, "--reference-assembly-paths", referenceAssemblyPathsArg); + AppendResponseFileCommand(args, "--output-assembly-path", EffectiveOutputAssemblyItemSpec); + AppendResponseFileCommand(args, "--generated-assembly-directory", GeneratedAssemblyDirectory!); + AppendResponseFileOptionalCommand(args, "--assembly-originator-key-file", AssemblyOriginatorKeyFile); + AppendResponseFileCommand(args, "--treat-warnings-as-errors", TreatWarningsAsErrors.ToString()); + + // Add any additional arguments that are not statically known + foreach (ITaskItem additionalArgument in AdditionalArguments ?? []) + { + _ = args.AppendLine(additionalArgument.ItemSpec); + } + + return args.ToString(); + } + + /// + /// Appends a command line argument to the response file arguments, with the right format. + /// + /// The command line arguments being built. + /// The command name to append. + /// The command value to append. + private static void AppendResponseFileCommand(StringBuilder args, string commandName, string commandValue) + { + _ = args.Append($"{commandName} ").AppendLine(commandValue); + } + + /// + /// Appends an optional command line argument to the response file arguments, with the right format. + /// + /// The command line arguments being built. + /// The command name to append. + /// The optional command value to append. + /// This method will not append the command if is . + private static void AppendResponseFileOptionalCommand(StringBuilder args, string commandName, string? commandValue) + { + if (commandValue is not null) + { + AppendResponseFileCommand(args, commandName, commandValue); + } + } +} \ No newline at end of file diff --git a/src/RunCsWinRTGeneratorTask/RunCsWinRTMergedProjectionGenerator.cs b/src/RunCsWinRTGeneratorTask/RunCsWinRTMergedProjectionGenerator.cs new file mode 100644 index 000000000..a98e8e5fe --- /dev/null +++ b/src/RunCsWinRTGeneratorTask/RunCsWinRTMergedProjectionGenerator.cs @@ -0,0 +1,198 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.NET.Build.Tasks; + +/// +/// The custom MSBuild task that invokes the 'cswinrtprojectiongen' tool. +/// +public sealed class RunCsWinRTMergedProjectionGenerator : ToolTask +{ + /// + /// Gets or sets the paths to assembly files that are reference assemblies, representing + /// the entire surface area for compilation. These assemblies are the full set of assemblies + /// that will contribute to the interop .dll being generated. + /// + [Required] + public ITaskItem[]? ReferenceAssemblyPaths { get; set; } + + /// + /// Gets or sets the directory where the generated assembly will be placed. + /// + [Required] + public string? GeneratedAssemblyDirectory { get; set; } + + /// + /// Gets or sets the paths to the WinMD files needed by the cswinrt tool + /// to generate the merged projection. + /// + [Required] + public string[]? WinMDPaths { get; set; } + + /// + /// Gets or sets the target framework for which the projection is being generated. + /// + public string? TargetFramework { get; set; } + + /// + /// Gets or sets the target Windows WinMD or version for which the projection targets. + /// + public string? WindowsMetadata { get; set; } + + /// + /// Gets or sets the path to the cswinrt.exe tool. + /// + public string? CsWinRTExePath { get; set; } + + /// + /// Gets or sets the tools directory where the 'cswinrtprojectiongen' tool is located. + /// + [Required] + public string? CsWinRTToolsDirectory { get; set; } + + /// + /// Gets or sets the architecture of 'cswinrtprojectiongen' to use. + /// + /// + /// If not set, the architecture will be determined based on the current process architecture. + /// + public string? CsWinRTToolsArchitecture { get; set; } + + /// + /// Gets or sets additional arguments to pass to the tool. + /// + public ITaskItem[]? AdditionalArguments { get; set; } + + /// + protected override string ToolName => "cswinrtprojectiongen.exe"; + + /// +#if NET10_0_OR_GREATER + [MemberNotNullWhen(true, nameof(ReferenceAssemblyPaths))] + [MemberNotNullWhen(true, nameof(GeneratedAssemblyDirectory))] + [MemberNotNullWhen(true, nameof(WinMDPaths))] + [MemberNotNullWhen(true, nameof(TargetFramework))] + [MemberNotNullWhen(true, nameof(WindowsMetadata))] + [MemberNotNullWhen(true, nameof(CsWinRTPath))] + [MemberNotNullWhen(true, nameof(CsWinRTToolsDirectory))] +#endif + protected override bool ValidateParameters() + { + if (!base.ValidateParameters()) + { + return false; + } + + if (ReferenceAssemblyPaths is not { Length: > 0 }) + { + Log.LogWarning("Invalid 'ReferenceAssemblyPaths' input(s)."); + + return false; + } + + if (GeneratedAssemblyDirectory is null || !Directory.Exists(GeneratedAssemblyDirectory)) + { + Log.LogWarning("Generated assembly directory '{0}' is invalid or does not exist.", GeneratedAssemblyDirectory); + + return false; + } + + if (WinMDPaths is not { Length: > 0 }) + { + Log.LogWarning("Invalid 'WinMDPaths' input(s)."); + + return false; + } + + if (CsWinRTToolsDirectory is null || !Directory.Exists(CsWinRTToolsDirectory)) + { + Log.LogWarning("Tools directory '{0}' is invalid or does not exist.", CsWinRTToolsDirectory); + + return false; + } + + if (CsWinRTToolsArchitecture is not null && + !CsWinRTToolsArchitecture.Equals("x86", StringComparison.OrdinalIgnoreCase) && + !CsWinRTToolsArchitecture.Equals("x64", StringComparison.OrdinalIgnoreCase) && + !CsWinRTToolsArchitecture.Equals("arm64", StringComparison.OrdinalIgnoreCase) && + !CsWinRTToolsArchitecture.Equals("AnyCPU", StringComparison.OrdinalIgnoreCase)) + { + Log.LogWarning("Tools architecture '{0}' is invalid (it must be 'x86', 'x64', 'arm64', or 'AnyCPU').", CsWinRTToolsArchitecture); + + return false; + } + + return true; + } + + /// + [SuppressMessage("Style", "IDE0072", Justification = "We always use 'x86' as a fallback for all other CPU architectures.")] + protected override string GenerateFullPathToTool() + { + string? effectiveArchitecture = CsWinRTToolsArchitecture; + + // Special case for when 'AnyCPU' is specified (mostly for testing scenarios). + // We just reuse the exact input directory and assume the architecture matches. + // This makes it easy to run the task against a local build of 'cswinrtgen'. + if (effectiveArchitecture?.Equals("AnyCPU", StringComparison.OrdinalIgnoreCase) is true) + { + return Path.Combine(CsWinRTToolsDirectory!, ToolName); + } + + // If the architecture is not specified, determine it based on the current process architecture + effectiveArchitecture ??= RuntimeInformation.ProcessArchitecture switch + { + Architecture.X64 => "x64", + Architecture.Arm64 => "arm64", + _ => "x86" + }; + + // The tool is inside an architecture-specific subfolder, as it's a native binary + string architectureDirectory = $"win-{effectiveArchitecture}"; + + return Path.Combine(CsWinRTToolsDirectory!, architectureDirectory, ToolName); + } + + /// + protected override string GenerateResponseFileCommands() + { + StringBuilder args = new(); + + IEnumerable referenceAssemblyPaths = ReferenceAssemblyPaths!.Select(static path => path.ItemSpec); + string referenceAssemblyPathsArg = string.Join(",", referenceAssemblyPaths); + + string winMDPathsArg = string.Join(",", WinMDPaths); + + AppendResponseFileCommand(args, "--reference-assembly-paths", referenceAssemblyPathsArg); + AppendResponseFileCommand(args, "--generated-assembly-directory", GeneratedAssemblyDirectory!); + AppendResponseFileCommand(args, "--winmd-paths", winMDPathsArg); + AppendResponseFileCommand(args, "--target-framework", TargetFramework!); + AppendResponseFileCommand(args, "--windows-metadata", WindowsMetadata!); + AppendResponseFileCommand(args, "--cswinrt-exe-path", CsWinRTExePath!); + + // Add any additional arguments that are not statically known + foreach (ITaskItem additionalArgument in AdditionalArguments ?? []) + { + _ = args.AppendLine(additionalArgument.ItemSpec); + } + + return args.ToString(); + } + + /// + /// Appends a command line argument to the response file arguments, with the right format. + /// + /// The command line arguments being built. + /// The command name to append. + /// The command value to append. + private static void AppendResponseFileCommand(StringBuilder args, string commandName, string commandValue) + { + _ = args.Append($"{commandName} ").AppendLine(commandValue); + } +} \ No newline at end of file diff --git a/src/Tests/FunctionalTests/Directory.Build.props b/src/Tests/FunctionalTests/Directory.Build.props index 52aa9bac2..4a79cf33e 100644 --- a/src/Tests/FunctionalTests/Directory.Build.props +++ b/src/Tests/FunctionalTests/Directory.Build.props @@ -1,9 +1,12 @@ + + true + + - true true false true diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index e3f4d8ff9..febf5bb34 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -245,7 +245,7 @@ private static void EmitTypeForwards(ModuleDefinition inputModule, ModuleDefinit // We need an assembly reference for the merged projection .dll that will be generated. // The version doesn't matter here (as long as it's not '255.255.255.255'). The real .dll // will always have a version number equal or higher than this, so it will load correctly. - AssemblyReference projectionAssembly = new("WinRT.Projection.dll"u8, new Version(0, 0, 0, 0)) + AssemblyReference projectionAssembly = new("WinRT.Projection"u8, new Version(0, 0, 0, 0)) { PublicKeyOrToken = ImplValues.PublicKeyData, HasPublicKey = true @@ -268,6 +268,19 @@ private static void EmitTypeForwards(ModuleDefinition inputModule, ModuleDefinit Attributes = AsmResolver.PE.DotNet.Metadata.Tables.TypeAttributes.Forwarder }); } + + TypeReference interfaceIIDsTypeReference = inputModule.CreateTypeReference("ABI"u8, "InterfaceIIDs"u8); + if (interfaceIIDsTypeReference.Resolve() is TypeDefinition interfaceIIDsTypeDefinition) + { + // Forward the 'ABI.InterfaceIIDs' type as well, as it's used for IID resolution at runtime + implModule.ExportedTypes.Add(new ExportedType( + implementation: projectionAssembly.ImportWith(implModule.DefaultImporter), + ns: interfaceIIDsTypeDefinition.Namespace, + name: interfaceIIDsTypeDefinition.Name) + { + Attributes = AsmResolver.PE.DotNet.Metadata.Tables.TypeAttributes.Forwarder + }); + } } catch (Exception e) { diff --git a/src/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs b/src/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs index adb6e8f07..61ed14041 100644 --- a/src/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs +++ b/src/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs @@ -40,5 +40,7 @@ public static void AssemblyAttributes( // Create the attribute and add it to the assembly module.Assembly!.CustomAttributes.Add(InteropCustomAttributeFactory.IgnoresAccessChecksTo(assemblyName, interopDefinitions, module)); } + + module.Assembly!.CustomAttributes.Add(InteropCustomAttributeFactory.IgnoresAccessChecksTo("WinRT.Projection", interopDefinitions, module)); } } \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.UserDefinedType.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.UserDefinedType.cs index 9e7a5b156..a78e892eb 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.UserDefinedType.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.UserDefinedType.cs @@ -118,13 +118,13 @@ public static void InterfaceEntriesImpl( { // Finally, we have the base scenario of simple non-generic projected Windows Runtime interface types. In this // case, the marshalling code will just be in the declaring assembly of each of these projected interface types. - TypeReference ImplTypeReference = interfaceType.DeclaringModule!.CreateTypeReference($"ABI.{typeSignature.Namespace}", $"{typeSignature.Name}Impl"); + TypeReference ImplTypeReference = interopReferences.WinRTProjection.CreateTypeReference($"ABI.{typeSignature.Namespace}", $"{typeSignature.Name}Impl"); MemberReference get_VtableMethod = ImplTypeReference.CreateMemberReference("get_Vtable"u8, MethodSignature.CreateStatic(interopReferences.CorLibTypeFactory.IntPtr)); // For normal projected types, the IID is in the generated 'InterfaceIIDs' type in the containing assembly string get_IIDMethodName = $"get_IID_{typeSignature.FullName.Replace('.', '_')}"; TypeSignature get_IIDMethodReturnType = WellKnownTypeSignatureFactory.InGuid(interopReferences); - TypeReference interfaceIIDsTypeReference = interfaceType.DeclaringModule!.CreateTypeReference("ABI"u8, "InterfaceIIDs"u8); + TypeReference interfaceIIDsTypeReference = interopReferences.WinRTProjection.CreateTypeReference("ABI"u8, "InterfaceIIDs"u8); MemberReference get_IIDMethod = interfaceIIDsTypeReference.CreateMemberReference(get_IIDMethodName, MethodSignature.CreateStatic(get_IIDMethodReturnType)); // Add the entry from the ABI type in the same declaring assembly diff --git a/src/WinRT.Interop.Generator/Helpers/GuidGenerator.cs b/src/WinRT.Interop.Generator/Helpers/GuidGenerator.cs index ffdd900a1..cd346796a 100644 --- a/src/WinRT.Interop.Generator/Helpers/GuidGenerator.cs +++ b/src/WinRT.Interop.Generator/Helpers/GuidGenerator.cs @@ -10,7 +10,6 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using WindowsRuntime.InteropGenerator.References; -using WindowsRuntime.InteropGenerator.Resolvers; namespace WindowsRuntime.InteropGenerator.Helpers; @@ -58,15 +57,6 @@ public static bool TryGetIIDFromWellKnownInterfaceIIDsOrAttribute(ITypeDescripto if (type.Resolve() is TypeDefinition typeDefinition) { - // For delegate types, we resolve the IID from their generated RVA field. - // Only interface types have the '[Guid]' attribute on them, not delegates. - if (typeDefinition.IsClass && typeDefinition.IsDelegate) - { - iid = InterfaceIIDResolver.GetIID(typeDefinition); - - return true; - } - // If the type was a normal projected type, then try to resolve the IID from the '[Guid]' attribute if (TryGetIIDFromAttribute(typeDefinition, interopReferences, out iid)) { diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index 3ab5f8a10..3fe3d19a7 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -88,6 +88,15 @@ public InteropReferences( publicKey: false, publicKeyOrToken: WellKnownPublicKeyTokens.SystemMemory); + /// + /// Gets the for WinRT.Projection.dll. + /// + public AssemblyReference WinRTProjection => field ??= new AssemblyReference( + name: "WinRT.Projection"u8, + version: new Version(0, 0, 0, 0), + publicKey: false, + publicKeyOrToken: default); + /// /// Gets the for . /// diff --git a/src/cswinrt.slnx b/src/cswinrt.slnx index 7db15b0a3..d8b3d7469 100644 --- a/src/cswinrt.slnx +++ b/src/cswinrt.slnx @@ -356,6 +356,7 @@ + diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index 13ff88e44..508b7a8e6 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -4703,8 +4703,24 @@ R"(#pragma warning disable IL2026 type.TypeNamespace(), type.TypeName()); } + void write_class_winrt_classname_attribute(writer& w, TypeDef const& type) + { + if (settings.reference_projection) + { + return; + } + + w.write("[WindowsRuntimeClassName(%.RuntimeClassName)]\n", + bind(type, typedef_name_type::ABI, false)); + } + void write_comwrapper_marshaller_attribute(writer& w, TypeDef const& type) { + if (settings.reference_projection) + { + return; + } + w.write("[ABI.%.%ComWrappersMarshaller]\n", type.TypeNamespace(), type.TypeName()); } @@ -8614,8 +8630,7 @@ return MarshalInspectable<%>.FromAbi(thisPtr); auto gc_pressure_amount = get_gc_pressure_amount(type); w.write(R"( -%[WindowsRuntimeClassName(%.RuntimeClassName)] -%%%% %class %% +%%%%%% %class %% { % @@ -8632,7 +8647,7 @@ return MarshalInspectable<%>.FromAbi(thisPtr); } )", bind(type), - bind(type, typedef_name_type::ABI, false), + bind(type), bind(type, true), bind(type), bind(type), @@ -8908,11 +8923,19 @@ return false; { method_signature signature{ get_delegate_invoke(type) }; w.write(R"( -%%%% delegate % %(%); +%%%%% delegate % %(%); )", bind(type), bind(type, false), bind(type), + bind([&](writer& w) + { + if (settings.reference_projection) + { + write_guid_attribute(w, type); + w.write("\n"); + } + }), internal_accessibility(), bind(signature), bind(type, typedef_name_type::Projected, false), diff --git a/src/cswinrt/main.cpp b/src/cswinrt/main.cpp index ceb34c7d9..ad26c075e 100644 --- a/src/cswinrt/main.cpp +++ b/src/cswinrt/main.cpp @@ -43,6 +43,7 @@ namespace cswinrt { "public_exclusiveto", 0, 0, {}, "Make exclusiveto interfaces public in the projection (default is internal)"}, { "idic_exclusiveto", 0, 0, {}, "Make exclusiveto interfaces support IDynamicInterfaceCastable (IDIC) for RCW scenarios (default is false)"}, { "partial_factory", 0, 0, {}, "Allows to provide an additional component activation factory (default is false)"}, + { "reference_projection", 0, 0, {}, "Generates a projection to be used as a reference assembly (default is false)"}, { "help", 0, option::no_max, {}, "Show detailed help" }, { "?", 0, option::no_max, {}, {} }, }; @@ -112,6 +113,7 @@ Where is one or more of: settings.public_exclusiveto = args.exists("public_exclusiveto"); settings.idic_exclusiveto = args.exists("idic_exclusiveto"); settings.partial_factory = args.exists("partial_factory"); + settings.reference_projection = args.exists("reference_projection"); settings.input = args.files("input", database::is_database); for (auto && include : args.values("include")) @@ -251,58 +253,62 @@ Where is one or more of: writer w(ns); writer helperWriter("WinRT"); w.write_begin(); - for (auto&& [name, type] : members.types) + + if (!settings.reference_projection) { - currentType = name; - if (!settings.filter.includes(type)) { continue; } - if (distance(type.GenericParam()) != 0) { continue; } - if (auto mapping = get_mapped_type(ns, name)) + for (auto&& [name, type] : members.types) { - if (!mapping->emit_abi) + currentType = name; + if (!settings.filter.includes(type)) { continue; } + if (distance(type.GenericParam()) != 0) { continue; } + if (auto mapping = get_mapped_type(ns, name)) { - continue; + if (!mapping->emit_abi) + { + continue; + } } - } - auto guard{ w.push_generic_params(type.GenericParam()) }; - auto guard1{ helperWriter.push_generic_params(type.GenericParam()) }; + auto guard{ w.push_generic_params(type.GenericParam()) }; + auto guard1{ helperWriter.push_generic_params(type.GenericParam()) }; - switch (get_category(type)) - { - case category::class_type: - // For both static and attributes, we don't need to pass them across the ABI. - if (!is_static(type) && - !is_attribute_type(type)) - { - write_winrt_comwrappers_typemapgroup_assembly_attribute(w, type, false); - } - break; - case category::delegate_type: - write_winrt_comwrappers_typemapgroup_assembly_attribute(w, type, true); - break; - case category::enum_type: - write_winrt_comwrappers_typemapgroup_assembly_attribute(w, type, true); - break; - case category::interface_type: - write_winrt_idic_typemapgroup_assembly_attribute(w, type); - break; - case category::struct_type: - // Similarly for API contracts, we don't expect them to be passed across the ABI. - if (!is_api_contract_type(type)) + switch (get_category(type)) { + case category::class_type: + // For both static and attributes, we don't need to pass them across the ABI. + if (!is_static(type) && + !is_attribute_type(type)) + { + write_winrt_comwrappers_typemapgroup_assembly_attribute(w, type, false); + } + break; + case category::delegate_type: write_winrt_comwrappers_typemapgroup_assembly_attribute(w, type, true); + break; + case category::enum_type: + write_winrt_comwrappers_typemapgroup_assembly_attribute(w, type, true); + break; + case category::interface_type: + write_winrt_idic_typemapgroup_assembly_attribute(w, type); + break; + case category::struct_type: + // Similarly for API contracts, we don't expect them to be passed across the ABI. + if (!is_api_contract_type(type)) + { + write_winrt_comwrappers_typemapgroup_assembly_attribute(w, type, true); + } + break; } - break; } - } - // Attributes need to be written at the start, so handling this addition separately. - if (ns == "Windows.Storage.Streams" && settings.addition_filter.includes(ns)) - { - w.write(R"( + // Attributes need to be written at the start, so handling this addition separately. + if (ns == "Windows.Storage.Streams" && settings.addition_filter.includes(ns)) + { + w.write(R"( [assembly: TypeMapAssociation( typeof(global::System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeBuffer), typeof(global::ABI.System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeBuffer))] )"); + } } currentType = ""; @@ -370,50 +376,54 @@ Where is one or more of: if (written) { w.write_end_projected(); - w.write_begin_abi(); - for (auto&& [name, type] : members.types) + if (!settings.reference_projection) { - currentType = name; - if (!settings.filter.includes(type)) { continue; } - if (distance(type.GenericParam()) != 0) { continue; } - if (auto mapping = get_mapped_type(ns, name)) + w.write_begin_abi(); + + for (auto&& [name, type] : members.types) { - if (!mapping->emit_abi) + currentType = name; + if (!settings.filter.includes(type)) { continue; } + if (distance(type.GenericParam()) != 0) { continue; } + if (auto mapping = get_mapped_type(ns, name)) { - continue; + if (!mapping->emit_abi) + { + continue; + } } - } - if (is_api_contract_type(type)) { continue; } - if (is_attribute_type(type)) { continue; } - auto guard{ w.push_generic_params(type.GenericParam()) }; + if (is_api_contract_type(type)) { continue; } + if (is_attribute_type(type)) { continue; } + auto guard{ w.push_generic_params(type.GenericParam()) }; - switch (get_category(type)) - { - case category::class_type: - write_abi_class(w, type); - if (settings.component && componentActivatableClasses.count(type) == 1) + switch (get_category(type)) { - write_winrt_exposed_type_class(w, type, true); + case category::class_type: + write_abi_class(w, type); + if (settings.component && componentActivatableClasses.count(type) == 1) + { + write_winrt_exposed_type_class(w, type, true); + } + break; + case category::delegate_type: + write_abi_delegate(w, type); + write_temp_delegate_event_source_subclass(w, type); + break; + case category::enum_type: + write_abi_enum(w, type); + break; + case category::interface_type: + write_abi_interface(w, type); + break; + case category::struct_type: + write_abi_struct(w, type); + break; } - break; - case category::delegate_type: - write_abi_delegate(w, type); - write_temp_delegate_event_source_subclass(w, type); - break; - case category::enum_type: - write_abi_enum(w, type); - break; - case category::interface_type: - write_abi_interface(w, type); - break; - case category::struct_type: - write_abi_struct(w, type); - break; } + w.write_end_abi(); } - w.write_end_abi(); currentType = ""; @@ -544,6 +554,25 @@ ComWrappersSupport.RegisterAuthoringMetadataTypeLookup(new Func(GetM } writer ws; write_file_header(ws); + + if (std::string(string.name) == "ComInteropHelpers") + { + // Determine which COM interop helpers to include by checking whether the newer + // types used in the COM interop helpers are being projected or not. + // The ComInteropHelpers file makes use of UAC_VERSION_* to conditionally + // include the ones that are being projected. + int uapContractversion = 7; // default to 17763 + if (c.find("Windows.Graphics.Display.DisplayInformation")) + { + uapContractversion = 15; + } + + ws.write(R"( +#define UAC_VERSION_% +)", + uapContractversion); + } + ws.write(string.value); ws.flush_to_file(settings.output_folder / (std::string(string.name) + ".cs")); } diff --git a/src/cswinrt/settings.h b/src/cswinrt/settings.h index 8fd59dc73..8ef1c12ca 100644 --- a/src/cswinrt/settings.h +++ b/src/cswinrt/settings.h @@ -20,6 +20,7 @@ namespace cswinrt bool public_exclusiveto{}; bool idic_exclusiveto{}; bool partial_factory{}; + bool reference_projection{}; }; extern settings_type settings; diff --git a/src/cswinrt/strings/additions/Microsoft.UI.Xaml.Media.Animation/Microsoft.UI.Xaml.Media.Animation.KeyTime.cs b/src/cswinrt/strings/additions/Microsoft.UI.Xaml.Media.Animation/Microsoft.UI.Xaml.Media.Animation.KeyTime.cs index 7756cc860..1fcbfe17c 100644 --- a/src/cswinrt/strings/additions/Microsoft.UI.Xaml.Media.Animation/Microsoft.UI.Xaml.Media.Animation.KeyTime.cs +++ b/src/cswinrt/strings/additions/Microsoft.UI.Xaml.Media.Animation/Microsoft.UI.Xaml.Media.Animation.KeyTime.cs @@ -5,7 +5,9 @@ namespace Microsoft.UI.Xaml.Media.Animation [WindowsRuntimeMetadata("Microsoft.UI")] [WindowsRuntimeClassName("Windows.Foundation.IReference")] +#if !CSWINRT_REFERENCE_PROJECTION [ABI.Microsoft.UI.Xaml.Media.Animation.KeyTimeComWrappersMarshaller] +#endif [StructLayout(LayoutKind.Sequential)] public readonly struct KeyTime : IEquatable { diff --git a/src/cswinrt/strings/additions/Microsoft.UI.Xaml.Media.Animation/Microsoft.UI.Xaml.Media.Animation.RepeatBehavior.cs b/src/cswinrt/strings/additions/Microsoft.UI.Xaml.Media.Animation/Microsoft.UI.Xaml.Media.Animation.RepeatBehavior.cs index 2e88be353..c7137c97e 100644 --- a/src/cswinrt/strings/additions/Microsoft.UI.Xaml.Media.Animation/Microsoft.UI.Xaml.Media.Animation.RepeatBehavior.cs +++ b/src/cswinrt/strings/additions/Microsoft.UI.Xaml.Media.Animation/Microsoft.UI.Xaml.Media.Animation.RepeatBehavior.cs @@ -5,7 +5,9 @@ namespace Microsoft.UI.Xaml.Media.Animation [WindowsRuntimeMetadata("Microsoft.UI")] [WindowsRuntimeClassName("Windows.Foundation.IReference")] +#if !CSWINRT_REFERENCE_PROJECTION [ABI.Microsoft.UI.Xaml.Media.Animation.RepeatBehaviorComWrappersMarshaller] +#endif [StructLayout(LayoutKind.Sequential)] public struct RepeatBehavior : IFormattable, IEquatable { diff --git a/src/cswinrt/strings/additions/Microsoft.UI.Xaml.Media.Media3D/Microsoft.UI.Xaml.Media.Media3D.Matrix3D.cs b/src/cswinrt/strings/additions/Microsoft.UI.Xaml.Media.Media3D/Microsoft.UI.Xaml.Media.Media3D.Matrix3D.cs index 68756303b..1f930c957 100644 --- a/src/cswinrt/strings/additions/Microsoft.UI.Xaml.Media.Media3D/Microsoft.UI.Xaml.Media.Media3D.Matrix3D.cs +++ b/src/cswinrt/strings/additions/Microsoft.UI.Xaml.Media.Media3D/Microsoft.UI.Xaml.Media.Media3D.Matrix3D.cs @@ -5,7 +5,9 @@ namespace Microsoft.UI.Xaml.Media.Media3D [WindowsRuntimeMetadata("Microsoft.UI")] [WindowsRuntimeClassName("Windows.Foundation.IReference")] +#if !CSWINRT_REFERENCE_PROJECTION [ABI.Microsoft.UI.Xaml.Media.Media3D.Matrix3DComWrappersMarshaller] +#endif [StructLayout(LayoutKind.Sequential)] public struct Matrix3D : IFormattable, IEquatable { diff --git a/src/cswinrt/strings/additions/Microsoft.UI.Xaml/Microsoft.UI.Xaml.CornerRadius.cs b/src/cswinrt/strings/additions/Microsoft.UI.Xaml/Microsoft.UI.Xaml.CornerRadius.cs index 4459762ea..e4a7700de 100644 --- a/src/cswinrt/strings/additions/Microsoft.UI.Xaml/Microsoft.UI.Xaml.CornerRadius.cs +++ b/src/cswinrt/strings/additions/Microsoft.UI.Xaml/Microsoft.UI.Xaml.CornerRadius.cs @@ -5,7 +5,9 @@ namespace Microsoft.UI.Xaml [WindowsRuntimeMetadata("Microsoft.UI")] [WindowsRuntimeClassName("Windows.Foundation.IReference")] +#if !CSWINRT_REFERENCE_PROJECTION [ABI.Microsoft.UI.Xaml.CornerRadiusComWrappersMarshaller] +#endif [StructLayout(LayoutKind.Sequential)] public struct CornerRadius : IEquatable { diff --git a/src/cswinrt/strings/additions/Microsoft.UI.Xaml/Microsoft.UI.Xaml.Duration.cs b/src/cswinrt/strings/additions/Microsoft.UI.Xaml/Microsoft.UI.Xaml.Duration.cs index 04a704653..1fade054e 100644 --- a/src/cswinrt/strings/additions/Microsoft.UI.Xaml/Microsoft.UI.Xaml.Duration.cs +++ b/src/cswinrt/strings/additions/Microsoft.UI.Xaml/Microsoft.UI.Xaml.Duration.cs @@ -5,7 +5,9 @@ namespace Microsoft.UI.Xaml [WindowsRuntimeMetadata("Microsoft.UI")] [WindowsRuntimeClassName("Windows.Foundation.IReference")] +#if !CSWINRT_REFERENCE_PROJECTION [ABI.Microsoft.UI.Xaml.DurationComWrappersMarshaller] +#endif [StructLayout(LayoutKind.Sequential)] public readonly struct Duration : IEquatable { diff --git a/src/cswinrt/strings/additions/Microsoft.UI.Xaml/Microsoft.UI.Xaml.GridLength.cs b/src/cswinrt/strings/additions/Microsoft.UI.Xaml/Microsoft.UI.Xaml.GridLength.cs index aa901dabe..39bc6765c 100644 --- a/src/cswinrt/strings/additions/Microsoft.UI.Xaml/Microsoft.UI.Xaml.GridLength.cs +++ b/src/cswinrt/strings/additions/Microsoft.UI.Xaml/Microsoft.UI.Xaml.GridLength.cs @@ -5,7 +5,9 @@ namespace Microsoft.UI.Xaml [WindowsRuntimeMetadata("Microsoft.UI")] [WindowsRuntimeClassName("Windows.Foundation.IReference")] +#if !CSWINRT_REFERENCE_PROJECTION [ABI.Microsoft.UI.Xaml.GridLengthComWrappersMarshaller] +#endif [StructLayout(LayoutKind.Sequential)] public readonly struct GridLength : IEquatable { diff --git a/src/cswinrt/strings/additions/Windows.UI.Xaml.Media.Animation/Windows.UI.Xaml.Media.Animation.KeyTime.cs b/src/cswinrt/strings/additions/Windows.UI.Xaml.Media.Animation/Windows.UI.Xaml.Media.Animation.KeyTime.cs index 4d4151b0d..d046aafb1 100644 --- a/src/cswinrt/strings/additions/Windows.UI.Xaml.Media.Animation/Windows.UI.Xaml.Media.Animation.KeyTime.cs +++ b/src/cswinrt/strings/additions/Windows.UI.Xaml.Media.Animation/Windows.UI.Xaml.Media.Animation.KeyTime.cs @@ -5,7 +5,9 @@ namespace Windows.UI.Xaml.Media.Animation [WindowsRuntimeMetadata("Windows.Foundation.UniversalApiContract")] [WindowsRuntimeClassName("Windows.Foundation.IReference")] +#if !CSWINRT_REFERENCE_PROJECTION [ABI.Windows.UI.Xaml.Media.Animation.KeyTimeComWrappersMarshaller] +#endif [StructLayout(LayoutKind.Sequential)] public readonly struct KeyTime : IEquatable { diff --git a/src/cswinrt/strings/additions/Windows.UI.Xaml.Media.Animation/Windows.UI.Xaml.Media.Animation.RepeatBehavior.cs b/src/cswinrt/strings/additions/Windows.UI.Xaml.Media.Animation/Windows.UI.Xaml.Media.Animation.RepeatBehavior.cs index 84f04ec05..16e346d73 100644 --- a/src/cswinrt/strings/additions/Windows.UI.Xaml.Media.Animation/Windows.UI.Xaml.Media.Animation.RepeatBehavior.cs +++ b/src/cswinrt/strings/additions/Windows.UI.Xaml.Media.Animation/Windows.UI.Xaml.Media.Animation.RepeatBehavior.cs @@ -5,7 +5,9 @@ namespace Windows.UI.Xaml.Media.Animation [WindowsRuntimeMetadata("Windows.Foundation.UniversalApiContract")] [WindowsRuntimeClassName("Windows.Foundation.IReference")] +#if !CSWINRT_REFERENCE_PROJECTION [ABI.Windows.UI.Xaml.Media.Animation.RepeatBehaviorComWrappersMarshaller] +#endif [StructLayout(LayoutKind.Sequential)] public struct RepeatBehavior : IFormattable, IEquatable { diff --git a/src/cswinrt/strings/additions/Windows.UI.Xaml.Media.Media3D/Windows.UI.Xaml.Media.Media3D.Matrix3D.cs b/src/cswinrt/strings/additions/Windows.UI.Xaml.Media.Media3D/Windows.UI.Xaml.Media.Media3D.Matrix3D.cs index 6ce09403a..9eb295dbe 100644 --- a/src/cswinrt/strings/additions/Windows.UI.Xaml.Media.Media3D/Windows.UI.Xaml.Media.Media3D.Matrix3D.cs +++ b/src/cswinrt/strings/additions/Windows.UI.Xaml.Media.Media3D/Windows.UI.Xaml.Media.Media3D.Matrix3D.cs @@ -5,7 +5,9 @@ namespace Windows.UI.Xaml.Media.Media3D [WindowsRuntimeMetadata("Windows.Foundation.UniversalApiContract")] [WindowsRuntimeClassName("Windows.Foundation.IReference")] +#if !CSWINRT_REFERENCE_PROJECTION [ABI.Windows.UI.Xaml.Media.Media3D.Matrix3DComWrappersMarshaller] +#endif [StructLayout(LayoutKind.Sequential)] public struct Matrix3D : IFormattable, IEquatable { diff --git a/src/cswinrt/strings/additions/Windows.UI.Xaml/Windows.UI.Xaml.CornerRadius.cs b/src/cswinrt/strings/additions/Windows.UI.Xaml/Windows.UI.Xaml.CornerRadius.cs index d622579de..9edb460d7 100644 --- a/src/cswinrt/strings/additions/Windows.UI.Xaml/Windows.UI.Xaml.CornerRadius.cs +++ b/src/cswinrt/strings/additions/Windows.UI.Xaml/Windows.UI.Xaml.CornerRadius.cs @@ -5,7 +5,9 @@ namespace Windows.UI.Xaml [WindowsRuntimeMetadata("Windows.Foundation.UniversalApiContract")] [WindowsRuntimeClassName("Windows.Foundation.IReference")] +#if !CSWINRT_REFERENCE_PROJECTION [ABI.Windows.UI.Xaml.CornerRadiusComWrappersMarshaller] +#endif [StructLayout(LayoutKind.Sequential)] public struct CornerRadius : IEquatable { diff --git a/src/cswinrt/strings/additions/Windows.UI.Xaml/Windows.UI.Xaml.Duration.cs b/src/cswinrt/strings/additions/Windows.UI.Xaml/Windows.UI.Xaml.Duration.cs index 5f0545960..2e68f436c 100644 --- a/src/cswinrt/strings/additions/Windows.UI.Xaml/Windows.UI.Xaml.Duration.cs +++ b/src/cswinrt/strings/additions/Windows.UI.Xaml/Windows.UI.Xaml.Duration.cs @@ -5,7 +5,9 @@ namespace Windows.UI.Xaml [WindowsRuntimeMetadata("Windows.Foundation.UniversalApiContract")] [WindowsRuntimeClassName("Windows.Foundation.IReference")] +#if !CSWINRT_REFERENCE_PROJECTION [ABI.Windows.UI.Xaml.DurationComWrappersMarshaller] +#endif [StructLayout(LayoutKind.Sequential)] public readonly struct Duration : IEquatable { diff --git a/src/cswinrt/strings/additions/Windows.UI.Xaml/Windows.UI.Xaml.GridLength.cs b/src/cswinrt/strings/additions/Windows.UI.Xaml/Windows.UI.Xaml.GridLength.cs index 24dcdfb19..4bb5b6465 100644 --- a/src/cswinrt/strings/additions/Windows.UI.Xaml/Windows.UI.Xaml.GridLength.cs +++ b/src/cswinrt/strings/additions/Windows.UI.Xaml/Windows.UI.Xaml.GridLength.cs @@ -5,7 +5,9 @@ namespace Windows.UI.Xaml [WindowsRuntimeMetadata("Windows.Foundation.UniversalApiContract")] [WindowsRuntimeClassName("Windows.Foundation.IReference")] +#if !CSWINRT_REFERENCE_PROJECTION [ABI.Windows.UI.Xaml.GridLengthComWrappersMarshaller] +#endif [StructLayout(LayoutKind.Sequential)] public readonly struct GridLength : IEquatable { From ea29f1a1b08e963b654e851ab40caf347568996e Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Fri, 5 Dec 2025 00:29:42 -0800 Subject: [PATCH 02/15] Add generator for generating merged projection --- .../CommandLineArgumentNameAttribute.cs | 19 +++ .../Errors/UnhandledImplException.cs | 38 +++++ .../Errors/WellKnownImplException.cs | 37 +++++ .../Errors/WellKnownImplExceptions.cs | 82 +++++++++ .../ProjectionGeneratorExceptionExtensions.cs | 21 +++ .../Extensions/SignatureComparerExtensions.cs | 68 ++++++++ .../Generation/ProjectionGenerator.Emit.cs | 94 +++++++++++ .../ProjectionGenerator.Generate.cs | 156 ++++++++++++++++++ .../Generation/ProjectionGenerator.cs | 52 ++++++ .../ProjectionGeneratorArgs.Parsing.cs | 134 +++++++++++++++ .../Generation/ProjectionGeneratorArgs.cs | 40 +++++ src/WinRT.Projection.Generator/Program.cs | 8 + .../Resolvers/PathAssemblyResolver.cs | 97 +++++++++++ .../WinRT.Projection.Generator.csproj | 45 +++++ 14 files changed, 891 insertions(+) create mode 100644 src/WinRT.Projection.Generator/Attributes/CommandLineArgumentNameAttribute.cs create mode 100644 src/WinRT.Projection.Generator/Errors/UnhandledImplException.cs create mode 100644 src/WinRT.Projection.Generator/Errors/WellKnownImplException.cs create mode 100644 src/WinRT.Projection.Generator/Errors/WellKnownImplExceptions.cs create mode 100644 src/WinRT.Projection.Generator/Extensions/ProjectionGeneratorExceptionExtensions.cs create mode 100644 src/WinRT.Projection.Generator/Extensions/SignatureComparerExtensions.cs create mode 100644 src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs create mode 100644 src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs create mode 100644 src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs create mode 100644 src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs create mode 100644 src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs create mode 100644 src/WinRT.Projection.Generator/Program.cs create mode 100644 src/WinRT.Projection.Generator/Resolvers/PathAssemblyResolver.cs create mode 100644 src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj diff --git a/src/WinRT.Projection.Generator/Attributes/CommandLineArgumentNameAttribute.cs b/src/WinRT.Projection.Generator/Attributes/CommandLineArgumentNameAttribute.cs new file mode 100644 index 000000000..044139902 --- /dev/null +++ b/src/WinRT.Projection.Generator/Attributes/CommandLineArgumentNameAttribute.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; + +namespace WindowsRuntime.ProjectionGenerator.Attributes; + +/// +/// An attribute indicating the name of a given command line argument. +/// +/// The command line argument name. +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] +internal sealed class CommandLineArgumentNameAttribute(string name) : Attribute +{ + /// + /// Gets the command line argument name. + /// + public string Name { get; } = name; +} diff --git a/src/WinRT.Projection.Generator/Errors/UnhandledImplException.cs b/src/WinRT.Projection.Generator/Errors/UnhandledImplException.cs new file mode 100644 index 000000000..de0df58c6 --- /dev/null +++ b/src/WinRT.Projection.Generator/Errors/UnhandledImplException.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; + +namespace WindowsRuntime.ProjectionGenerator.Errors; + +/// +/// An unhandled exception for the projection generator. +/// +internal sealed class UnhandledProjectionGeneratorException : Exception +{ + /// + /// The phase that failed. + /// + private readonly string _phase; + + /// + /// Creates a new instance with the specified parameters. + /// + /// The phase that failed. + /// The inner exception. + public UnhandledProjectionGeneratorException(string phase, Exception exception) + : base(null, exception) + { + _phase = phase; + } + + /// + public override string ToString() + { + return + $"""error {WellKnownProjectionGeneratorExceptions.ErrorPrefix}9999: The CsWinRT projection generator failed with an unhandled exception """ + + $"""('{InnerException!.GetType().Name}': '{InnerException!.Message}') during the {_phase} phase. This might be due to an invalid """ + + $"""configuration in the current project, but the generator should still correctly identify that and fail gracefully. Please open an """ + + $"""issue at https://github.com/microsoft/CsWinRT and provide a minimal repro, if possible."""; + } +} diff --git a/src/WinRT.Projection.Generator/Errors/WellKnownImplException.cs b/src/WinRT.Projection.Generator/Errors/WellKnownImplException.cs new file mode 100644 index 000000000..dda1df313 --- /dev/null +++ b/src/WinRT.Projection.Generator/Errors/WellKnownImplException.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; + +namespace WindowsRuntime.ProjectionGenerator.Errors; + +/// +/// A well known exceptions for the interop generator. +/// +internal sealed class WellKnownProjectionGeneratorException : Exception +{ + /// + /// Creates a new instance with the specified parameters. + /// + /// The id of the exception. + /// The exception message. + /// The inner exception. + public WellKnownProjectionGeneratorException(string id, string message, Exception? innerException) + : base(message, innerException) + { + Id = id; + } + + /// + /// Gets the id of the exception. + /// + public string Id { get; } + + /// + public override string ToString() + { + return InnerException is not null + ? $"""error {Id}: {Message} Inner exception: '{InnerException!.GetType().Name}': '{InnerException!.Message}'.""" + : $"""error {Id}: {Message}"""; + } +} diff --git a/src/WinRT.Projection.Generator/Errors/WellKnownImplExceptions.cs b/src/WinRT.Projection.Generator/Errors/WellKnownImplExceptions.cs new file mode 100644 index 000000000..d6c09bc28 --- /dev/null +++ b/src/WinRT.Projection.Generator/Errors/WellKnownImplExceptions.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace WindowsRuntime.ProjectionGenerator.Errors; + +/// +/// Well known exceptions for the interop generator. +/// +internal static class WellKnownProjectionGeneratorExceptions +{ + /// + /// The prefix for all errors produced by this tool. + /// + public const string ErrorPrefix = "CSWINRTPROJECTIONGEN"; + + /// + /// Some exception was thrown when trying to read the response file. + /// + public static Exception ResponseFileReadError(Exception exception) + { + return Exception(1, "Failed to read the response file to run 'cswinrtgen'.", exception); + } + + /// + /// Failed to parse an argument from the response file. + /// + public static Exception ResponseFileArgumentParsingError(string argumentName, Exception? exception = null) + { + return Exception(2, $"Failed to parse argument '{argumentName}' from response file.", exception); + } + + /// + /// The input response file is malformed. + /// + public static Exception MalformedResponseFile() + { + return Exception(3, "The response file is malformed and contains invalid content."); + } + + /// + /// Diagnostics when emitting the impl .dll to disk. + /// + public static Exception EmitDllError(IEnumerable diagnostics) + { + string combinedDiagnostics = string.Join("\n", diagnostics.Select(static diagnostic => $"{diagnostic.Severity}: '{diagnostic.GetMessage()}'")); + + return Exception(4, $"Failed to emit the projection dll.\n{combinedDiagnostics}"); + } + + /// + /// Exception when emitting the impl .dll to disk. + /// + public static Exception EmitDllError(Exception exception) + { + return Exception(5, "Failed to emit the projection dll.", exception); + } + + /// + /// Exception when emitting the impl .dll to disk. + /// + public static Exception CreateCompilationError(Exception exception) + { + return Exception(6, "Failed to create the compilation dll.", exception); + } + + /// + /// Creates a new exception with the specified id and message. + /// + /// The exception id. + /// The exception message. + /// The inner exception. + /// The resulting exception. + private static Exception Exception(int id, string message, Exception? innerException = null) + { + return new WellKnownProjectionGeneratorException($"{ErrorPrefix}{id:0000}", message, innerException); + } +} \ No newline at end of file diff --git a/src/WinRT.Projection.Generator/Extensions/ProjectionGeneratorExceptionExtensions.cs b/src/WinRT.Projection.Generator/Extensions/ProjectionGeneratorExceptionExtensions.cs new file mode 100644 index 000000000..d1261ac8f --- /dev/null +++ b/src/WinRT.Projection.Generator/Extensions/ProjectionGeneratorExceptionExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using WindowsRuntime.ProjectionGenerator.Errors; + +namespace WindowsRuntime.ProjectionGenerator; + +/// +/// Extensions for interop exceptions. +/// +internal static class ProjectionGeneratorExceptionExtensions +{ + extension(Exception exception) + { + /// + /// Gets a value indicating whether an exception is well known (and should therefore not be caught). + /// + public bool IsWellKnown => exception is OperationCanceledException or WellKnownProjectionGeneratorException; + } +} diff --git a/src/WinRT.Projection.Generator/Extensions/SignatureComparerExtensions.cs b/src/WinRT.Projection.Generator/Extensions/SignatureComparerExtensions.cs new file mode 100644 index 000000000..92fcb22fa --- /dev/null +++ b/src/WinRT.Projection.Generator/Extensions/SignatureComparerExtensions.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using AsmResolver.DotNet.Signatures; + +namespace WindowsRuntime.ProjectionGenerator; + +/// +/// Extensions for . +/// +internal static class SignatureComparerExtensions +{ + /// + /// Backing field for . + /// + private static readonly SignatureComparer IgnoreVersion = new(SignatureComparisonFlags.VersionAgnostic); + + extension(SignatureComparer comparer) + { + /// + /// An immutable default instance of , with . + /// + public static SignatureComparer IgnoreVersion => IgnoreVersion; + + /// + /// Creates an instance for a pair of values. + /// + /// The resulting instance. + public IEqualityComparer<(TypeSignature, TypeSignature)> MakeValueTupleComparer() + { + return new SignatureValueTupleComparer(comparer); + } + } +} + +/// +/// An for a pair of values. +/// +file sealed class SignatureValueTupleComparer : IEqualityComparer<(TypeSignature, TypeSignature)> +{ + /// + /// The wrapped instance used for comparison. + /// + private readonly SignatureComparer _comparer; + + /// + /// Creates a new instance with the specified parameters. + /// + /// The instance to wrap. + public SignatureValueTupleComparer(SignatureComparer comparer) + { + _comparer = comparer; + } + + /// + public bool Equals((TypeSignature, TypeSignature) x, (TypeSignature, TypeSignature) y) + { + return _comparer.Equals(x.Item1, y.Item1) && _comparer.Equals(x.Item2, y.Item2); + } + + /// + public int GetHashCode((TypeSignature, TypeSignature) obj) + { + return HashCode.Combine(_comparer.GetHashCode(obj.Item1), _comparer.GetHashCode(obj.Item2)); + } +} diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs new file mode 100644 index 000000000..f870b1028 --- /dev/null +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Emit; +using WindowsRuntime.ProjectionGenerator.Errors; + +namespace WindowsRuntime.ProjectionGenerator.Generation; + +/// +internal partial class ProjectionGenerator +{ + private static readonly string ProjectionAssemblyName = "WinRT.Projection"; + + /// + /// Runs the emit logic for the generator. + /// + /// The arguments for this invocation. + private static void Emit(ProjectionGeneratorArgs args) + { + args.Token.ThrowIfCancellationRequested(); + + string sourcesFolder = GenerateSources(args, out HashSet projectionReferenceAssemblies); + + args.Token.ThrowIfCancellationRequested(); + + string[] referencesWithoutProjections = [.. args.ReferenceAssemblyPaths.Where(r => !projectionReferenceAssemblies.Contains(r))]; + + CSharpCompilation compilation = CreateCompilationForProjection(sourcesFolder, referencesWithoutProjections); + + args.Token.ThrowIfCancellationRequested(); + + string projectionDllPath = Path.Combine(args.GeneratedAssemblyDirectory, ProjectionAssemblyName + ".dll"); + SaveDll(compilation, projectionDllPath); + } + + private static CSharpCompilation CreateCompilationForProjection(string sourcesFolder, string[] referencePaths) + { + try + { + // Parse the source files into a syntax tree + List syntaxTrees = []; + foreach (string file in Directory.GetFiles(sourcesFolder, "*.cs")) + { + syntaxTrees.Add(CSharpSyntaxTree.ParseText(File.ReadAllText(file), path: file)); + } + + // Build references list + List references = []; + + foreach (string refPath in referencePaths) + { + references.Add(MetadataReference.CreateFromFile(refPath)); + } + + // Create the compilation + CSharpCompilation compilation = CSharpCompilation.Create( + ProjectionAssemblyName, + syntaxTrees, + references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true)); + + return compilation; + } + catch (Exception e) when (!e.IsWellKnown) + { + throw WellKnownProjectionGeneratorExceptions.CreateCompilationError(e); + } + } + + private static void SaveDll(CSharpCompilation compilation, string dllPath) + { + try + { + // Emit the compilation to a file + using FileStream fileStream = new(dllPath, FileMode.Create); + EmitResult result = compilation.Emit(fileStream); + + if (!result.Success) + { + throw WellKnownProjectionGeneratorExceptions.EmitDllError(result.Diagnostics); + } + } + catch (Exception e) when (!e.IsWellKnown) + { + throw WellKnownProjectionGeneratorExceptions.EmitDllError(e); + } + } +} \ No newline at end of file diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs new file mode 100644 index 000000000..18b4ab1be --- /dev/null +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionGenerator.Resolvers; + +namespace WindowsRuntime.ProjectionGenerator.Generation; + +/// +internal partial class ProjectionGenerator +{ + /// + /// Creates a temporary folder for CsWinRT generation output. + /// + /// The path to the created temporary folder. + private static string GetTempFolder() + { + string outputDir = Path.Combine(Path.GetTempPath(), "CsWinRT", Path.GetRandomFileName()).TrimEnd('\\'); + _ = Directory.CreateDirectory(outputDir); + return outputDir; + } + + /// + /// Generate the projection sources using CsWinRT for the generator. + /// + /// The arguments for this invocation. + /// The projection reference assemblies which were used to generate the sources. + /// The path to the folder containing the generated sources. + private static string GenerateSources(ProjectionGeneratorArgs args, out HashSet projectionReferenceAssemblies) + { + args.Token.ThrowIfCancellationRequested(); + + GenerateRspFile(args, out string outputFolder, out string rspFile, out projectionReferenceAssemblies); + + args.Token.ThrowIfCancellationRequested(); + + RunCsWinRT(args, rspFile); + + return outputFolder; + } + + /// + /// Generates a response file for CsWinRT based on the provided arguments and reference assemblies. + /// + /// The arguments for this invocation. + /// The folder where sources are generated in. + /// The generated response file for running cswinrt.exe. + /// The projection reference assemblies which were used to generate the rsp file. + private static void GenerateRspFile(ProjectionGeneratorArgs args, out string outputFolder, out string rspFile, out HashSet projectionReferenceAssemblies) + { + args.Token.ThrowIfCancellationRequested(); + + outputFolder = GetTempFolder(); + rspFile = Path.Combine(outputFolder, "ProjectionGenerator.rsp"); + projectionReferenceAssemblies = []; + + using StreamWriter fileStream = new(rspFile); + PathAssemblyResolver resolver = new(args.ReferenceAssemblyPaths); + foreach (string referenceAssemblyPath in args.ReferenceAssemblyPaths) + { + ModuleDefinition moduleDefinition = ModuleDefinition.FromFile(referenceAssemblyPath, resolver.ReaderParameters); + + // Check if this is a reference assembly as the ones we are regenerating are reference assemblies. + if (!IsReferenceAssembly(moduleDefinition) || !IsWindowsRuntimeReferenceAssembly(moduleDefinition)) + { + continue; + } + + _ = projectionReferenceAssemblies.Add(referenceAssemblyPath); + + if (moduleDefinition.Assembly is not null) + { + if (moduleDefinition.Assembly.Name == "Microsoft.Windows.SDK.NET") + { + // Given we know this is the Windows SDK projection and types will be only from that namespace, + // we just include that namespace rather than going through the types. + fileStream.WriteLine($"-include Windows"); + fileStream.WriteLine($"-include WinRT.Interop"); + continue; + } + else if (moduleDefinition.Assembly.Name == "Microsoft.WinUI") + { + // In additional to projecting the individual types, make sure + // the additions get included by including the namespace. + fileStream.WriteLine($"-include Microsoft.UI"); + } + } + + foreach (TypeDefinition exportedType in moduleDefinition.TopLevelTypes) + { + fileStream.WriteLine($"-include {exportedType.FullName}"); + } + } + + fileStream.WriteLine($"-target {args.TargetFramework}"); + fileStream.WriteLine($"-input {args.WindowsMetadata}"); + fileStream.WriteLine($"-output \"{outputFolder}\""); + foreach (string winmdPath in args.WinMDPaths) + { + fileStream.WriteLine($"-input \"{winmdPath}\""); + } + } + + /// + /// Executes the CsWinRT tool with the specified response file. + /// + /// The arguments for this invocation. + /// The path to the response file containing CsWinRT arguments. + private static void RunCsWinRT(ProjectionGeneratorArgs args, string rspFile) + { + ProcessStartInfo processInfo = new() + { + FileName = args.CsWinRTExePath, + Arguments = "@" + rspFile, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true + }; + + using Process cswinrtProcess = Process.Start(processInfo) ?? throw new Exception(); + string error = cswinrtProcess.StandardError.ReadToEnd(); + cswinrtProcess.WaitForExit(); + + if (cswinrtProcess.ExitCode != 0) + { + throw new Win32Exception(cswinrtProcess.ExitCode, error); + } + } + + /// + /// Checks if the specified module definition represents a reference assembly. + /// + /// The module definition to check. + /// true if the module is a reference assembly; otherwise, false. + private static bool IsReferenceAssembly(ModuleDefinition moduleDefinition) + { + return moduleDefinition.Assembly is not null && moduleDefinition.Assembly.HasCustomAttribute("System.Runtime.CompilerServices"u8, "ReferenceAssemblyAttribute"u8); + } + + /// + /// Checks if the specified module definition represents a Windows Runtime reference assembly. + /// + /// The module definition to check. + /// true if the module is a Windows Runtime reference assembly; otherwise, false. + private static bool IsWindowsRuntimeReferenceAssembly(ModuleDefinition moduleDefinition) + { + return moduleDefinition.Assembly is not null && moduleDefinition.Assembly.HasCustomAttribute("WindowsRuntime.InteropServices"u8, "WindowsRuntimeReferenceAssemblyAttribute"u8); + } +} \ No newline at end of file diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs new file mode 100644 index 000000000..19b2a6416 --- /dev/null +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Threading; +using ConsoleAppFramework; +using WindowsRuntime.ProjectionGenerator.Errors; + +namespace WindowsRuntime.ProjectionGenerator.Generation; + +/// +/// The implementation of the CsWinRT projection .dll generator. +/// +internal static partial class ProjectionGenerator +{ + /// + /// Runs the projection generator to produce the resulting WinRT.Projection.dll assembly. + /// + /// The path to the response file to use. + /// The token for the operation. + public static void Run([Argument] string responseFilePath, CancellationToken token) + { + ProjectionGeneratorArgs args; + + // Parse the actual arguments from the response file + try + { + args = ProjectionGeneratorArgs.ParseFromResponseFile(responseFilePath, token); + } + catch (Exception e) when (!e.IsWellKnown) + { + throw new UnhandledProjectionGeneratorException("parsing", e); + } + + args.Token.ThrowIfCancellationRequested(); + + try + { + Emit(args); + } + catch (Exception e) when (!e.IsWellKnown) + { + throw new UnhandledProjectionGeneratorException("emit", e); + } + + args.Token.ThrowIfCancellationRequested(); + + // Notify the user that generation was successful + ConsoleApp.Log($"Projection code generated -> {Path.Combine(args.GeneratedAssemblyDirectory, ProjectionAssemblyName)}"); + } +} \ No newline at end of file diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs new file mode 100644 index 000000000..16baff67d --- /dev/null +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Threading; +using WindowsRuntime.ProjectionGenerator.Attributes; +using WindowsRuntime.ProjectionGenerator.Errors; + +#pragma warning disable IDE0046 + +namespace WindowsRuntime.ProjectionGenerator.Generation; + +/// +internal partial class ProjectionGeneratorArgs +{ + /// + /// Parses an instance from a target response file. + /// + /// The path to the response file. + /// The token for the operation. + /// The resulting instance. + public static ProjectionGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) + { + // If the path is a response file, it will start with the '@' character. + // This matches the default escaping 'ToolTask' uses for response files. + if (path is ['@', .. string escapedPath]) + { + path = escapedPath; + } + + string[] responseArgs; + + // Read all lines in the response file (each line contains a single command line argument) + try + { + responseArgs = File.ReadAllLines(path); + } + catch (Exception e) + { + throw WellKnownProjectionGeneratorExceptions.ResponseFileReadError(e); + } + + Dictionary argsMap = []; + + // Build a map with all the commands and their values + foreach (string line in responseArgs) + { + string trimmedLine = line.Trim(); + + // Each line has the command line argument name followed by a space, and then the + // argument value. If there are no spaces on any given line, the file is malformed. + int indexOfSpace = trimmedLine.IndexOf(' '); + + if (indexOfSpace == -1) + { + throw WellKnownProjectionGeneratorExceptions.MalformedResponseFile(); + } + + // Now we can parse the actual command line argument name and value + string argumentName = trimmedLine.AsSpan()[..indexOfSpace].ToString(); + string argumentValue = trimmedLine.AsSpan()[(indexOfSpace + 1)..].TrimEnd().ToString(); + + // We should never have duplicate commands + if (!argsMap.TryAdd(argumentName, argumentValue)) + { + throw WellKnownProjectionGeneratorExceptions.MalformedResponseFile(); + } + } + + // Parse all commands to create the managed arguments to use + return new() + { + ReferenceAssemblyPaths = GetStringArrayArgument(argsMap, nameof(ReferenceAssemblyPaths)), + GeneratedAssemblyDirectory = GetStringArgument(argsMap, nameof(GeneratedAssemblyDirectory)), + WinMDPaths = GetStringArrayArgument(argsMap, nameof(WinMDPaths)), + TargetFramework = GetStringArgument(argsMap, nameof(TargetFramework)), + WindowsMetadata = GetStringArgument(argsMap, nameof(WindowsMetadata)), + CsWinRTExePath = GetStringArgument(argsMap, nameof(CsWinRTExePath)), + Token = token + }; + } + + /// + /// Gets the command line argument name for a property. + /// + /// The target property name. + /// The command line argument name for . + public static string GetCommandLineArgumentName(string propertyName) + { + try + { + return typeof(ProjectionGeneratorArgs).GetProperty(propertyName)!.GetCustomAttribute()!.Name; + } + catch (Exception e) + { + throw WellKnownProjectionGeneratorExceptions.ResponseFileArgumentParsingError(propertyName, e); + } + } + + /// + /// Parses a array argument. + /// + /// The input map with raw arguments. + /// The target property name. + /// The resulting argument. + private static string[] GetStringArrayArgument(Dictionary argsMap, string propertyName) + { + if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) + { + return argumentValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + } + + throw WellKnownProjectionGeneratorExceptions.ResponseFileArgumentParsingError(propertyName); + } + + /// + /// Parses a argument. + /// + /// The input map with raw arguments. + /// The target property name. + /// The resulting argument. + private static string GetStringArgument(Dictionary argsMap, string propertyName) + { + if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) + { + return argumentValue; + } + + throw WellKnownProjectionGeneratorExceptions.ResponseFileArgumentParsingError(propertyName); + } +} diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs new file mode 100644 index 000000000..5a22e7211 --- /dev/null +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Threading; +using WindowsRuntime.ProjectionGenerator.Attributes; + +namespace WindowsRuntime.ProjectionGenerator.Generation; + +/// +/// Input parameters for . +/// +internal sealed partial class ProjectionGeneratorArgs +{ + /// Gets the input .dll paths. + [CommandLineArgumentName("--reference-assembly-paths")] + public required string[] ReferenceAssemblyPaths { get; init; } + + /// Gets the directory to use to place the generated assembly. + [CommandLineArgumentName("--generated-assembly-directory")] + public required string GeneratedAssemblyDirectory { get; init; } + + /// Gets the input .winmd paths. + [CommandLineArgumentName("--winmd-paths")] + public required string[] WinMDPaths { get; init; } + + /// Gets the target framework being built for. + [CommandLineArgumentName("--target-framework")] + public required string TargetFramework { get; init; } + + /// Gets the Windows WinMD or version which the projection targets. + [CommandLineArgumentName("--windows-metadata")] + public required string WindowsMetadata { get; init; } + + /// Gets the path to CsWinRT.exe. + [CommandLineArgumentName("--cswinrt-exe-path")] + public required string CsWinRTExePath { get; init; } + + /// Gets the token for the operation. + public required CancellationToken Token { get; init; } +} \ No newline at end of file diff --git a/src/WinRT.Projection.Generator/Program.cs b/src/WinRT.Projection.Generator/Program.cs new file mode 100644 index 000000000..afb40e394 --- /dev/null +++ b/src/WinRT.Projection.Generator/Program.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using ConsoleAppFramework; +using WindowsRuntime.ProjectionGenerator.Generation; + +// Run the projection generator with all parsed arguments +ConsoleApp.Run(args, ProjectionGenerator.Run); \ No newline at end of file diff --git a/src/WinRT.Projection.Generator/Resolvers/PathAssemblyResolver.cs b/src/WinRT.Projection.Generator/Resolvers/PathAssemblyResolver.cs new file mode 100644 index 000000000..8fa5cbc71 --- /dev/null +++ b/src/WinRT.Projection.Generator/Resolvers/PathAssemblyResolver.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using AsmResolver.DotNet; +using AsmResolver.DotNet.Serialized; +using AsmResolver.DotNet.Signatures; + +namespace WindowsRuntime.ProjectionGenerator.Resolvers; + +/// +/// A custom from a specific set of reference paths. +/// +internal sealed class PathAssemblyResolver : IAssemblyResolver +{ + /// + /// The input .dll paths to load assemblies from. + /// + private readonly string[] _referencePath; + + /// + /// The cached assemblies. + /// + private readonly ConcurrentDictionary _cache = new(SignatureComparer.IgnoreVersion); + + /// + /// Creates a new instance with the specified parameters. + /// + /// The input .dll paths. + public PathAssemblyResolver(string[] referencePath) + { + _referencePath = referencePath; + ReaderParameters = new RuntimeContext(new DotNetRuntimeInfo(".NETCoreApp", new Version(10, 0)), this).DefaultReaderParameters; + } + + /// + /// Gets the instance to use. + /// + public ModuleReaderParameters ReaderParameters { get; } + + /// + public AssemblyDefinition? Resolve(AssemblyDescriptor assembly) + { + // If we already have the assembly in the cache, return it + if (_cache.TryGetValue(assembly, out AssemblyDefinition? cachedDefinition)) + { + return cachedDefinition; + } + + // We can't load an assembly without a name + if (assembly.Name is null) + { + return null; + } + + // Find the first match in our list of reference paths, and load that assembly + foreach (string path in _referencePath) + { + if (Path.GetFileNameWithoutExtension(path.AsSpan()).SequenceEqual(assembly.Name)) + { + return _cache.GetOrAdd( + key: assembly, + valueFactory: static (_, args) => AssemblyDefinition.FromFile(args.Path, args.Parameters), + factoryArgument: (Path: path, Parameters: ReaderParameters)); + } + } + + return null; + } + + /// + public void AddToCache(AssemblyDescriptor descriptor, AssemblyDefinition definition) + { + _ = _cache.TryAdd(descriptor, definition); + } + + /// + public bool RemoveFromCache(AssemblyDescriptor descriptor) + { + return _cache.TryRemove(descriptor, out _); + } + + /// + public bool HasCached(AssemblyDescriptor descriptor) + { + return _cache.ContainsKey(descriptor); + } + + /// + public void ClearCache() + { + _cache.Clear(); + } +} diff --git a/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj b/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj new file mode 100644 index 000000000..42a604d0a --- /dev/null +++ b/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj @@ -0,0 +1,45 @@ + + + Exe + net10.0 + preview + enable + true + true + true + true + Speed + false + + + true + + + WindowsRuntime.ProjectionGenerator + + + cswinrtprojectiongen + + + true + true + latest + latest-all + true + strict + true + + + $(NoWarn);AD0001 + + + + + + + + From d39f0af81fc35b3270b5d7c431938b694dacea2e Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Fri, 5 Dec 2025 12:03:58 -0800 Subject: [PATCH 03/15] Try to fix build --- src/cswinrt.slnx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cswinrt.slnx b/src/cswinrt.slnx index d8b3d7469..7d35e1c0c 100644 --- a/src/cswinrt.slnx +++ b/src/cswinrt.slnx @@ -177,6 +177,7 @@ + From 99704d075ea9cf69bc7e945796fb7a6ffaa8ee10 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Fri, 5 Dec 2025 19:07:55 -0800 Subject: [PATCH 04/15] Fix AOT --- .../Generation/ImplGenerator.cs | 16 +++++++++++++++- src/cswinrt.slnx | 2 -- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index febf5bb34..d6f625ac2 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -254,7 +254,7 @@ private static void EmitTypeForwards(ModuleDefinition inputModule, ModuleDefinit foreach (TypeDefinition exportedType in inputModule.TopLevelTypes) { // We only need to forward public types - if (!exportedType.IsPublic) + if (!exportedType.IsPublic && !IsForwardedInternalType(exportedType)) { continue; } @@ -288,6 +288,20 @@ private static void EmitTypeForwards(ModuleDefinition inputModule, ModuleDefinit } } + /// + /// There are a couple types in the Windows SDK projection which are not public and vtables are generated for. + /// This checks whether it is one of those by checking the namespace. + /// + /// The type to check. + /// True if the namespace is one of the forwarded internal types; otherwise, false. + private static bool IsForwardedInternalType(TypeDefinition exportedType) + { + return exportedType.IsClass && + // Check not static class + !(exportedType.IsAbstract && exportedType.IsSealed) && + exportedType.Namespace?.Value is "System.IO" or "Windows.Storage.Streams"; + } + /// /// Writes the impl module to disk. /// diff --git a/src/cswinrt.slnx b/src/cswinrt.slnx index 7d35e1c0c..5257edd28 100644 --- a/src/cswinrt.slnx +++ b/src/cswinrt.slnx @@ -175,8 +175,6 @@ - - From 2e7c611b2f3ffcc231c4450b768cacafd40aaa20 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Fri, 5 Dec 2025 20:52:24 -0800 Subject: [PATCH 05/15] Cleanup workarounds --- nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets | 3 ++- nuget/Microsoft.Windows.CsWinRT.targets | 2 ++ src/Projections/Directory.Build.props | 3 --- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets b/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets index 09084f99c..d15034ea3 100644 --- a/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets +++ b/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets @@ -19,7 +19,8 @@ Copyright (C) Microsoft Corporation. All rights reserved. So the switch for it is currently opt-in. It can be changed to opt-out in the future. We only enable this automatically if the project is explicitly targeting CsWinRT 3.0. --> - true + true + true false - false true - true From 5df75b2fff605a7264821fe44ab64108fc2ca5dc Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Sat, 6 Dec 2025 13:30:22 -0800 Subject: [PATCH 06/15] Move to handling more of the reference projection and simplify targets --- ...crosoft.Windows.CsWinRT.CsWinRTGen.targets | 55 ++------ nuget/Microsoft.Windows.CsWinRT.targets | 6 +- src/cswinrt/code_writers.h | 121 ++++++++++++++-- src/cswinrt/strings/ComInteropHelpers.cs | 130 +++++++++++++++++- .../WindowsRuntimeBuffer.cs | 2 + 5 files changed, 250 insertions(+), 64 deletions(-) diff --git a/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets b/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets index d15034ea3..b439d0f37 100644 --- a/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets +++ b/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets @@ -54,9 +54,9 @@ Copyright (C) Microsoft Corporation. All rights reserved. - $(IntermediateOutputPath)\runtimes\ + $(IntermediateOutputPath)\forwarder\ <_CsWinRTGeneratorForwarderAssemblyPath>$(CsWinRTGeneratorForwarderAssemblyDirectory)$(AssemblyName).dll - <_CsWinRTGeneratorForwarderOutputAssemblyPath>runtimes\$(AssemblyName).dll + <_CsWinRTGeneratorForwarderOutputAssemblyPath>forwarder\$(AssemblyName).dll - - - - $(AssemblyName) - .NETCoreApp - false - true - true - _RunCsWinRTForwarderImplGenerator - AnyCPU - - - - - + - - <_SourceItemsToCopyToOutputDirectory - Include="@(CsWinRTGeneratorForwarderAssemblyPath)" - TargetPath="$(_CsWinRTGeneratorForwarderOutputAssemblyPath)" /> - - - - - - - + + + + - - - - - $(TargetPlatformMoniker) - $(TargetPlatformIdentifier) - $(TargetFrameworkIdentifier) - $(TargetFrameworkVersion.TrimStart('vV')) - $([MSBuild]::NormalizePath('$(OutputPath)', '$(AssemblyName).dll')) - @(CopyUpToDateMarker) + @(CsWinRTInputs) diff --git a/nuget/Microsoft.Windows.CsWinRT.targets b/nuget/Microsoft.Windows.CsWinRT.targets index 19eeed9d4..9b817dff0 100644 --- a/nuget/Microsoft.Windows.CsWinRT.targets +++ b/nuget/Microsoft.Windows.CsWinRT.targets @@ -14,8 +14,10 @@ Copyright (C) Microsoft Corporation. All rights reserved. false true false - true - false + false + true + true + $([MSBuild]::NormalizePath($(TargetDir), 'ref', $(TargetFileName))) true $(IntermediateOutputPath)\forwarder\ <_CsWinRTGeneratorForwarderAssemblyPath>$(CsWinRTGeneratorForwarderAssemblyDirectory)$(AssemblyName).dll - <_CsWinRTGeneratorForwarderOutputAssemblyPath>forwarder\$(AssemblyName).dll From 856ed291312e608489b3228c44f16498ca00bc02 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Wed, 26 Nov 2025 13:18:54 -0800 Subject: [PATCH 10/15] Fix typemap not being used. --- src/Tests/UnitTest/TestModuleInitializer.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/Tests/UnitTest/TestModuleInitializer.cs diff --git a/src/Tests/UnitTest/TestModuleInitializer.cs b/src/Tests/UnitTest/TestModuleInitializer.cs new file mode 100644 index 000000000..859978fde --- /dev/null +++ b/src/Tests/UnitTest/TestModuleInitializer.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Reflection; + +namespace UnitTest; + +// CsWinRT makes use of the .NET typemap to register all the projected types. +// As part this, .NET makes use of TypeMapAssemblyTarget to discover the assemblies with the type map. +// But this needs to be on the launching executable for it to discover them by default. Given with +// a test runner, they aren't, this does the alternative way of setting the assembly with that attribute +// as the entry assembly which then allows .NET to discover it. +internal static class ProjectionTypesInitializer +{ + [System.Runtime.CompilerServices.ModuleInitializer] + internal static void InitializeProjectionTypes() + { + Assembly.SetEntryAssembly(typeof(ProjectionTypesInitializer).Assembly); + } +} From 6856f160e76d186d0099cff7dd4cbbdc5a35e5be Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Sun, 7 Dec 2025 19:03:21 -0800 Subject: [PATCH 11/15] Fix unit tests and get merged projection working --- .../TypeMapAssemblyTargetGenerator.Execute.cs | 5 +++- src/Tests/UnitTest/Directory.Build.props | 9 +++++++ .../UnitTest/TestComponentCSharp_Tests.cs | 25 ++++++++++--------- src/Tests/UnitTest/UnitTest.csproj | 2 +- src/cswinrt/code_writers.h | 2 ++ 5 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 src/Tests/UnitTest/Directory.Build.props diff --git a/src/Authoring/WinRT.SourceGenerator2/TypeMapAssemblyTargetGenerator.Execute.cs b/src/Authoring/WinRT.SourceGenerator2/TypeMapAssemblyTargetGenerator.Execute.cs index aba7fa179..b8a8798cf 100644 --- a/src/Authoring/WinRT.SourceGenerator2/TypeMapAssemblyTargetGenerator.Execute.cs +++ b/src/Authoring/WinRT.SourceGenerator2/TypeMapAssemblyTargetGenerator.Execute.cs @@ -127,8 +127,11 @@ public static void EmitDefaultTypeMapAssemblyTargetAttributes(SourceProductionCo #pragma warning disable [assembly: global::System.Runtime.InteropServices.TypeMapAssemblyTarget("WinRT.Interop")] - //[assembly: global::System.Runtime.InteropServices.TypeMapAssemblyTarget("WinRT.Projection")] + [assembly: global::System.Runtime.InteropServices.TypeMapAssemblyTarget("WinRT.Projection")] [assembly: global::System.Runtime.InteropServices.TypeMapAssemblyTarget("WinRT.Runtime2")] + [assembly: global::System.Runtime.InteropServices.TypeMapAssemblyTarget("WinRT.Interop")] + [assembly: global::System.Runtime.InteropServices.TypeMapAssemblyTarget("WinRT.Projection")] + [assembly: global::System.Runtime.InteropServices.TypeMapAssemblyTarget("WinRT.Runtime2")] """; context.AddSource("TypeMapAssemblyTarget.g.cs", source); diff --git a/src/Tests/UnitTest/Directory.Build.props b/src/Tests/UnitTest/Directory.Build.props new file mode 100644 index 000000000..629129c59 --- /dev/null +++ b/src/Tests/UnitTest/Directory.Build.props @@ -0,0 +1,9 @@ + + + + true + + + + + diff --git a/src/Tests/UnitTest/TestComponentCSharp_Tests.cs b/src/Tests/UnitTest/TestComponentCSharp_Tests.cs index 064d093ab..c745762b9 100644 --- a/src/Tests/UnitTest/TestComponentCSharp_Tests.cs +++ b/src/Tests/UnitTest/TestComponentCSharp_Tests.cs @@ -35,6 +35,7 @@ using WindowsRuntime.InteropServices; using WindowsRuntime; using System.Runtime.InteropServices.Marshalling; +using Windows.Foundation.Tasks; // Test SupportedOSPlatform warnings for APIs targeting 10.0.19041.0: [assembly: global::System.Runtime.Versioning.SupportedOSPlatform("Windows10.0.18362.0")] @@ -2012,18 +2013,18 @@ public void TestAsyncActionWait() { var asyncAction = TestObject.DoitAsync(); TestObject.CompleteAsync(); - asyncAction.Wait(); + asyncAction.AsTask().Wait(); Assert.Equal(AsyncStatus.Completed, asyncAction.Status); asyncAction = TestObject.DoitAsync(); TestObject.CompleteAsync(E_FAIL); - var e = Assert.Throws(() => asyncAction.Wait()); + var e = Assert.Throws(() => asyncAction.AsTask().Wait()); Assert.Equal(E_FAIL, e.InnerException.HResult); Assert.Equal(AsyncStatus.Error, asyncAction.Status); asyncAction = TestObject.DoitAsync(); asyncAction.Cancel(); - e = Assert.Throws(() => asyncAction.Wait()); + e = Assert.Throws(() => asyncAction.AsTask().Wait()); Assert.True(e.InnerException is TaskCanceledException); Assert.Equal(AsyncStatus.Canceled, asyncAction.Status); } @@ -2100,18 +2101,18 @@ public void TestAsyncActionWithProgressWait() { var asyncAction = TestObject.DoitAsyncWithProgress(); TestObject.CompleteAsync(); - asyncAction.Wait(); + asyncAction.AsTask().Wait(); Assert.Equal(AsyncStatus.Completed, asyncAction.Status); asyncAction = TestObject.DoitAsyncWithProgress(); TestObject.CompleteAsync(E_FAIL); - var e = Assert.Throws(() => asyncAction.Wait()); + var e = Assert.Throws(() => asyncAction.AsTask().Wait()); Assert.Equal(E_FAIL, e.InnerException.HResult); Assert.Equal(AsyncStatus.Error, asyncAction.Status); asyncAction = TestObject.DoitAsyncWithProgress(); asyncAction.Cancel(); - e = Assert.Throws(() => asyncAction.Wait()); + e = Assert.Throws(() => asyncAction.AsTask().Wait()); Assert.True(e.InnerException is TaskCanceledException); Assert.Equal(AsyncStatus.Canceled, asyncAction.Status); } @@ -2152,18 +2153,18 @@ public void TestAsyncOperationWait() { var asyncOperation = TestObject.AddAsync(42, 8); TestObject.CompleteAsync(); - asyncOperation.Wait(); + asyncOperation.AsTask().Wait(); Assert.Equal(AsyncStatus.Completed, asyncOperation.Status); asyncOperation = TestObject.AddAsync(42, 8); TestObject.CompleteAsync(E_FAIL); - var e = Assert.Throws(() => asyncOperation.Wait()); + var e = Assert.Throws(() => asyncOperation.AsTask().Wait()); Assert.Equal(E_FAIL, e.InnerException.HResult); Assert.Equal(AsyncStatus.Error, asyncOperation.Status); asyncOperation = TestObject.AddAsync(42, 8); asyncOperation.Cancel(); - e = Assert.Throws(() => asyncOperation.Wait()); + e = Assert.Throws(() => asyncOperation.AsTask().Wait()); Assert.True(e.InnerException is TaskCanceledException); Assert.Equal(AsyncStatus.Canceled, asyncOperation.Status); } @@ -2243,18 +2244,18 @@ public void TestAsyncOperationWithProgressWait() { var asyncOperation = TestObject.AddAsyncWithProgress(42, 8); TestObject.CompleteAsync(); - asyncOperation.Wait(); + asyncOperation.AsTask().Wait(); Assert.Equal(AsyncStatus.Completed, asyncOperation.Status); asyncOperation = TestObject.AddAsyncWithProgress(42, 8); TestObject.CompleteAsync(E_FAIL); - var e = Assert.Throws(() => asyncOperation.Wait()); + var e = Assert.Throws(() => asyncOperation.AsTask().Wait()); Assert.Equal(E_FAIL, e.InnerException.HResult); Assert.Equal(AsyncStatus.Error, asyncOperation.Status); asyncOperation = TestObject.AddAsyncWithProgress(42, 8); asyncOperation.Cancel(); - e = Assert.Throws(() => asyncOperation.Wait()); + e = Assert.Throws(() => asyncOperation.AsTask().Wait()); Assert.True(e.InnerException is TaskCanceledException); Assert.Equal(AsyncStatus.Canceled, asyncOperation.Status); } diff --git a/src/Tests/UnitTest/UnitTest.csproj b/src/Tests/UnitTest/UnitTest.csproj index ffad185bf..94a990f33 100644 --- a/src/Tests/UnitTest/UnitTest.csproj +++ b/src/Tests/UnitTest/UnitTest.csproj @@ -6,7 +6,6 @@ UnitTest 1701;1702;0436;1658 true - true false @@ -33,6 +32,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index 110359beb..b8148fb8c 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -8564,12 +8564,14 @@ public static nint Vtable w.write(R"( [DynamicInterfaceCastableImplementation] +% file interface % : % { % % } )", + bind(type), type.TypeName(), bind(type, typedef_name_type::Projected, false), bind(type), From 76a89d6d98291dbcfbcd9083965feb5a7e804058 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Sun, 7 Dec 2025 23:43:08 -0800 Subject: [PATCH 12/15] PR feedback --- .../Generation/ProjectionGenerator.Emit.cs | 7 ++++++- .../Generation/ProjectionGenerator.cs | 2 ++ .../WinRT.Projection.Generator.csproj | 7 ------- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs index f870b1028..b5a3328f9 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using ConsoleAppFramework; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; @@ -15,7 +16,7 @@ namespace WindowsRuntime.ProjectionGenerator.Generation; /// internal partial class ProjectionGenerator { - private static readonly string ProjectionAssemblyName = "WinRT.Projection"; + private const string ProjectionAssemblyName = "WinRT.Projection"; /// /// Runs the emit logic for the generator. @@ -25,12 +26,16 @@ private static void Emit(ProjectionGeneratorArgs args) { args.Token.ThrowIfCancellationRequested(); + ConsoleApp.Log("Generating merged projection sources"); + string sourcesFolder = GenerateSources(args, out HashSet projectionReferenceAssemblies); args.Token.ThrowIfCancellationRequested(); string[] referencesWithoutProjections = [.. args.ReferenceAssemblyPaths.Where(r => !projectionReferenceAssemblies.Contains(r))]; + ConsoleApp.Log("Compiling merged projection"); + CSharpCompilation compilation = CreateCompilationForProjection(sourcesFolder, referencesWithoutProjections); args.Token.ThrowIfCancellationRequested(); diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs index 19b2a6416..feb668613 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs @@ -37,6 +37,8 @@ public static void Run([Argument] string responseFilePath, CancellationToken tok try { + ConsoleApp.Log($"Processing {args.ReferenceAssemblyPaths.Length + 1} modules"); + Emit(args); } catch (Exception e) when (!e.IsWellKnown) diff --git a/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj b/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj index 42a604d0a..0f72f4368 100644 --- a/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj +++ b/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj @@ -28,13 +28,6 @@ true strict true - - - $(NoWarn);AD0001 From 28bfd9c62e6cbce5f9abec5e63e7b26443bb71dc Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Mon, 8 Dec 2025 00:19:06 -0800 Subject: [PATCH 13/15] Remove forwarder not needed anymore --- .../Generation/ImplGenerator.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index d6f625ac2..1061622e3 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -268,19 +268,6 @@ private static void EmitTypeForwards(ModuleDefinition inputModule, ModuleDefinit Attributes = AsmResolver.PE.DotNet.Metadata.Tables.TypeAttributes.Forwarder }); } - - TypeReference interfaceIIDsTypeReference = inputModule.CreateTypeReference("ABI"u8, "InterfaceIIDs"u8); - if (interfaceIIDsTypeReference.Resolve() is TypeDefinition interfaceIIDsTypeDefinition) - { - // Forward the 'ABI.InterfaceIIDs' type as well, as it's used for IID resolution at runtime - implModule.ExportedTypes.Add(new ExportedType( - implementation: projectionAssembly.ImportWith(implModule.DefaultImporter), - ns: interfaceIIDsTypeDefinition.Namespace, - name: interfaceIIDsTypeDefinition.Name) - { - Attributes = AsmResolver.PE.DotNet.Metadata.Tables.TypeAttributes.Forwarder - }); - } } catch (Exception e) { From 590d62a4ddeb7a0df7733b9e06f884d5f1ad05f2 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Mon, 8 Dec 2025 15:44:20 -0800 Subject: [PATCH 14/15] PR feedback --- .../Generation/ProjectionGenerator.Emit.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs index b5a3328f9..8e9a7449f 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Text; using WindowsRuntime.ProjectionGenerator.Errors; namespace WindowsRuntime.ProjectionGenerator.Generation; @@ -26,8 +27,6 @@ private static void Emit(ProjectionGeneratorArgs args) { args.Token.ThrowIfCancellationRequested(); - ConsoleApp.Log("Generating merged projection sources"); - string sourcesFolder = GenerateSources(args, out HashSet projectionReferenceAssemblies); args.Token.ThrowIfCancellationRequested(); @@ -52,7 +51,8 @@ private static CSharpCompilation CreateCompilationForProjection(string sourcesFo List syntaxTrees = []; foreach (string file in Directory.GetFiles(sourcesFolder, "*.cs")) { - syntaxTrees.Add(CSharpSyntaxTree.ParseText(File.ReadAllText(file), path: file)); + using Stream stream = File.OpenRead(file); + syntaxTrees.Add(CSharpSyntaxTree.ParseText(SourceText.From(stream), path: file)); } // Build references list From 9b2ddb9e4625dcc5aa32056030a9954767d21f93 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Mon, 8 Dec 2025 15:47:34 -0800 Subject: [PATCH 15/15] Cleanup InternalsVisisbleTo --- src/Projections/Test/Test.csproj | 1 - .../TestHost.ProbeByClass/TestHost.ProbeByClass.csproj | 1 - src/Projections/TestSubset/TestSubset.csproj | 1 - src/Projections/Windows/Windows.csproj | 1 - src/Tests/UnitTest/ComInteropTests.cs | 4 ++-- 5 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Projections/Test/Test.csproj b/src/Projections/Test/Test.csproj index 77e452072..d15aa7b43 100644 --- a/src/Projections/Test/Test.csproj +++ b/src/Projections/Test/Test.csproj @@ -15,7 +15,6 @@ - diff --git a/src/Projections/TestHost.ProbeByClass/TestHost.ProbeByClass.csproj b/src/Projections/TestHost.ProbeByClass/TestHost.ProbeByClass.csproj index 7ff120ddf..155acb0ca 100644 --- a/src/Projections/TestHost.ProbeByClass/TestHost.ProbeByClass.csproj +++ b/src/Projections/TestHost.ProbeByClass/TestHost.ProbeByClass.csproj @@ -12,7 +12,6 @@ - diff --git a/src/Projections/TestSubset/TestSubset.csproj b/src/Projections/TestSubset/TestSubset.csproj index 1c5100bb8..fbf2a38d8 100644 --- a/src/Projections/TestSubset/TestSubset.csproj +++ b/src/Projections/TestSubset/TestSubset.csproj @@ -15,7 +15,6 @@ - diff --git a/src/Projections/Windows/Windows.csproj b/src/Projections/Windows/Windows.csproj index 020f8f45f..0a57c3399 100644 --- a/src/Projections/Windows/Windows.csproj +++ b/src/Projections/Windows/Windows.csproj @@ -10,7 +10,6 @@ - diff --git a/src/Tests/UnitTest/ComInteropTests.cs b/src/Tests/UnitTest/ComInteropTests.cs index 111b76fbd..40df497f0 100644 --- a/src/Tests/UnitTest/ComInteropTests.cs +++ b/src/Tests/UnitTest/ComInteropTests.cs @@ -56,8 +56,8 @@ public void TestHWND() public void TestMockDragDropManager() { var interop = (WinRT.Interop.IDragDropManagerInterop)Class.ComInterop; - Guid iid = typeof(ICoreDragDropManager).GUID; - var manager = interop.GetForWindow(new IntPtr(0), iid); + Guid iid_ICoreDragDropManager = new("7D56D344-8464-4FAF-AA49-37EA6E2D7BD1"); + var manager = interop.GetForWindow(new IntPtr(0), iid_ICoreDragDropManager); Assert.NotNull(manager); }