Port WindowsRuntime.Internal.idl to C# in new WinRT.Internal project#2429
Open
Sergio0694 wants to merge 11 commits into
Open
Port WindowsRuntime.Internal.idl to C# in new WinRT.Internal project#2429Sergio0694 wants to merge 11 commits into
Sergio0694 wants to merge 11 commits into
Conversation
Add a new SDK project src/WinRT.Internal/WinRT.Internal.csproj targeting net10.0 with implicit usings and nullable enabled. Also update cswinrt.slnx to include the new project (solution entry: WinRT.Internal/Windows.Internal.csproj).
Port the contents of src/cswinrt/WindowsRuntime.Internal.idl to C# source files in the new src/WinRT.Internal project, in preparation for removing the legacy C++ cswinrt project. The C# definitions cover: - HWND struct (custom-mapped to System.IntPtr by the projection writer). - [ProjectionInternal] custom attribute (consumed by CsWinRT to emit internal projections for the marked interfaces). - All 14 interop interfaces (IAccountsSettingsPaneInterop, IDisplayInformationStaticsInterop, IDragDropManagerInterop, IInputPaneInterop, IPlayToManagerInterop, IPrintManagerInterop, IRadialControllerConfigurationInterop, IRadialControllerIndependentInputSourceInterop, IRadialControllerInterop, ISpatialInteractionManagerInterop, ISystemMediaTransportControlsInterop, IUIViewSettingsInterop, IUserConsentVerifierInterop, IWebAuthenticationCoreManagerInterop) with their original IIDs, parameter lists, and reference links to the corresponding Win32 *interop.idl documentation pages. Each interface carries [Guid(...)] (from System.Runtime.InteropServices) and [ProjectionInternal], and exposes the same methods as the IDL with the same parameter order. The "riid" out-parameter selector uses "ref Guid riid" rather than "in" / "[In] ref" so that the C# compiler emits a plain "Guid&" byref without "modreq(InAttribute)" / "modopt(IsReadOnlyAttribute)" decorations. The csproj targets "net10.0-windows10.0.26100.1" (revision .1 selects the cswinrt3 Windows SDK projection reference assemblies, which carry [WindowsRuntimeMetadata] attributes the WinMD generator reads) and pins WindowsSdkPackageVersion to the matching SDK NET Ref package so the implicit FrameworkReference resolves. All CsWinRT MSBuild processing (CsWinRTEnabled / CsWinRTGenerateProjection / CsWinRTGenerateInteropAssembly[2]) is disabled because this project itself feeds back into the CsWinRT build pipeline. It references the locally built WinRT.Runtime.dll directly via a HintPath so it does not require the Microsoft.Windows.CsWinRT NuGet package. The output .dll is only an intermediate artifact; the .winmd that the CsWinRT toolchain consumes will be wired up in a follow-up commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sion Two small fixes to the WinMD generator needed for the C# port of WindowsRuntime.Internal.idl to produce metadata compatible with both the existing toolchain and the original C++-generated .winmd: 1. Preserve [In] / [Out] parameter direction flags from input parameters. Previously, GetWinRTParameterAttributes unconditionally returned ParameterAttributes.Out for any by-reference parameter, which is wrong for "ref Guid riid"-style COM interop signatures that MIDL emits as [in] ref. The new logic honors any explicit In/Out flag already set by the C# compiler on the input parameter, and only falls back to a type-based heuristic when no flag is set (Span<T> -> [out], everything else including byref -> [in]). This matches MIDL's convention for "ref const T" parameters and keeps the projection writer's ParameterCategoryResolver classifying them as Ref rather than Out. 2. Always stamp the .winmd assembly identity with version 255.255.255.255 (the WinRT "unbound" convention used by every Windows Runtime metadata assembly). The "--assembly-version" argument is still used as-is for the type-level "[Windows.Foundation.Metadata.Version]" attribute applied to each authored type. Verified against the existing TestComponent and TestComponentCSharp .winmd outputs, both of which carry v255.255.255.255 as their assembly identity regardless of the project's <AssemblyVersion>. Also bumps the WinRT.Internal csproj to use AssemblyName "WindowsRuntime.Internal" so the produced .dll (and its .winmd) match the identity that the rest of the CsWinRT toolchain expects. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds an MSBuild target to WinRT.Internal.csproj that invokes 'cswinrtwinmdgen.exe' (built from this solution) after CoreCompile and emits 'WindowsRuntime.Internal.winmd' next to the compiled 'WindowsRuntime.Internal.dll' in '$(TargetDir)'. Adds ProjectReference items to WinRT.WinMD.Generator (the tool 'cswinrtwinmdgen.exe') and WinRT.Generator.Tasks (the RunCsWinRTWinMDGenerator MSBuild task) with ReferenceOutputAssembly=false so they participate in the build graph without becoming runtime references. The 'UsingTask' resolves the task assembly out of the locally built WinRT.Generator.Tasks output, and the tool path is computed from the locally built WinRT.WinMD.Generator output. 'CsWinRTToolsArchitecture=AnyCPU' is passed so the tool task uses the top-level 'cswinrtwinmdgen.exe' apphost wrapper (no per-arch publish folder). Verified: the produced 'WindowsRuntime.Internal.winmd' contains exactly the same set of public types as the legacy C++-generated 'WindowsRuntime.Internal.winmd' (HWND struct, all 14 interop interfaces with their original IIDs and method counts, and the ProjectionInternalAttribute class). The only extra entry is a synthesized 'IProjectionInternalAttributeClass' interface that the C# WinMD generator emits for attribute classes; it is non-public and is filtered out by the projection writer's 'AssemblyAnalyzer.DiscoverPublicTypes' step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Now that WinRT.Internal produces WindowsRuntime.Internal.winmd via the
C# WinMD generator, point all build infrastructure at the new file
instead of the legacy cswinrt (C++) bin directory. The output cswinrt
project still exists on disk for now but no longer needs to be built
for any consumer of WindowsRuntime.Internal.winmd to function; that
project will be deleted in a follow-up commit.
Specifically:
- src/Directory.Build.props:
- 'CsWinRTInteropMetadata' now resolves to
'src/WinRT.Internal/bin/$(Configuration)/$(CsWinRTInternalTargetFramework)/WindowsRuntime.Internal.winmd'
(with 'CsWinRTInternalTargetFramework' defaulting to
'net10.0-windows10.0.26100.1', matching the WinRT.Internal csproj
TFM that selects the cswinrt3 SDK projection reference assemblies).
- 'AssemblyVersionNumber' now defaults to '3.0.0.0' (the WinRT
runtime baseline for CsWinRT 3.0). Without this, dev builds of
WinRT.Runtime would stamp v1.0.0.0 by default, and the cswinrt3
SDK projection would fail to resolve against the local runtime.
- 'CsWinRTPath' / 'CsWinRTExe' are kept defined (still pointing at
the legacy cswinrt bin folder), but only as path strings: the C#
'cswinrtprojectiongen.exe' tool no longer reads or executes the
.exe path, so the file no longer needs to exist. They will be
removed entirely once 'src/cswinrt' itself is dropped.
- src/build.cmd: 'interop_winmd' now points at the WinRT.Internal
output instead of '_build/{platform}/{config}/cswinrt/bin/'.
- build/AzurePipelineTemplates/CsWinRT-Build-Steps.yml: the "Stage
WindowsRuntime.Internal.winmd" task copies from
'src/WinRT.Internal/bin/$(BuildConfiguration)/net10.0-windows10.0.26100.1/'
rather than the cswinrt bin folder; staging into
'$(StagingFolder)/native' is preserved so the downstream nuget pack
step (CsWinRT-PublishToNuGet-Steps.yml) still finds the file at the
same place.
- src/cswinrt.slnx: WinRT.Internal no longer carries '<Build
Project="false" />' so it participates in default solution-level
builds.
- src/WinRT.Internal/WinRT.Internal.csproj: switches from
'<Reference HintPath="..." />' to '<ProjectReference Private="false"
/>' for WinRT.Runtime so that 'dotnet build' on this project alone
implicitly builds WinRT.Runtime2 first (no copy of WinRT.Runtime.dll
into bin).
Verified end-to-end: 'dotnet build src/WinRT.Sdk.Projection/...' now
consumes the new WindowsRuntime.Internal.winmd and produces
WinRT.Sdk.Projection.dll containing the expected
'ABI.WindowsRuntime.Internal.IAccountsSettingsPaneInteropMethods'
internal projection (alongside the regular projected
'Windows.UI.ApplicationSettings.AccountsSettingsPane' runtime class),
confirming the full interop pipeline still works.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refactor several WinRT.Internal sources and adjust project settings: - HWND.cs: add pragma to suppress naming warning, rename reserved field to __Reserved, document projection to nint and add reference link. - Interop interfaces: normalize GUID literals to uppercase for IPrintManagerInterop, IRadialControllerConfigurationInterop, ISystemMediaTransportControlsInterop, and IUIViewSettingsInterop. - ProjectionInternalAttribute.cs: add C# language tag to the code sample and convert the attribute type to a forward declaration (public sealed class ProjectionInternalAttribute : Attribute;). - WinRT.Internal.csproj: clean up XML/comments, include BOM, disable nullable (Windows Runtime types don't support nullability annotations), adjust NoWarn entries, inline ProjectReference entries, and reformat UsingTask/Target elements. These changes improve consistency, clarify projection mappings, and update build settings to match the Windows Runtime metadata requirements.
When Visual Studio (or the persistent 'dotnet build-server') loads 'WinRT.Generator.Tasks.dll' to run the 'RunCsWinRTWinMDGenerator' custom MSBuild task, the main MSBuild process keeps the assembly loaded for the lifetime of the build session. The 'ProjectReference' from WinRT.Internal to WinRT.Generator.Tasks then triggers WinRT.Generator.Tasks to rebuild on the next incremental build (or the next 'right-click -> Build' in VS), which fails with: warning MSB3026: Could not copy ... WinRT.Generator.Tasks.dll ... The file is locked by: "MSBuild.exe (...) , MSBuild.exe (...), ..." error MSB3027: ... Exceeded retry count of 10. Adding 'TaskFactory="TaskHostFactory"' to the 'UsingTask' loads the task in a separate 'MSBuild.TaskHost' child process, which exits after the build, so the main MSBuild process never keeps a handle to 'WinRT.Generator.Tasks.dll'. Subsequent rebuilds can then overwrite it without contention. Verified with three back-to-back 'dotnet build' invocations that each trigger 'CoreCompile' on WinRT.Internal (and, in the third case, a rebuild of WinRT.Generator.Tasks itself): all complete cleanly with 0 warnings / 0 errors and 'WindowsRuntime.Internal.winmd' gets regenerated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add explicit properties and invoke the WinMD generator task to produce this project's .winmd and ensure incremental builds recognize the generated file. Introduces _CsWinRTWinMDGenToolsDirectory and _CsWinRTInternalWinMDOutputPath, calls RunCsWinRTWinMDGenerator with those paths, and adds a FileWrites entry for the generated .winmd. Also reformats the UsingTask tag and adds inline comments for clarity.
The previous 'TaskFactory="TaskHostFactory"' attempt did not fully
resolve the file-lock issue in Visual Studio: VS keeps multiple
'MSBuild.exe' build-server processes alive across IntelliSense and
build sessions, and at least one of them ends up holding a handle to
'WinRT.Generator.Tasks.dll' even with the task host opt-in. The result
in VS is the same 'MSB3027' (file locked by multiple 'MSBuild.exe'
processes) error on every other build of WinRT.Internal.
The root issue is that this project depends on its own custom MSBuild
task ('RunCsWinRTWinMDGenerator' from WinRT.Generator.Tasks) and is
also part of the build graph that produces that task assembly via a
'ProjectReference'. Any time MSBuild loads the task DLL during a build
session and VS later tries to rebuild it, the copy fails.
Rather than chase that contention, this commit removes the custom-task
dependency for this project entirely:
- Drops the 'ProjectReference' to WinRT.Generator.Tasks (no longer
needed).
- Drops the 'UsingTask' / 'RunCsWinRTWinMDGenerator' call.
- Invokes 'cswinrtwinmdgen.exe' directly via a stock '<Exec>' task
with a generated '.rsp' response file passed as '@<file>' (the same
format 'ConsoleAppFramework' / 'WinMDGeneratorArgs.ParseFromResponseFile'
already understands).
The response file is written under '$(IntermediateOutputPath)' via
'<WriteLinesToFile WriteOnlyWhenDifferent="true">' and registered as
a 'FileWrites' item so it's tracked by the incremental-build cache.
The end-to-end behavior is identical to before — same arguments,
same generator, same '.winmd' output — but the build never loads
'WinRT.Generator.Tasks.dll' into MSBuild's process space, so it can
freely be rebuilt at any time.
Verified with four back-to-back 'dotnet build' invocations on the
same build server, each triggering a different point in the
dependency chain (HWND.cs source change -> rebuild of
'WinRT.Generator.Tasks.dll' itself -> rebuild of
'cswinrtwinmdgen.exe' itself), all completing with 0 errors and the
'.winmd' regenerated correctly each time.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reformat and clarify WinRT.Internal.csproj: normalize header, collapse XML comment into a block, wrap ProjectReference attributes across lines for readability, tweak comment wording, add comments around preparing/writing the generator '.rsp' file, and mark the response and output files in FileWrites to help incremental builds. Also small doc fix in WinMDWriter: fully qualify ReadOnlySpan/Span as System.ReadOnlySpan/System.Span in XML docs to avoid ambiguity.
Clarify and normalize in-code comments and formatting across the repo and remove a stale comment in the build script. Changes touch Directory.Build.props, WinMD.WinMD.Generator (WinMDValues.cs, WinMDWriter.Members.cs, WinMDWriter.cs) and build.cmd. All edits are comment/whitespace-level (consistent quoting, naming like 'Windows Runtime'/'WinRT.Runtime', and punctuation) and do not change runtime behavior.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Port the contents of
src/cswinrt/WindowsRuntime.Internal.idl(thehand-authored Windows Runtime metadata that the legacy C++
cswinrttool used to compile into
WindowsRuntime.Internal.winmd) to a newC#
WinRT.Internalproject, and wire the existing C# WinMD generatorin as the producer of the
.winmd. The newWinRT.Internalprojectnow owns this artifact end-to-end so the CsWinRT toolchain no longer
needs the legacy C++
cswinrtproject to be built at all.Motivation
The legacy C++
cswinrtproject does two things: it producescswinrt.exe(the old projection compiler) and it compilesWindowsRuntime.Internal.idlintoWindowsRuntime.Internal.winmd.The first responsibility has already been ported to C# (the
WinRT.Projection.Writerlibrary +cswinrtprojectiongen.exe); thesecond was still tying the rest of the build to the C++ project.
Moving the
.idlto a C# source-of-truth + the C# WinMD generatorremoves that dependency. A follow-up PR can now delete
src/cswinrtentirely.
Changes
New
WinRT.InternalC# projectsrc/WinRT.Internal/WinRT.Internal.csproj: targetsnet10.0-windows10.0.26100.1(TFM revision.1selects thecswinrt3Windows SDK projection reference assemblies which carry[WindowsRuntimeMetadata]attributes the WinMD generator reads).Pins
WindowsSdkPackageVersionso the SDK adds the matchingframework reference. Disables all CsWinRT MSBuild processing
(
CsWinRTEnabled/CsWinRTGenerateProjection/CsWinRTGenerateInteropAssembly[2]) since this project itselffeeds back into the CsWinRT toolchain. References the locally built
WinRT.Runtime.dlldirectly via aProjectReference Private="false".src/WinRT.Internal/HWND.cs: struct counterpart of the IDLHWND(custom-mapped tonintby the projection writer).src/WinRT.Internal/ProjectionInternalAttribute.cs: forwarddeclaration of the
[ProjectionInternal]marker the projectionwriter reads to emit
internalprojections.src/WinRT.Internal/I*Interop.cs: one file per interopinterface, all 14 of the IDL ones
(
IAccountsSettingsPaneInterop,IDragDropManagerInterop,IInputPaneInterop,IPlayToManagerInterop,IPrintManagerInterop,IRadialControllerInterop,IRadialControllerConfigurationInterop,IRadialControllerIndependentInputSourceInterop,ISpatialInteractionManagerInterop,ISystemMediaTransportControlsInterop,IUIViewSettingsInterop,IUserConsentVerifierInterop,IWebAuthenticationCoreManagerInterop,IDisplayInformationStaticsInterop) with their original IIDs,parameter ordering, and reference links to the corresponding
Win32
*interop.idldocumentation pages.WinMD generator improvements
Two small fixes to
WinRT.WinMD.Generatorwere needed for the outputto be functionally equivalent to the C++
cswinrt-generated.winmd:src/WinRT.WinMD.Generator/Writers/WinMDWriter.Members.cs:GetWinRTParameterAttributesnow preserves the input parameter's[In]/[Out]flags rather than unconditionally emitting[Out]for any by-reference parameter. When no flag is set on the input,
byref params default to
[In](matching MIDL's convention forref const T), keeping the projection writer'sParameterCategoryResolverclassifyingref Guid riidasParameterCategory.Refrather thanOut.src/WinRT.WinMD.Generator/Writers/WinMDWriter.cs+src/WinRT.WinMD.Generator/References/WinMDValues.cs:The authored
.winmdassembly identity is now always stamped255.255.255.255(the WinRT "unbound" convention used by everyWindows Runtime metadata assembly). The
--assembly-versionargument continues to drive the type-level
[Windows.Foundation.Metadata.Version]attribute. Verified againstthe existing
TestComponent/TestComponentCSharp.winmdoutputs, both of which carry
v255.255.255.255regardless of theproject's
<AssemblyVersion>.MSBuild integration
src/WinRT.Internal/WinRT.Internal.csproj(targetGenerateWindowsRuntimeInternalWinMD): writes a response file to$(IntermediateOutputPath)and invokescswinrtwinmdgen.exedirectly via
<Exec>(instead of aUsingTask). This avoids theMSB3027file-lock contention that happens in Visual Studio whenthe persistent MSBuild build server keeps a custom-task assembly
loaded across incremental builds — MSBuild never needs to load
WinRT.Generator.Tasks.dllfor this project at all.Build infrastructure rewiring
src/Directory.Build.props:$(CsWinRTInteropMetadata)nowresolves to
src/WinRT.Internal/bin/$(Configuration)/net10.0-windows10.0.26100.1/WindowsRuntime.Internal.winmd.$(AssemblyVersionNumber)defaults to3.0.0.0sodotnet buildworks locally without explicit
/p:AssemblyVersionNumber=....Legacy
$(CsWinRTPath)/$(CsWinRTExe)are kept defined aspath strings (the
.exeno longer needs to exist; they will beremoved entirely once
src/cswinrtis dropped).src/build.cmd:interop_winmdnow points at the WinRT.Internaloutput instead of
_build/.../cswinrt/bin/.build/AzurePipelineTemplates/CsWinRT-Build-Steps.yml: the"Stage WindowsRuntime.Internal.winmd" task copies from
src/WinRT.Internal/bin/.../net10.0-windows10.0.26100.1/ratherthan the
cswinrtbin folder; downstream staging into$(StagingFolder)/nativeis unchanged so the nuget pack stepstill finds it at the same place.
src/cswinrt.slnx:WinRT.Internalis included in thedefault solution-level build.
Verification
WindowsRuntime.Internal.winmdcontains exactly the sameset of public types (
HWND+ 14 interop interfaces +ProjectionInternalAttribute) with identical IIDs and methodcounts as the legacy C++-generated
WindowsRuntime.Internal.winmd.dotnet build src/WinRT.Sdk.Projection/...successfully consumesthe new
.winmdand produces aWinRT.Sdk.Projection.dllcontaining the expected
ABI.WindowsRuntime.Internal.IAccountsSettingsPaneInteropMethodsinternal projection alongside the regular
Windows.UI.ApplicationSettings.AccountsSettingsPaneruntimeclass, confirming the full interop pipeline still works.
rebuilds of
WinRT.Generator.Tasks.dllandcswinrtwinmdgen.exethemselves while the build session is alive) all complete cleanly
with
0errors — noMSB3027file-lock issues.Follow-up
The legacy
src/cswinrtdirectory is still on disk for now (noconsumer depends on it being built). It will be deleted entirely in
a follow-up PR.