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
{{ message }}
Repository navigation
.NET 11 regression: marshalling an AutoDual class array throws "Type passed must be an interface" #134574
Passing a COM-visible AutoDual class array as LPArray to an unmanaged-function-pointer delegate succeeds on .NET 10 but throws ArgumentException on .NET 11. The caller asks the runtime to marshal the class interface; it does not call Marshal.GetComInterfaceForObject with a class type.
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{[UnmanagedFunctionPointer(CallingConvention.Cdecl)]privatedelegateintReadArray([MarshalAs(UnmanagedType.LPArray,SizeConst=1)]AutoDualValue[]values);privatestaticintMain(){Console.WriteLine(RuntimeInformation.FrameworkDescription);if(!OperatingSystem.IsWindows()){Console.WriteLine("Requires Windows built-in COM interop.");return2;}varread=Marshal.GetDelegateForFunctionPointer<ReadArray>((nint)(delegate* unmanaged[Cdecl]<nint*,int>)&HasInterfacePointer);try{intactual=read([newAutoDualValue()]);Console.WriteLine($"Expected non-null native interface pointer: 1; actual: {actual}");Console.WriteLine(actual==1?"PASS":"BUG: missing class interface pointer.");returnactual==1?0:1;}catch(ArgumentExceptionex){Console.WriteLine($"BUG: AutoDual class interface rejected: {ex}");return1;}}[UnmanagedCallersOnly(CallConvs=[typeof(CallConvCdecl)])]privatestaticintHasInterfacePointer(nint*values)=>values[0]!=0?1:0;}[ComVisible(true)][Guid("F1B7A84E-6F7D-42B9-83D7-E7A39DAB1386")][ClassInterface(ClassInterfaceType.AutoDual)]publicclassAutoDualValue{publicintGetValue()=>42;}
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
The native callee receives a non-null interface pointer for the AutoDual object, as on .NET 10. AutoDual is discouraged for versioning reasons but is still a supported ClassInterfaceType.
.NET 11.0.0-rc.1.26420.103
BUG: AutoDual class interface rejected: System.ArgumentException: Type passed must be an interface. (Parameter 'T')
at System.Runtime.InteropServices.Marshal.GetComInterfaceForObject(Object o, Type T, CustomQueryInterfaceMode mode)
at System.StubHelpers.TypedInterfaceArrayElementMarshaler`1.ConvertToUnmanaged(T& managed, Byte* unmanaged)
at System.StubHelpers.IArrayElementMarshaler`2.System.StubHelpers.IArrayMarshaler<T,TSelf>.ConvertContentsToUnmanaged(Array managedArray, Byte* unmanaged, Int32 length)
at Program.Main() in Program.cs:line 26
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
The class-default-interface resolution feeds the class type to TypedInterfaceArrayElementMarshaler, which calls Marshal.GetComInterfaceForObject(managed, typeof(T)). That public API requires T to be an interface and rejects the class type. See class interface selection and typed element conversion.
The repro uses the supported Windows built-in COM interop path; it is not a source-generated COM or NativeAOT example.
Note
This report was prepared with GitHub Copilot assistance; the included runtime outputs were reproduced locally.
Description
Passing a COM-visible AutoDual class array as LPArray to an unmanaged-function-pointer delegate succeeds on .NET 10 but throws ArgumentException on .NET 11. The caller asks the runtime to marshal the class interface; it does not call Marshal.GetComInterfaceForObject with a class type.
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
The native callee receives a non-null interface pointer for the AutoDual object, as on .NET 10. AutoDual is discouraged for versioning reasons but is still a supported ClassInterfaceType.
.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
The class-default-interface resolution feeds the class type to TypedInterfaceArrayElementMarshaler, which calls Marshal.GetComInterfaceForObject(managed, typeof(T)). That public API requires T to be an interface and rejects the class type. See class interface selection and typed element conversion.
The repro uses the supported Windows built-in COM interop path; it is not a source-generated COM or NativeAOT example.
Note
This report was prepared with GitHub Copilot assistance; the included runtime outputs were reproduced locally.