diff --git a/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets b/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets
index 1c3ad8a56..919848f48 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
@@ -17,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
<_RunCsWinRTGeneratorPropertyInputsCachePath Condition="'$(_RunCsWinRTGeneratorPropertyInputsCachePath)' == ''">$(IntermediateOutputPath)$(MSBuildProjectName).cswinrtgen.cache
<_RunCsWinRTGeneratorPropertyInputsCachePath>$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '$(_RunCsWinRTGeneratorPropertyInputsCachePath)'))
+
+
+ $(IntermediateOutputPath)\forwarder\
+ <_CsWinRTGeneratorForwarderAssemblyPath>$(CsWinRTGeneratorForwarderAssemblyDirectory)$(AssemblyName).dll
+ <_CsWinRTRefAssemblyPath>$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '$(IntermediateOutputPath)$(AssemblyName).dll'))
+
+
+ <_CsWinRTGeneratorMergedProjectionAssemblyPath>$(IntermediateOutputPath)WinRT.Projection.dll
+ <_CsWinRTGeneratorMergedProjectionOutputAssemblyPath>WinRT.Projection.dll
+
+ $(DefineConstants);CSWINRT_REFERENCE_PROJECTION
-
+
+
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTEffectiveToolsDirectory)" />
@@ -125,17 +157,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'">
+
+
+
+
+
+
+
+
+
+
+
+
+ <_SourceItemsToCopyToOutputDirectory
+ Include="@(IntermediateAssembly)"
+ TargetPath="ref\$(AssemblyName).dll" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(_CsWinRTRefAssemblyPath)
+ @(CsWinRTInputs)
+
+
+
+
+
+
+
+
+
+ <_ReferencePathsWithWinMDs Include="@(ReferencePathWithRefAssemblies)" Condition="'%(ReferencePathWithRefAssemblies.CsWinRTInputs)' != ''" />
+ <_WinMDPathsList Include="@(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..661c3d027 100644
--- a/nuget/Microsoft.Windows.CsWinRT.targets
+++ b/nuget/Microsoft.Windows.CsWinRT.targets
@@ -13,6 +13,10 @@ Copyright (C) Microsoft Corporation. All rights reserved.
false
false
true
+ false
+ false
+ true
+ false
true
+
+ <_DebugSymbolsIntermediatePath Remove="@(_DebugSymbolsIntermediatePath)" />
+
+
@@ -259,6 +270,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.
-public_enums
-public_exclusiveto
-idic_exclusiveto
+ -reference_projection
$(CsWinRTCommandVerbosity)
@@ -272,6 +284,7 @@ $(CsWinRTEmbeddedProjection)
$(CsWinRTEmbeddedEnums)
$(CsWinRTPublicExclusiveTo)
$(CsWinRTDynamicallyInterfaceCastableExclusiveTo)
+$(CsWinRTReferenceProjection)
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/Directory.Packages.props b/src/Directory.Packages.props
index 5b1aa5b5c..5ff2ad70a 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -12,9 +12,9 @@
-
-
-
+
+
+
diff --git a/src/Projections/Directory.Build.props b/src/Projections/Directory.Build.props
index a29cc3d46..51c066c82 100644
--- a/src/Projections/Directory.Build.props
+++ b/src/Projections/Directory.Build.props
@@ -25,7 +25,8 @@
true
true
- true
+ 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/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/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 9755336b3..7c0c7c3ac 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/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);
}
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/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);
+ }
+}
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/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs
index e3f4d8ff9..1061622e3 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
@@ -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;
}
@@ -275,6 +275,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/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs b/src/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs
index 8d67601db..1d92d2d3e 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 21cfc46d8..805e10a6c 100644
--- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.UserDefinedType.cs
+++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.UserDefinedType.cs
@@ -131,13 +131,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 49493ab62..eb723672e 100644
--- a/src/WinRT.Interop.Generator/Helpers/GuidGenerator.cs
+++ b/src/WinRT.Interop.Generator/Helpers/GuidGenerator.cs
@@ -9,7 +9,6 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;
using WindowsRuntime.InteropGenerator.References;
-using WindowsRuntime.InteropGenerator.Resolvers;
namespace WindowsRuntime.InteropGenerator.Helpers;
@@ -66,15 +65,6 @@ public static bool TryGetIIDFromWellKnownInterfaceIIDsOrAttribute(
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 (typeDefinition.TryGetGuidAttribute(interopReferences, out iid))
{
diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs
index a09d59b45..c47e2dbf6 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/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..8e9a7449f
--- /dev/null
+++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs
@@ -0,0 +1,99 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using ConsoleAppFramework;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Emit;
+using Microsoft.CodeAnalysis.Text;
+using WindowsRuntime.ProjectionGenerator.Errors;
+
+namespace WindowsRuntime.ProjectionGenerator.Generation;
+
+///
+internal partial class ProjectionGenerator
+{
+ private const 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))];
+
+ ConsoleApp.Log("Compiling merged projection");
+
+ 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"))
+ {
+ using Stream stream = File.OpenRead(file);
+ syntaxTrees.Add(CSharpSyntaxTree.ParseText(SourceText.From(stream), 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..d001c9cd4
--- /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 addition 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..feb668613
--- /dev/null
+++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs
@@ -0,0 +1,54 @@
+// 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
+ {
+ ConsoleApp.Log($"Processing {args.ReferenceAssemblyPaths.Length + 1} modules");
+
+ 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..0f72f4368
--- /dev/null
+++ b/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj
@@ -0,0 +1,38 @@
+
+
+ Exe
+ net10.0
+ preview
+ enable
+ true
+ true
+ true
+ true
+ Speed
+ false
+
+
+ true
+
+
+ WindowsRuntime.ProjectionGenerator
+
+
+ cswinrtprojectiongen
+
+
+ true
+ true
+ latest
+ latest-all
+ true
+ strict
+ true
+
+
+
+
+
+
+
+
diff --git a/src/cswinrt.slnx b/src/cswinrt.slnx
index 7db15b0a3..5257edd28 100644
--- a/src/cswinrt.slnx
+++ b/src/cswinrt.slnx
@@ -175,8 +175,7 @@
-
-
+
@@ -356,6 +355,7 @@
+
diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h
index 13ff88e44..8f3969cf4 100644
--- a/src/cswinrt/code_writers.h
+++ b/src/cswinrt/code_writers.h
@@ -1588,6 +1588,12 @@ namespace cswinrt
void write_abi_static_method_call(writer& w, type_semantics const& iface, MethodDef const& method, std::string const& targetObjRef)
{
+ if (settings.reference_projection)
+ {
+ w.write("throw null");
+ return;
+ }
+
method_signature signature{ method };
w.write("%.%(%%%)", bind(iface, typedef_name_type::StaticAbiClass, true),
method.Name(),
@@ -1598,6 +1604,12 @@ namespace cswinrt
void write_unsafe_accessor_static_method_call(writer& w, std::string const& unsafeAccessorMethod, MethodDef const& method, std::string const& targetObjRef)
{
+ if (settings.reference_projection)
+ {
+ w.write("throw null");
+ return;
+ }
+
method_signature signature{ method };
w.write("%(null, %%%)",
unsafeAccessorMethod,
@@ -1608,6 +1620,12 @@ namespace cswinrt
void write_abi_get_property_static_method_call(writer& w, type_semantics const& iface, Property const& prop, std::string const& targetObjRef)
{
+ if (settings.reference_projection)
+ {
+ w.write("throw null");
+ return;
+ }
+
w.write("%.%(%)",
bind(iface, typedef_name_type::StaticAbiClass, true),
prop.Name(),
@@ -1616,6 +1634,12 @@ namespace cswinrt
void write_abi_set_property_static_method_call(writer& w, type_semantics const& iface, Property const& prop, std::string const& targetObjRef)
{
+ if (settings.reference_projection)
+ {
+ w.write("throw null");
+ return;
+ }
+
w.write("%.%(%, value)",
bind(iface, typedef_name_type::StaticAbiClass, true),
prop.Name(),
@@ -1624,6 +1648,12 @@ namespace cswinrt
void write_unsafe_accessor_property_static_method_call(writer& w, std::string const& unsafeAccessorMethod, std::string const& targetObjRef, bool get)
{
+ if (settings.reference_projection)
+ {
+ w.write("throw null");
+ return;
+ }
+
w.write("%(null, %%)",
unsafeAccessorMethod,
targetObjRef,
@@ -1632,6 +1662,12 @@ namespace cswinrt
void write_abi_event_source_static_method_call(writer& w, type_semantics const& iface, Event const& evt, bool isSubscribeCall, std::string const& targetObjRef, bool is_static_event = false)
{
+ if (settings.reference_projection)
+ {
+ w.write("throw null");
+ return;
+ }
+
bool is_unsafe_accessor_call = false;
w.write("%(%, %).%(value)",
bind([&](writer& w) {
@@ -2611,6 +2647,22 @@ private static % _% = new %("%.%", %.IID);
void write_activation_factory_objref_definition(writer& w, TypeDef const& classType)
{
auto objrefname = w.write_temp("%", bind(classType));
+ if (settings.reference_projection)
+ {
+ w.write(R"(
+private static WindowsRuntimeObjectReference %
+{
+ get
+ {
+ throw null;
+ }
+}
+)",
+ objrefname);
+ return;
+ }
+
+
w.write(R"(
private static WindowsRuntimeObjectReference %
{
@@ -2636,6 +2688,22 @@ private static WindowsRuntimeObjectReference %
void write_static_objref_definition(writer& w, TypeDef const& staticsType, TypeDef const& classType)
{
auto objrefname = w.write_temp("%", bind(staticsType));
+ if (settings.reference_projection)
+ {
+ w.write(R"(
+private static WindowsRuntimeObjectReference %
+{
+ get
+ {
+ throw null;
+ }
+}
+)",
+ objrefname);
+ return;
+ }
+
+
w.write(R"(
private static WindowsRuntimeObjectReference %
{
@@ -2916,6 +2984,11 @@ if (GetType() == typeof(%))
}
}
+ if (settings.reference_projection)
+ {
+ return;
+ }
+
w.write(R"(
protected %(WindowsRuntimeActivationTypes.DerivedComposed _, WindowsRuntimeObjectReference activationFactoryObjectReference, in Guid iid)
:base(_, activationFactoryObjectReference, in iid)
@@ -4703,8 +4776,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());
}
@@ -6219,25 +6308,37 @@ public override unsafe void Invoke(
ReadOnlySpan