Skip to content

Commit fbf2ce7

Browse files
authored
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.
1 parent 7a2db0b commit fbf2ce7

53 files changed

Lines changed: 3023 additions & 1606 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,3 +457,12 @@ dotnet_diagnostic.xUnit1031.severity = none
457457
# The latter brings incosistency in the codebase and some times in one test case.
458458
# So we are disabling this rule with respect to the above mentioned reasons.
459459
dotnet_diagnostic.xUnit2013.severity = none
460+
461+
# Attributes needed for interop are not CLS-compliant, which produces CS3016 warnings. Disabling here doesn't
462+
# actually work, but when we apply the [CLSCompliant(false)] attribute to the partial declarations of the generated
463+
# types it then fires CS3019 warnings about it not making sense on internal types. We disable these warnings for
464+
# anything in the CsWin32 subfolders. Other options are to disable CS3016 entirely in the project, but that would
465+
# suppress the warning for all code, not just the interop code. Hopefully https://github.com/dotnet/roslyn/issues/68526
466+
# is addressed so we can remove all of this complication.
467+
[{**/Windows/**/*.cs}]
468+
dotnet_diagnostic.CS3019.severity = none
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
---
2+
name: cswin32-com
3+
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+
#if FEATURE_WINDOWSINTEROP
25+
using ComScope<IRunningObjectTable> rot = new();
26+
HRESULT hr = 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`).
35+
36+
```csharp
37+
[SupportedOSPlatform("windows6.1")]
38+
internal unsafe struct IWbemLocator : IComIID
39+
{
40+
public static Guid Guid { get; } = new(0xDC12A687, ...);
41+
42+
// .NET 7+ static abstract IComIID implementation
43+
static ref readonly Guid IComIID.Guid
44+
{
45+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
46+
get
47+
{
48+
ReadOnlySpan<byte> data = [ /* 16 GUID bytes */ ];
49+
return ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(data));
50+
}
51+
}
52+
53+
private readonly void** _lpVtbl;
54+
55+
// IUnknown (vtable 0-2) + interface methods at correct indices
56+
public HRESULT ConnectServer(char* strNetworkResource, ...) {
57+
fixed (IWbemLocator* pThis = &this)
58+
return ((delegate* unmanaged[Stdcall]<IWbemLocator*, char*, ..., HRESULT>)_lpVtbl[3])(pThis, ...);
59+
}
60+
61+
public static Guid CLSID { get; } = new(0x4590F811, ...);
62+
}
63+
```
64+
65+
**Requirements:**
66+
- `delegate* unmanaged[Stdcall]` — needs .NET 5+
67+
- Exact vtable indices — unused slots can be omitted as long as used method indices are correct
68+
- Dual `IComIID` — static abstract on .NET 7+, instance-based on net472 (polyfill in `src/Framework/Framework/`)
69+
- `char*` with `fixed` for BSTR string parameters
70+
- CS0592 prevents `[SupportedOSPlatform]` on structs — put on individual methods instead
71+
72+
## Activation
73+
74+
```csharp
75+
// Via ComClassFactory (AOT-compatible)
76+
if (ComClassFactory.TryCreate(IWbemLocator.CLSID, out var factory, out HRESULT hr))
77+
using ComScope<IWbemLocator> instance = factory.TryCreateInstance<IWbemLocator>(out hr);
78+
79+
// Via CoCreateInstance — use IID.Get<T>() for the IID
80+
Guid clsid = IWbemLocator.CLSID;
81+
using ComScope<IWbemLocator> locator = new();
82+
hr = PInvoke.CoCreateInstance(&clsid, null, CLSCTX.CLSCTX_INPROC_SERVER, IID.Get<IWbemLocator>(), locator);
83+
```
84+
85+
**Key points:**
86+
- Use `IID.Get<T>()` — do not take `&localGuid`
87+
- Initialize `ComScope<T>` with `new()`. It implicitly converts to `T**` / `void**` output parameters
88+
89+
## Lifetime & Access
90+
91+
- `ComScope<T>` is a `ref struct` — use with `using`. Calls `Release()` on dispose.
92+
- Access methods via `scope.Pointer->Method(...)`.
93+
- Pass `ComScope<T>` directly as `T**` or `void**` output parameter (implicit conversion).
94+
95+
## File Organization
96+
97+
| Location | Contents |
98+
|----------|----------|
99+
| `src/Framework/Windows/Win32/System/Com/` | `ComScope.cs`, `ComClassFactory.cs` |
100+
| `src/Framework/Windows/Win32/IID.cs` | Generic IID lookup |
101+
| `src/Framework/Utilities/Wmi/` | Manual WMI structs (.NET-only) |
102+
| `src/Framework/Framework/` | net472 `IComIID` polyfill |
103+
104+
## CS3016 CLS Compliance
105+
106+
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.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
name: cswin32-interop
3+
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.
15+
3. **Use CsWin32 types directly** (`HANDLE`, `HMODULE`, `HRESULT.S_OK`, `FILE_FLAGS_AND_ATTRIBUTES`, etc.).
16+
4. **Call `PInvoke.*` directly** — no wrappers. Types flow via `InternalsVisibleTo`.
17+
5. **Prefer CsWin32 for Windows APIs**. Use `[LibraryImport]` only for non-Windows native calls (e.g. `libc`), guarded with `#if NET`.
18+
19+
### Dual Guard Pattern
20+
21+
```csharp
22+
#if FEATURE_WINDOWSINTEROP
23+
if (IsWindows)
24+
{
25+
PInvoke.GetFileAttributesEx(fullPath, out WIN32_FILE_ATTRIBUTE_DATA data);
26+
}
27+
#endif
28+
// Cross-platform fallback
29+
```
30+
31+
**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 |
46+
| `#if FEATURE_WINDOWSINTEROP && NET` | `delegate* unmanaged`, `ComScope<T>` | Yes |
47+
| `#if FEATURE_WINDOWSINTEROP && !NETSTANDARD` | CsWin32 types without `static abstract` (net472 + net10) | Yes |
48+
| `#if !NET` / `#if FEATURE_MSCOREE` | net472-only = inherently Windows | No |
49+
50+
**Namespace imports** must be inside `#if FEATURE_WINDOWSINTEROP`. WDK APIs use `Windows.Wdk` namespace.
51+
52+
**Files**: `src/Framework/Windows/` (CsWin32 partials), `src/Shared/Win32/` (COM helpers), `src/Framework/Utilities/Wmi/` (.NET-only COM structs), `src/Framework/Framework/` (net472 polyfills).
53+
54+
### Constant Replacements
55+
56+
`NativeMethodsShared.S_OK``HRESULT.S_OK`, `InvalidHandle``HANDLE.INVALID_HANDLE_VALUE`, `FILE_ATTRIBUTE_DIRECTORY``FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_DIRECTORY`, `STD_OUTPUT_HANDLE``STD_HANDLE.STD_OUTPUT_HANDLE`, `GENERIC_READ``FILE_ACCESS_RIGHTS.FILE_GENERIC_READ`. Pattern: `CsWin32EnumType.ORIGINAL_NAME` — check generated types in `obj/`.
57+
58+
## BufferScope<T>
59+
60+
`BufferScope<T>` (`src/Framework/Utilities/BufferScope.cs`) — stackalloc initial buffer with `ArrayPool<T>` fallback. Lives in Framework, available to all projects via `InternalsVisibleTo`.
61+
62+
```csharp
63+
using BufferScope<char> buffer = new(stackalloc char[(int)PInvoke.MAX_PATH]);
64+
int length = (int)PInvoke.GetShortPathName(path, buffer.AsSpan());
65+
if (length > buffer.Length)
66+
{
67+
buffer.EnsureCapacity(length);
68+
length = (int)PInvoke.GetShortPathName(path, buffer.AsSpan());
69+
}
70+
if (length > 0) path = buffer.Slice(0, length).ToString();
71+
```
72+
73+
- `ref struct` — always use with `using`. Never stack-allocate more than 1024 bytes.
74+
- Check CsWin32 convenience overloads (e.g. `GetShortPathName(string, Span<char>)`) before writing `fixed` blocks.
75+
76+
## Gotchas
77+
78+
### CA1416 Platform Compatibility
79+
80+
No blanket `NoWarn` — handle semantically:
81+
- `if (IsWindows)` satisfies `[SupportedOSPlatform]` — no pragma needed
82+
- `if (IsUnixLike)` satisfies `[UnsupportedOSPlatform("windows")]`
83+
- **Never use `!IsWindows`** — use `else if (IsUnixLike)`. See `documentation/specs/CA1416-analyzer-analysis.md`
84+
- Use versioned `[SupportedOSPlatform("windows6.1")]` on methods calling CsWin32 APIs
85+
- `#pragma warning disable CA1416` only for **static local functions** (analyzer limitation)
86+
- CS0592 prevents `[SupportedOSPlatform]` on `partial struct` — put on individual members instead
87+
88+
### Type Conversions
89+
90+
- `HANDLE ↔ IntPtr`: `(HANDLE)intPtr` / `(IntPtr)h.Value`. Sentinels: `HANDLE.Null`, `HANDLE.INVALID_HANDLE_VALUE`
91+
- `FILETIME → long`: `data.ftLastWriteTime.ToLong()` — CsWin32 uses `ComTypes.FILETIME` (int fields), not `Win32.Foundation.FILETIME`
92+
- `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
106+
dotnet msbuild MSBuild.SourceBuild.slnf /p:DotNetBuildSourceOnly=true -v:q
107+
```
108+
109+
**Everything** only referenced inside `#if FEATURE_WINDOWSINTEROP` must also be guarded:
110+
- **IDE0005**: `using` directives — most common failure
111+
- **IDE0051/IDE0052**: Private members (methods, fields)
112+
- **CS1587**: XML doc comments (move inside `#if`, not before)

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ Instructions for GitHub Copilot and other AI coding agents working with the MSBu
1515

