Migrate Windows interop to CsWin32 source-generated P/Invoke#13576
Migrate Windows interop to CsWin32 source-generated P/Invoke#13576JeremyKuhne merged 10 commits intodotnet:mainfrom
Conversation
🔍 Skill Validator Results✅ All checks passed
Summary
Full validator output```text Found 2 skill(s) [cswin32-com] 📊 cswin32-com: 1,258 BPE tokens [chars/4: 1,188] (detailed ✓), 8 sections, 3 code blocks [cswin32-interop] 📊 cswin32-interop: 1,533 BPE tokens [chars/4: 1,474] (detailed ✓), 12 sections, 3 code blocks ✅ All checks passed (2 skill(s)) ``` |
There was a problem hiding this comment.
Pull request overview
Migrates MSBuild’s Windows interop surface from hand-written P/Invokes and [ComImport] COM interfaces to CsWin32 source-generated interop, with compile-time gating to support Native AOT and source-only builds.
Changes:
- Introduces CsWin32 configuration + build gating (
FEATURE_WINDOWSINTEROP/FeatureWindowsInterop) and updates projects/tests to consumeWindows.Win32.PInvokeAPIs. - Adds new interop helper types (e.g.,
BufferScope<T>,ComScope<T>,ComClassFactory,IID, partials forHRESULT/BSTR/VARIANT/FILETIME) to enable AOT-friendly patterns. - Reworks Windows command-line retrieval to support WMI + Debug Engine sources and expands unit test coverage for the new behavior.
Reviewed changes
Copilot reviewed 53 out of 53 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Utilities.UnitTests/ProcessExtensions_Tests.cs | Adds tests for new command-line retrieval sources and edge cases. |
| src/Tasks/Unzip.cs | Adjusts platform guard logic for Unix file modes (CA1416-friendly). |
| src/Tasks/NativeMethods.cs | Removes legacy COM interface and updates HRESULT usage for mscoree interop. |
| src/Tasks/Microsoft.Build.Tasks.csproj | Includes moved IFixedTypeInfo definition and minor formatting. |
| src/Tasks/IFixedTypeInfo.cs | Moves the fixed ITypeInfo COM interface into its own file. |
| src/Tasks/FileState.cs | Switches file attribute queries to CsWin32 GetFileAttributesEx. |
| src/Tasks/ComReference.cs | Uses HMODULE + PInvoke.LoadLibrary/FreeLibrary/GetModuleFileName with BufferScope. |
| src/Tasks/AssemblyDependency/GlobalAssemblyCache.cs | Uses CsWin32 HRESULT.S_OK and removes dead non-Windows path for net472. |
| src/Tasks/AssemblyDependency/AssemblyInformation.cs | Removes dead non-Windows net472 paths and updates HRESULT / buffer logic. |
| src/Tasks.UnitTests/AddToWin32Manifest_Tests.cs | Updates platform attributes and switches to PInvoke.FreeLibrary. |
| src/Shared/UnitTests/NativeMethodsShared_Tests.cs | Updates to PInvoke.LoadLibrary/GetProcAddress/FreeLibrary and versioned platform attributes. |
| src/Framework/Windows/Win32/System/Variant/VARIANT.cs | Adds disposable/utility partial for CsWin32 VARIANT. |
| src/Framework/Windows/Win32/System/Com/ComScope.cs | Adds COM pointer lifetime management for struct-based COM. |
| src/Framework/Windows/Win32/System/Com/ComClassFactory.cs | Adds AOT-friendly COM activation via CoGetClassObject. |
| src/Framework/Windows/Win32/PInvoke.GetFileAttributesEx.cs | Adds a typed overload wrapper for GetFileAttributesEx. |
| src/Framework/Windows/Win32/IID.cs | Adds generic IID lookup helper for IComIID COM structs. |
| src/Framework/Windows/Win32/GeneratedInteropClsCompliance.cs | Applies [CLSCompliant(false)] via partials for generated COM structs. |
| src/Framework/Windows/Win32/Foundation/HRESULT.cs | Adds helper conversions and FromLastError() for HRESULT. |
| src/Framework/Windows/Win32/Foundation/FileTimeExtensions.cs | Adds FILETIME conversion helper (ToLong). |
| src/Framework/Windows/Win32/Foundation/BSTR.cs | Adds disposable/safe construction partial for BSTR. |
| src/Framework/Utilities/Wmi/IWbemServices.cs | Adds struct-based COM definition for WMI IWbemServices. |
| src/Framework/Utilities/Wmi/IWbemLocator.cs | Adds struct-based COM definition for WMI IWbemLocator (incl. CLSID). |
| src/Framework/Utilities/Wmi/IWbemClassObject.cs | Adds struct-based COM definition for WMI IWbemClassObject. |
| src/Framework/Utilities/Wmi/IEnumWbemClassObject.cs | Adds struct-based COM definition for WMI enumeration. |
| src/Framework/Utilities/TypeInfo.cs | Adds cached IsReferenceOrContainsReferences helper (net472 polyfill). |
| src/Framework/Utilities/ProcessExtensions.cs | Adds selectable Windows command-line sources (WMI / DebugEngine) and BufferScope usage. |
| src/Framework/Utilities/BufferScope.cs | Adds stackalloc + ArrayPool buffer helper used across interop. |
| src/Framework/Telemetry/CrashTelemetry.cs | Switches memory stats gathering to MEMORYSTATUSEX via TryGetMemoryStatus. |
| src/Framework/SupportedOSPlatform.cs | Adds UnsupportedOSPlatformGuardAttribute polyfill for older TFMs. |
| src/Framework/Shared/TrimmingAttributes.cs | Adds trimming/AOT attribute polyfills for net472/netstandard2.0. |
| src/Framework/NativeMethods.txt | Declares the Win32 APIs/types for CsWin32 generation. |
| src/Framework/NativeMethods.json | Configures CsWin32 generation (no marshaling, no safe handles, preserve sig). |
| src/Framework/NativeMethods.cs | Large cleanup: replaces hand-written interop with CsWin32 calls + adds guards/helpers. |
| src/Framework/Microsoft.Build.Framework.csproj | Adds CsWin32/PolySharp refs + compile removes/includes for feature-gated interop files. |
| src/Framework/Framework/Windows/Win32/IComIID.cs | Adds net472 instance-based IComIID polyfill for COM structs. |
| src/Framework/FileSystem/WindowsFileSystem.cs | Switches existence checks to PInvoke.GetFileAttributes. |
| src/Framework/FileSystem/MSBuildOnWindowsFileSystem.cs | Updates [SupportedOSPlatform] versioning. |
| src/Framework/FileSystem/FileSystems.cs | Wraps Windows file-system selection in FEATURE_WINDOWSINTEROP. |
| src/Framework/EncodingUtilities.cs | Switches OEM code page query to PInvoke.GetOEMCP. |
| src/Framework/CommunicationsUtilities.cs | Versions [SupportedOSPlatform] attributes to windows6.1. |
| src/Framework.UnitTests/TypeInfo_Tests.cs | Adds tests for the new TypeInfo<T> helper. |
| src/Framework.UnitTests/BufferScope_Tests.cs | Adds tests for BufferScope<T> behavior and edge cases. |
| src/Directory.BeforeCommon.targets | Introduces FEATURE_WINDOWSINTEROP define + FeatureWindowsInterop property gate. |
| src/Build/Logging/SimpleErrorLogger.cs | Updates console mode enable/restore calls to new NativeMethods API signature. |
| src/Build/Logging/InProcessConsoleConfiguration.cs | Switches console mode/file type checks to CsWin32 APIs under feature guard. |
| src/Build/BackEnd/Components/Communications/NodeLauncher.cs | Updates Windows handle closing to PInvoke.CloseHandle and improves platform branching. |
| src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs | Switches memory checks to MEMORYSTATUSEX under FEATURE_WINDOWSINTEROP. |
| src/Build.UnitTests/ConsoleLogger_Tests.cs | Switches stdout handle/file type checks to CsWin32 APIs. |
| src/Build.UnitTests/BackEnd/TargetUpToDateChecker_Tests.cs | Updates platform attributes and uses typed enums for CreateFile flags. |
| eng/dependabot/Directory.Packages.props | Adds package versions for CsWin32 and PolySharp. |
| AGENTS.md | Updates repo guidance to reflect newer C# usage expectations. |
| .github/skills/cswin32-interop/SKILL.md | Adds internal guidance doc for CsWin32-based interop migration. |
| .editorconfig | Suppresses CS3019 for CsWin32-generated/interop folders. |
Comments suppressed due to low confidence (1)
src/Framework/NativeMethods.cs:1243
QueryIsScreenAndTryEnableAnsiColorCodesalways checksConsole.IsOutputRedirected, even whenuseStandardError: true. This means stderr redirection won’t be detected correctly (should useConsole.IsErrorRedirectedwhen targeting stderr), potentially enabling ANSI output when stderr is redirected or disabling it when only stdout is redirected.
internal static (bool acceptAnsiColorCodes, bool outputIsScreen, uint? originalConsoleMode) QueryIsScreenAndTryEnableAnsiColorCodes(bool useStandardError = false)
{
if (Console.IsOutputRedirected)
{
// There's no ANSI terminal support if console output is redirected.
return (acceptAnsiColorCodes: false, outputIsScreen: false, originalConsoleMode: null);
}
There was a problem hiding this comment.
Review Summary
| # | Dimension | Verdict |
|---|---|---|
| 1 | Backwards Compatibility | ✅ LGTM |
| 2 | ChangeWave Discipline | ✅ LGTM (no behavioral change) |
| 3 | Performance & Allocation | HasFlag() boxing on net472 hot paths |
| 4 | Test Coverage | ✅ LGTM — Comprehensive new tests for BufferScope, TypeInfo, ProcessExtensions |
| 5 | Error Message Quality | ✅ LGTM |
| 6 | Logging & Diagnostics | ✅ LGTM |
| 7 | String Comparison | ✅ LGTM |
| 8 | API Surface Discipline | ✅ LGTM — All removed members were internal effective visibility |
| 9 | Cross-Platform Correctness | ❌ BLOCKING — NodeLauncher.cs compile failure in source builds |
| 10 | Target Conventions | ✅ LGTM |
| 11 | Documentation | ✅ LGTM — Thorough SKILL.md and AGENTS.md updates |
| 12 | Code Simplification | ✅ LGTM — Clean removal of dead code |
| 13 | Concurrency & Thread Safety | ✅ LGTM |
| 14 | Naming Conventions | ✅ LGTM |
| 15 | SDK Integration | ✅ LGTM |
| 16 | Evaluation Model | ✅ LGTM |
| 17 | Serialization | ✅ LGTM |
| 18 | Incremental Build | ✅ LGTM |
| 19 | Resource Strings | ✅ LGTM |
| 20 | Interop Correctness | ✅ LGTM — GUID bytes, vtable indices, COM lifetime, BSTR/VARIANT all correct |
| 21 | Trimmability / AOT | ✅ LGTM — Good TrimmingAttributes polyfill |
| 22 | Adversarial Input | ✅ LGTM |
| 23 | Dependency Management | ✅ LGTM — CsWin32 and PolySharp appropriately configured |
| 24 | Security | ✅ LGTM |
Overall Assessment
This is a well-executed CsWin32 migration. The new BufferScope<T>, ComScope<T>, and struct-based COM patterns are clean and follow established conventions (dotnet/sdk patterns). The WMI interface vtable indices and GUID byte sequences are correct. Test coverage is comprehensive.
1 BLOCKING issue:
- NodeLauncher.cs — The
returnkeyword is inside#if FEATURE_WINDOWSINTEROP, causing a compile error in source builds where the flag is not defined. The#if/ternary split leaves a bare conditional expression without areturnstatement.
2 MODERATE issues:
HasFlag()on hot paths —WindowsFileSystem.DirectoryExistsandNativeMethods.LastWriteFileUtcTimeuseHasFlag()which boxes on .NET Framework. The rest of the PR correctly uses bitwise&for these checks. This is easily fixed for consistency.
Positive highlights:
- Excellent
BufferScope<T>utility replacingnew char[]with stackalloc + ArrayPool - Correct dual guard pattern (
#if FEATURE_WINDOWSINTEROP+if (IsWindows)) throughout - Clean COM lifetime management with
ComScope<T>andusingdeclarations - New
CommandLineSource.DebugEnginealternative to WMI is a nice addition - Comprehensive test coverage for new utilities and interop paths
Note
🔒 Integrity filter blocked 2 items
The following items were blocked because they don't meet the GitHub integrity level.
- #13576
pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved". - #13576
pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
To allow these resources, lower min-integrity in your GitHub frontmatter:
tools:
github:
min-integrity: approved # merged | approved | unapproved | noneGenerated by Expert Code Review (on open) for issue #13576 · ● 18.4M
Could you give a brief explanation of why this is helpful please? |
Because you can't mix and match versioned and unversioned attributes without getting warnings. CsWin32 puts the actual version on the attributes it adds. |
|
I intend to follow up with the rest of the interop, didn't want to make this any bigger than it already was, but still wanted enough to stress the skill doc. |
|
Rebased and force pushed to attempt to satisfy the skill validator, but it is still failing. :/ |
|
The skill validator appears to have an issue with it. Put up #13592. @JanKrivanek |
|
Rebased and force pushed to pick up the Skill Validation fix. |
|
Rebased to fix merge conflicts |
|
Rebased again to fix merge conflicts |
Replace hand-written [DllImport] declarations and COM [ComImport]
interfaces with CsWin32 source-generated interop. This enables
Native AOT scenarios where built-in COM marshalling and
System.Runtime.InteropServices marshalling infrastructure do not
exist. The approach follows patterns established in WinForms, WPF,
and the dotnet/sdk repository.
CsWin32 is configured in Microsoft.Build.Framework with
allowMarshaling: false and useSafeHandles: false, producing raw
pointer signatures that are AOT-compatible. Other projects consume
generated types via InternalsVisibleTo.
Key changes:
Infrastructure:
- Add FEATURE_WINDOWSINTEROP compile-time gate in
Directory.BeforeCommon.targets, disabled for source-only builds
- Add CsWin32 (Microsoft.Windows.CsWin32) and PolySharp package
references to Framework.csproj
- Add NativeMethods.txt and NativeMethods.json for CsWin32
configuration
- Add .editorconfig suppression for CS3019 in Windows/ folders
- Add cswin32-interop skill document for agent guidance
New utility types:
- BufferScope<T>: ref struct for stack-allocated buffers with
ArrayPool fallback, replacing manual ArrayPool rent/return and
stackalloc patterns throughout interop code
- TypeInfo<T>: cached RuntimeHelpers.IsReferenceOrContainsReferences
polyfill for net472
- ComScope<T>: COM pointer lifetime management (using-disposable)
- ComClassFactory: AOT-compatible COM activation without
Activator.CreateInstance
- IID: generic IID lookup via IComIID interface
- VARIANT, BSTR, HRESULT, FILETIME extensions: CsWin32 partial
type augmentations for safe usage patterns
NativeMethods.cs cleanup:
- Delete ~130 hand-written constants, enums, structs, and
[DllImport] declarations replaced by CsWin32 typed equivalents
- Replace MemoryStatus class with MEMORYSTATUSEX struct via
TryGetMemoryStatus
- Replace GetCurrentDirectory/GetFullPath/GetShortPathName/
GetLongPathName with BufferScope-based implementations
- Replace StreamHandleType enum with bool useStandardError parameter
- Delete DirectoryExists/FileExists/FileOrDirectoryExists wrappers;
callers use PInvoke.GetFileAttributes directly
- Delete HResultSucceeded/HResultFailed; callers use HRESULT.Failed
- Add [UnsupportedOSPlatformGuard("windows")] to IsUnixLike
- Version all [SupportedOSPlatform] attributes to "windows6.1"
COM interop modernization:
- Replace [ComImport] WMI interfaces (IWbemLocator, IWbemServices,
IEnumWbemClassObject, IWbemClassObject) with struct-based COM
using delegate* unmanaged[Stdcall] vtables
- Add IDebugClient4-based command line retrieval as alternative to
WMI (CommandLineSource.DebugEngine)
- Move IFixedTypeInfo from NativeMethods.cs to its own file in Tasks
Platform guard pattern:
- Apply dual-guard pattern: #if FEATURE_WINDOWSINTEROP wraps runtime
IsWindows checks, eliminating dead code in source builds
- Use IsUnixLike (positive guard) instead of !IsWindows for CA1416
platform compatibility analysis
- Files excluded at project level via <Compile Remove> when
FeatureWindowsInterop is off (WindowsFileSystem, Windows/ folder,
WMI structs)
Behavioral changes:
- Unzip.cs: !IsWindows → IsUnixLike for correct CA1416 analysis
- ProcessExtensions: ArrayPool<T> replaced with BufferScope<T>
- AssemblyInformation/GlobalAssemblyCache: remove dead non-Windows
code paths in net472-only (#if !FEATURE_ASSEMBLYLOADCONTEXT)
blocks, which only run on Windows
Some related changes are also in dotnet#13426.
|
#13610 broke this. I'm updating with both options (WMI, debugger) behind opt-ins. |
…OURCE env var (default off)
The build issue was fixed.
|
/ba-g #13703 is the only failure. |
|
|
Replace hand-written [DllImport] declarations and COM [ComImport] interfaces with CsWin32 source-generated interop. This enables Native AOT scenarios where built-in COM marshalling and System.Runtime.InteropServices marshalling infrastructure do not exist. The approach follows patterns established in WinForms, WPF, and the dotnet/sdk repository.
CsWin32 is configured in Microsoft.Build.Framework with allowMarshaling: false and useSafeHandles: false, producing raw pointer signatures that are AOT-compatible. Other projects consume generated types via InternalsVisibleTo.
Key changes:
Infrastructure:
New utility types:
NativeMethods.cs cleanup:
COM interop modernization:
Platform guard pattern:
Behavioral changes:
Some related changes are also in #13426.