You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Migrate Windows interop to CsWin32 source-generated P/Invoke (#13576)
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 #13426.
description: 'Guides struct-based COM interop in MSBuild using CsWin32 patterns. Consult when working with ComScope<T>, ComClassFactory, IComIID, IID.Get<T>(), delegate* unmanaged vtables, CoCreateInstance, or manually defining COM interfaces not in Win32 metadata (e.g. WMI IWbemLocator, IWbemServices).'
4
+
argument-hint: 'Describe the COM interface or activation pattern you are working with.'
5
+
---
6
+
7
+
# CsWin32 COM Interop Guide
8
+
9
+
Struct-based COM interop using CsWin32 patterns — AOT-compatible, no `[ComImport]` or built-in marshalling.
10
+
11
+
## Workflow
12
+
13
+
1.**Determine if the interface is in Win32 metadata.** If yes, add the name to `src/Framework/NativeMethods.txt` — CsWin32 generates it. If no (e.g. WMI), define a manual struct (see below).
14
+
2.**Create a `ComScope<T>`** for lifetime management: `using ComScope<T> scope = new();`
15
+
3.**Activate the COM object** via `ComClassFactory.TryCreate(CLSID, ...)` or `PInvoke.CoCreateInstance` with `IID.Get<T>()`.
16
+
4.**Call methods** via `scope.Pointer->Method(...)`. Pass `ComScope<T>` directly as `T**` output parameters.
17
+
5.**Guard with `#if FEATURE_WINDOWSINTEROP`** (or `&& NET` for manual structs needing `delegate* unmanaged`).
18
+
19
+
## COM Interfaces in Win32 Metadata
20
+
21
+
Add the interface name to `src/Framework/NativeMethods.txt` → CsWin32 generates it → use `ComScope<T>`:
22
+
23
+
```csharp
24
+
#ifFEATURE_WINDOWSINTEROP
25
+
usingComScope<IRunningObjectTable> rot=new();
26
+
HRESULThr=PInvoke.GetRunningObjectTable(0, rot);
27
+
if (hr.Failed) return;
28
+
rot.Pointer->SomeMethod(...);
29
+
#endif
30
+
```
31
+
32
+
## Manual COM Structs (Not in Metadata)
33
+
34
+
For interfaces not in Win32 metadata (e.g. WMI), define struct-based implementations in their own files, excluded via `<Compile Remove>` in source builds. Guard with `#if FEATURE_WINDOWSINTEROP && NET` (needs `delegate* unmanaged`).
CsWin32 COM structs trigger CS3016 under `[assembly: CLSCompliant(true)]`. Handled via `[CLSCompliant(false)]` partial declarations in `GeneratedInteropClsCompliance.cs`. CS3019 warnings suppressed in `.editorconfig` for `{**/Windows/**/*.cs}` — do not add per-file suppressions. See https://github.com/dotnet/roslyn/issues/68526.
description: 'Guides CsWin32 P/Invoke interop in MSBuild. Consult when working with the PInvoke class, Windows.Win32 namespaces, FEATURE_WINDOWSINTEROP, HANDLE/HMODULE/HRESULT types, BufferScope<T>, replacing [DllImport] with CsWin32, or conditioning Windows-only code for source builds.'
4
+
argument-hint: 'Describe the Windows API or interop code you are migrating or adding.'
5
+
---
6
+
7
+
# CsWin32 Interop Guide
8
+
9
+
[CsWin32](https://github.com/microsoft/CsWin32) replaces `[DllImport]` with source-generated `PInvoke.*` calls. `FEATURE_WINDOWSINTEROP` is the compile-time gate; source builds disable it.
10
+
11
+
## Rules
12
+
13
+
1.**Replace `[DllImport]` with `PInvoke.*`**. Delete old declarations and hand-written structs/enums/constants.
14
+
2.**Gate with `#if FEATURE_WINDOWSINTEROP`**, add runtime `IsWindows` check inside. Both required.
**WRONG**: `if (IsWindows) { #if FEATURE_WINDOWSINTEROP ... #endif }` — dead code in source builds.
32
+
33
+
Windows-only files are excluded via `<Compile Remove>` instead — no `#if` inside needed.
34
+
35
+
## Infrastructure
36
+
37
+
**Define**: `src/Directory.BeforeCommon.targets` sets `FEATURE_WINDOWSINTEROP` + `$(FeatureWindowsInterop)` when `DotNetBuildSourceOnly != true`. Use `$(FeatureWindowsInterop)` in `.csproj` for `<Compile Remove>`/`<Compile Include>`.
38
+
39
+
**CsWin32 config**: `src/Framework/NativeMethods.txt` (API list) + `NativeMethods.json` (`allowMarshaling: false`, `useSafeHandles: false`). Lives in Framework; other projects consume via `InternalsVisibleTo`. Do not add CsWin32 to other projects.
40
+
41
+
**Guard selection**:
42
+
43
+
| Guard | When | Runtime check? |
44
+
|-------|------|----------------|
45
+
|`#if FEATURE_WINDOWSINTEROP`| Multi-TFM Windows calls | Yes |
`BufferScope<T>` (`src/Framework/Utilities/BufferScope.cs`) — stackalloc initial buffer with `ArrayPool<T>` fallback. Lives in Framework, available to all projects via `InternalsVisibleTo`.
-`SafeFileHandle`: `new SafeFileHandle((IntPtr)h.Value, true)`, pass with `(HANDLE)handle.DangerousGetHandle()`
93
+
- Nullable structs: `(SECURITY_ATTRIBUTES?)null`
94
+
- Enum flags: use bitwise `&` — `HasFlag()` boxes on .NET Framework
95
+
- Anonymous unions: `systemInfo.Anonymous.Anonymous.wProcessorArchitecture` — check generated source in `obj/`
96
+
97
+
### Source-Build Verification (REQUIRED before pushing)
98
+
99
+
Source builds (`DotNetBuildSourceOnly=true`) disable `FEATURE_WINDOWSINTEROP`. CI treats **all warnings as errors**. Run both builds before every push:
100
+
101
+
```shell
102
+
# Normal build
103
+
dotnet msbuild MSBuild.Dev.slnf -v:q
104
+
105
+
# Source-build — catches unused usings/members/docs from #if guards
0 commit comments