1616
### Technology Stack
1717
- .NET 10.0 and .NET Framework 4.7.2
18-
- C# 13 features (especially collection expressions)
18+
- C# 14 features (especially collection expressions)
1919
- xUnit with Shouldly for testing
2020
- Multi-platform support (Windows, Linux, macOS)
2121

2222
## General
2323

2424
* Performance is the top priority - minimize allocations, avoid LINQ in hot paths, use efficient algorithms.
25-
* Always use the latest C# features, currently C# 13, especially collection expressions (`[]` over `new Type[]`).
25+
* Always use the latest C# features, currently C# 14, especially collection expressions (`[]` over `new Type[]`).
2626
* Match the style of surrounding code when making edits, but modernize aggressively for substantial changes.
2727

2828
## Code Review Instructions

eng/dependabot/Directory.Packages.props

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@
6363
<PackageVersion Update="Verify.XunitV3" Condition="'$(VerifyXunitV3Version)' != ''" Version="$(VerifyXunitV3Version)" />
6464
</ItemGroup>
6565

66+
<!-- CsWin32 source generator for Windows interop (dev-time only) -->
67+
<ItemGroup>
68+
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.183" />
69+
<PackageVersion Update="Microsoft.Windows.CsWin32" Condition="'$(MicrosoftWindowsCsWin32Version)' != ''" Version="$(MicrosoftWindowsCsWin32Version)" />
70+
71+
<PackageVersion Include="PolySharp" Version="1.15.0" />
72+
<PackageVersion Update="PolySharp" Condition="'$(PolySharpVersion)' != ''" Version="$(PolySharpVersion)" />
73+
</ItemGroup>
74+
6675
<!-- Roslyn analyzer authoring packages (used by ThreadSafeTaskAnalyzer) -->
6776
<ItemGroup>
6877
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />

