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
An array of an int-backed enum is no longer pinned for a direct managed-to-unmanaged call. .NET 11 passes a temporary buffer instead of the managed array storage, so writes that were visible to managed code on .NET 10 are lost when the parameter has no explicit Out attribute.
Reproduction steps
Create an empty directory outside the runtime repository and save the following as Repro.csproj and Program.cs. No external native library or NuGet package is required; the native-facing entry point is provided through UnmanagedCallersOnly or Windows built-in COM interop.
// Licensed to the .NET Foundation under one or more agreements.// The .NET Foundation licenses this file to you under the MIT license.usingSystem;usingSystem.Runtime.CompilerServices;usingSystem.Runtime.InteropServices;internalstaticunsafeclassProgram{privateenumValue:int{Before=1,After=42}[UnmanagedFunctionPointer(CallingConvention.Cdecl)]privatedelegateintReplaceFirst(Value[]values);privatestaticintMain(){Console.WriteLine(RuntimeInformation.FrameworkDescription);varreplace=Marshal.GetDelegateForFunctionPointer<ReplaceFirst>((nint)(delegate* unmanaged[Cdecl]<int*,int>)&WriteFirst);Value[]values=[Value.Before];intwritten=replace(values);Console.WriteLine($"Native side wrote: {written}; managed element: {(int)values[0]} (expected 42)");boolpassed=written==42&&values[0]==Value.After;Console.WriteLine(passed?"PASS":"BUG: enum array lost implicit copyback.");returnpassed?0:1;}[UnmanagedCallersOnly(CallConvs=[typeof(CallConvCdecl)])]privatestaticintWriteFirst(int*values){values[0]=42;returnvalues[0];}}
Build once with dotnet build Repro.csproj. Run the generated apphost against the two runtimes:
These commands assume the apphost resolves to a .NET installation containing the tested .NET 10 and .NET 11 runtimes, with no newer major version. If needed, set DOTNET_ROOT to that installation. Check the framework version printed by the program rather than assuming which runtime was selected. Remove the temporary roll-forward environment variables after the comparison.
Expected behavior
Preserve the established pinning behavior for an enum array whose native representation matches its underlying integral storage. In this direct-call repro, writing 42 through the native pointer should update the managed element.
.NET 11.0.0-rc.1.26420.103
Native side wrote: 42; managed element: 1 (expected 42)
BUG: enum array lost implicit copyback.
Regression
Confirmed by running the same net10.0-targeted program on .NET 10.0.5 and .NET 11.0.0-rc.1.26420.103.
The exact introducing commit has not been established by a runtime build-and-bisect. The implementation observations below are based on source inspection.
Configuration
Windows x64, CoreCLR, built-in runtime marshalling.
Passing runtime: .NET 10.0.5.
Failing runtime: .NET 11.0.0-rc.1.26420.103.
Compiled with SDK 11.0.100-rc.1.26420.103, targeting net10.0.
The repository runtime was not rebuilt for this comparison. Linux, ARM64, x86, Mono, NativeAOT, and source-generated marshalling were not tested.
Implementation observations
A separate address-comparison probe pinned the managed enum array with fixed and compared its address with the pointer observed by an UnmanagedCallersOnly callee. They were equal on .NET 10.0.5 and different on .NET 11.0.0-rc.1.26420.103, confirming lost pinning rather than only a copy-back assertion.
The new CanMarshalViaPinning predicate relies on the managed element TypeHandle's IsBlittable result. Enum arrays need recognition of their matching integral representation. The adjacent pinning implementation explicitly documents preserving implicit in/out behavior for compatibility.
This report is about direct built-in interop pinning, not a claim that arrays default to In/Out in every interop scenario. Explicit [In, Out] expresses writable-array intent, but does not replace the compatibility requirement to preserve pinning where it was previously supported. Interop best practices document pinning of the relevant blittable argument shapes.
Related but distinct: #134174 and #134284 concern pointer/function-pointer arrays and pointer-sized copying. The current pointer fix does not add enum handling. This issue is also separate from Unicode char-array pinning because that case requires character-set-sensitive eligibility.
Note
This report was prepared with GitHub Copilot assistance; the included runtime outputs were reproduced locally.
Description
An array of an int-backed enum is no longer pinned for a direct managed-to-unmanaged call. .NET 11 passes a temporary buffer instead of the managed array storage, so writes that were visible to managed code on .NET 10 are lost when the parameter has no explicit Out attribute.
Reproduction steps
Create an empty directory outside the runtime repository and save the following as
Repro.csprojandProgram.cs. No external native library or NuGet package is required; the native-facing entry point is provided throughUnmanagedCallersOnlyor Windows built-in COM interop.Repro.csproj
Program.cs
Build once with
dotnet build Repro.csproj. Run the generated apphost against the two runtimes:These commands assume the apphost resolves to a .NET installation containing the tested .NET 10 and .NET 11 runtimes, with no newer major version. If needed, set
DOTNET_ROOTto that installation. Check the framework version printed by the program rather than assuming which runtime was selected. Remove the temporary roll-forward environment variables after the comparison.Expected behavior
Preserve the established pinning behavior for an enum array whose native representation matches its underlying integral storage. In this direct-call repro, writing 42 through the native pointer should update the managed element.
.NET 10 output:
Actual behavior
.NET 11 output from the same compiled program:
Regression
Confirmed by running the same
net10.0-targeted program on .NET 10.0.5 and .NET 11.0.0-rc.1.26420.103.The exact introducing commit has not been established by a runtime build-and-bisect. The implementation observations below are based on source inspection.
Configuration
.NET 10.0.5..NET 11.0.0-rc.1.26420.103.11.0.100-rc.1.26420.103, targetingnet10.0.Implementation observations
A separate address-comparison probe pinned the managed enum array with fixed and compared its address with the pointer observed by an UnmanagedCallersOnly callee. They were equal on .NET 10.0.5 and different on .NET 11.0.0-rc.1.26420.103, confirming lost pinning rather than only a copy-back assertion.
The new CanMarshalViaPinning predicate relies on the managed element TypeHandle's IsBlittable result. Enum arrays need recognition of their matching integral representation. The adjacent pinning implementation explicitly documents preserving implicit in/out behavior for compatibility.
This report is about direct built-in interop pinning, not a claim that arrays default to In/Out in every interop scenario. Explicit [In, Out] expresses writable-array intent, but does not replace the compatibility requirement to preserve pinning where it was previously supported. Interop best practices document pinning of the relevant blittable argument shapes.
Related but distinct: #134174 and #134284 concern pointer/function-pointer arrays and pointer-sized copying. The current pointer fix does not add enum handling. This issue is also separate from Unicode char-array pinning because that case requires character-set-sensitive eligibility.
Note
This report was prepared with GitHub Copilot assistance; the included runtime outputs were reproduced locally.