src/Build.UnitTests/BackEnd/TargetUpToDateChecker_Tests.cs

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
using Microsoft.Build.Shared;
1616
using Microsoft.Win32.SafeHandles;
1717
using Xunit;
18+
#if FEATURE_WINDOWSINTEROP
19+
using Windows.Win32.Storage.FileSystem;
20+
#endif
1821

1922
#nullable disable
2023

@@ -960,7 +963,7 @@ private void IsAnyOutOfDateTestHelper(
960963

961964
[Fact(Skip = "Creating a symlink on Windows requires elevation.")]
962965
[SkipOnPlatform(TestPlatforms.AnyUnix, "Windows-specific test")]
963-
[SupportedOSPlatform("windows")]
966+
[SupportedOSPlatform("windows6.1")]
964967
public void NewSymlinkOldDestinationIsUpToDate()
965968
{
966969
SimpleSymlinkInputCheck(symlinkWriteTime: New,
@@ -971,7 +974,7 @@ public void NewSymlinkOldDestinationIsUpToDate()
971974

972975
[Fact(Skip = "Creating a symlink on Windows requires elevation.")]
973976
[SkipOnPlatform(TestPlatforms.AnyUnix, "Windows-specific test")]
974-
[SupportedOSPlatform("windows")]
977+
[SupportedOSPlatform("windows6.1")]
975978
public void OldSymlinkOldDestinationIsUpToDate()
976979
{
977980
SimpleSymlinkInputCheck(symlinkWriteTime: Old,
@@ -982,7 +985,7 @@ public void OldSymlinkOldDestinationIsUpToDate()
982985

983986
[Fact(Skip = "Creating a symlink on Windows requires elevation.")]
984987
[SkipOnPlatform(TestPlatforms.AnyUnix, "Windows-specific test")]
985-
[SupportedOSPlatform("windows")]
988+
[SupportedOSPlatform("windows6.1")]
986989
public void OldSymlinkNewDestinationIsNotUpToDate()
987990
{
988991
SimpleSymlinkInputCheck(symlinkWriteTime: Old,
@@ -993,7 +996,7 @@ public void OldSymlinkNewDestinationIsNotUpToDate()
993996

994997
[Fact(Skip = "Creating a symlink on Windows requires elevation.")]
995998
[SkipOnPlatform(TestPlatforms.AnyUnix, "Windows-specific test")]
996-
[SupportedOSPlatform("windows")]
999+
[SupportedOSPlatform("windows6.1")]
9971000
public void NewSymlinkNewDestinationIsNotUpToDate()
9981001
{
9991002
SimpleSymlinkInputCheck(symlinkWriteTime: Middle,
@@ -1004,15 +1007,26 @@ public void NewSymlinkNewDestinationIsNotUpToDate()
10041007

10051008
[DllImport("kernel32.dll")]
10061009
[return: MarshalAs(UnmanagedType.Bool)]
1007-
[SupportedOSPlatform("windows")]
1010+
[SupportedOSPlatform("windows6.1")]
10081011
private static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, UInt32 dwFlags);
10091012

10101013
[DllImport("kernel32.dll", SetLastError = true)]
1011-
[SupportedOSPlatform("windows")]
1014+
[SupportedOSPlatform("windows6.1")]
10121015
private static extern bool SetFileTime(SafeFileHandle hFile, ref long creationTime,
10131016
ref long lastAccessTime, ref long lastWriteTime);
10141017

1015-
[SupportedOSPlatform("windows")]
1018+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "CreateFileW")]
1019+
[SupportedOSPlatform("windows6.1")]
1020+
private static extern SafeFileHandle CreateFileForSymlink(
1021+
string lpFileName,
1022+
uint dwDesiredAccess,
1023+
uint dwShareMode,
1024+
IntPtr lpSecurityAttributes,
1025+
uint dwCreationDisposition,
1026+
uint dwFlagsAndAttributes,
1027+
IntPtr hTemplateFile);
1028+
1029+
[SupportedOSPlatform("windows6.1")]
10161030
private void SimpleSymlinkInputCheck(DateTime symlinkWriteTime, DateTime targetWriteTime,
10171031
DateTime outputWriteTime, bool expectedOutOfDate)
10181032
{
@@ -1038,11 +1052,13 @@ private void SimpleSymlinkInputCheck(DateTime symlinkWriteTime, DateTime targetW
10381052

10391053
// File.SetLastWriteTime on the symlink sets the target write time,
10401054
// so set the symlink's write time the hard way
1041-
using (SafeFileHandle handle =
1042-
NativeMethodsShared.CreateFile(
1043-
inputSymlink, NativeMethodsShared.GENERIC_READ | 0x100 /* FILE_WRITE_ATTRIBUTES */,
1044-
NativeMethodsShared.FILE_SHARE_READ, IntPtr.Zero, NativeMethodsShared.OPEN_EXISTING,
1045-
NativeMethodsShared.FILE_ATTRIBUTE_NORMAL | NativeMethodsShared.FILE_FLAG_OPEN_REPARSE_POINT,
1055+
using (SafeFileHandle handle = CreateFileForSymlink(
1056+
inputSymlink,
1057+
(uint)FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | 0x100 /* FILE_WRITE_ATTRIBUTES */,
1058+
(uint)FILE_SHARE_MODE.FILE_SHARE_READ,
1059+
IntPtr.Zero,
1060+
(uint)FILE_CREATION_DISPOSITION.OPEN_EXISTING,
1061+
(uint)(FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL | FILE_FLAGS_AND_ATTRIBUTES.FILE_FLAG_OPEN_REPARSE_POINT),
10461062
IntPtr.Zero))
10471063
{
10481064
if (handle.IsInvalid)

src/Build.UnitTests/ConsoleLogger_Tests.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
using Shouldly;
1919
using Xunit;
2020
using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem;
21+
#if FEATURE_WINDOWSINTEROP
22+
using Windows.Win32;
23+
using Windows.Win32.Foundation;
24+
using Windows.Win32.Storage.FileSystem;
25+
using Windows.Win32.System.Console;
26+
#endif
2127

2228
#nullable disable
2329

@@ -1959,18 +1965,16 @@ public void TestPrintTargetNamePerMessage()
19591965
/// Check to see what kind of device we are outputting the log to, is it a character device, a file, or something else
19601966
/// this can be used by loggers to modify their outputs based on the device they are writing to
19611967
/// </summary>
1962-
[SupportedOSPlatform("windows")]
1968+
[SupportedOSPlatform("windows6.1")]
19631969
internal bool IsRunningWithCharacterFileType()
19641970
{
19651971
// Get the std out handle
1966-
IntPtr stdHandle = NativeMethodsShared.GetStdHandle(NativeMethodsShared.STD_OUTPUT_HANDLE);
1972+
HANDLE stdHandle = PInvoke.GetStdHandle(STD_HANDLE.STD_OUTPUT_HANDLE);
19671973

1968-
if (stdHandle != Microsoft.Build.BackEnd.NativeMethods.InvalidHandle)
1974+
if (stdHandle != HANDLE.INVALID_HANDLE_VALUE)
19691975
{
1970-
uint fileType = NativeMethodsShared.GetFileType(stdHandle);
1971-
19721976
// The std out is a char type(LPT or Console)
1973-
return fileType == NativeMethodsShared.FILE_TYPE_CHAR;
1977+
return PInvoke.GetFileType(stdHandle) == FILE_TYPE.FILE_TYPE_CHAR;
19741978
}
19751979
else
19761980
{

0 commit comments

Comments
 (0)