diff --git a/docs/design/libraries/LibraryImportGenerator/Compatibility.md b/docs/design/libraries/LibraryImportGenerator/Compatibility.md index cd2e31f429cd07..63d7053ce694b3 100644 --- a/docs/design/libraries/LibraryImportGenerator/Compatibility.md +++ b/docs/design/libraries/LibraryImportGenerator/Compatibility.md @@ -8,6 +8,12 @@ Documentation on compatibility guidance and the current state. The version headi Due to trimming issues with NativeAOT's implementation of `Activator.CreateInstance`, we have decided to change our recommendation of providing a public parameterless constructor for `ref`, `out`, and return scenarios to a requirement. We already required a parameterless constructor of some visibility, so changing to a requirement matches our design principles of taking breaking changes to make interop more understandable and enforce more of our best practices instead of going out of our way to provide backward compatibility at increasing costs. +### `UnmanagedType.Interface` + +Support for `MarshalAs(UnmanagedType.Interface)` is added to the interop source generators. `UnmanagedType.Interface` will marshal a parameter/return value of a type `T` to a COM interface pointer the `ComInterfaceMarshaller` type. It will not support marshalling through the built-in COM interop subsystem. + +The `ComInterfaceMarshaller` type has the following general behavior: An unmanaged pointer is marshalled to a managed object through `GetOrCreateObjectForComInstance` on a shared `StrategyBasedComWrappers` instance. A managed object is marshalled to an unmanaged pointer through that same shared instance with the `GetOrCreateComInterfaceForObject` method and then calling `QueryInterface` on the returned `IUnknown*` to get the pointer for the unmanaged interface with the IID from the managed type as defined by our default interface details strategy (or the IID of `IUnknown` if the managed type has no IID). + ## Version 2 (.NET 7 Release) The focus of version 2 is to support all repos that make up the .NET Product, including ASP.NET Core and Windows Forms, as well as all packages in dotnet/runtime. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/NativeMarshallingAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/NativeMarshallingAttribute.cs index 96bdeeb9c9df34..8e39ad650b7957 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/NativeMarshallingAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/NativeMarshallingAttribute.cs @@ -12,7 +12,7 @@ namespace System.Runtime.InteropServices.Marshalling /// /// /// - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Delegate)] + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Delegate)] public sealed class NativeMarshallingAttribute : Attribute { /// diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs b/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs index 2150eacf9689c2..7149fc40a6f639 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs @@ -38,14 +38,15 @@ public static MarshallingInfoParser Create(StubEnvironment env, IGeneratorDiagno diagnostics, new MethodSignatureElementInfoProvider(env.Compilation, diagnostics, method, useSiteAttributeParsers), useSiteAttributeParsers, - ImmutableArray.Create( + ImmutableArray.Create( new MarshalAsAttributeParser(env.Compilation, diagnostics, defaultInfo), new MarshalUsingAttributeParser(env.Compilation, diagnostics), - new NativeMarshallingAttributeParser(env.Compilation, diagnostics)), + new NativeMarshallingAttributeParser(env.Compilation, diagnostics), + new ComInterfaceMarshallingInfoProvider(env.Compilation)), ImmutableArray.Create( new SafeHandleMarshallingInfoProvider(env.Compilation, method.ContainingType), new ArrayMarshallingInfoProvider(env.Compilation), - new CharMarshallingInfoProvider(defaultInfo), + new CharMarshallingInfoProvider(defaultInfo), new StringMarshallingInfoProvider(env.Compilation, diagnostics, unparsedAttributeData, defaultInfo), new BooleanMarshallingInfoProvider(), new BlittableTypeMarshallingInfoProvider(env.Compilation))); diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ComInterfaceMarshallingInfoProvider.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ComInterfaceMarshallingInfoProvider.cs new file mode 100644 index 00000000000000..2386acc0ef5070 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ComInterfaceMarshallingInfoProvider.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Interop +{ + /// + /// This class supports generating marshalling info for types with the System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute attribute. + /// + public class ComInterfaceMarshallingInfoProvider : IMarshallingInfoAttributeParser + { + private readonly Compilation _compilation; + + public ComInterfaceMarshallingInfoProvider(Compilation compilation) + { + _compilation = compilation; + } + + public bool CanParseAttributeType(INamedTypeSymbol attributeType) => attributeType.ToDisplayString() == TypeNames.GeneratedComInterfaceAttribute; + + public MarshallingInfo? ParseAttribute(AttributeData attributeData, ITypeSymbol type, int indirectionDepth, UseSiteAttributeProvider useSiteAttributes, GetMarshallingInfoCallback marshallingInfoCallback) + { + return CreateComInterfaceMarshallingInfo(_compilation, type); + } + + public static MarshallingInfo CreateComInterfaceMarshallingInfo( + Compilation compilation, + ITypeSymbol interfaceType) + { + INamedTypeSymbol? comInterfaceMarshaller = compilation.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_Marshalling_ComInterfaceMarshaller_Metadata); + if (comInterfaceMarshaller is null) + return new MissingSupportMarshallingInfo(); + + comInterfaceMarshaller = comInterfaceMarshaller.Construct(interfaceType); + + if (ManualTypeMarshallingHelper.HasEntryPointMarshallerAttribute(comInterfaceMarshaller)) + { + if (ManualTypeMarshallingHelper.TryGetValueMarshallersFromEntryType(comInterfaceMarshaller, interfaceType, compilation, out CustomTypeMarshallers? marshallers)) + { + return new NativeMarshallingAttributeInfo( + EntryPointType: ManagedTypeInfo.CreateTypeInfoForTypeSymbol(comInterfaceMarshaller), + Marshallers: marshallers.Value); + } + } + + return new MissingSupportMarshallingInfo(); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshalAsAttributeParser.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshalAsAttributeParser.cs index 4294f11610f205..aa9b25375c5beb 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshalAsAttributeParser.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MarshalAsAttributeParser.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; @@ -98,6 +97,18 @@ UseSiteAttributeData IUseSiteAttributeParser.ParseAttribute(AttributeData attrib } } + // We'll support the UnmanagedType.Interface option, but we'll explicitly + // exclude ComImport types as they will not work as expected + // unless they are migrated to [GeneratedComInterface]. + if (unmanagedType == UnmanagedType.Interface) + { + if (type is INamedTypeSymbol { IsComImport: true }) + { + return new MarshalAsInfo(unmanagedType, _defaultInfo.CharEncoding); + } + return ComInterfaceMarshallingInfoProvider.CreateComInterfaceMarshallingInfo(_compilation, type); + } + if (isArrayType) { if (type is not IArrayTypeSymbol { ElementType: ITypeSymbol elementType }) diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/StringMarshallingInfoProvider.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/StringMarshallingInfoProvider.cs index e8c9aee264d3df..8eeb1b21a95550 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/StringMarshallingInfoProvider.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/StringMarshallingInfoProvider.cs @@ -8,7 +8,6 @@ namespace Microsoft.Interop { - /// /// This class supports generating marshalling info for the type. /// This includes support for the System.Runtime.InteropServices.StringMarshalling enum. diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs index 314b8197699e2b..6abfc523c3092e 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs @@ -34,6 +34,8 @@ public static class TypeNames public const string UnmanagedCallersOnlyAttribute = "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute"; + public const string System_Runtime_InteropServices_ComImportAttribute = "System.Runtime.InteropServices.ComImportAttribute"; + public const string VirtualMethodIndexAttribute = "System.Runtime.InteropServices.Marshalling.VirtualMethodIndexAttribute"; public const string IUnmanagedVirtualMethodTableProvider = "System.Runtime.InteropServices.Marshalling.IUnmanagedVirtualMethodTableProvider"; @@ -140,5 +142,7 @@ public static string MarshalEx(InteropGenerationOptions options) public const string IComExposedClass = "System.Runtime.InteropServices.Marshalling.IComExposedClass"; public const string System_Runtime_InteropServices_Marshalling_SafeHandleMarshaller_Metadata = "System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller`1"; + + public const string System_Runtime_InteropServices_Marshalling_ComInterfaceMarshaller_Metadata = "System.Runtime.InteropServices.Marshalling.ComInterfaceMarshaller`1"; } } diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs index 5f82435829d892..a6cf4e8c8a58a5 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -332,6 +332,17 @@ public sealed partial class ComExposedClassAttribute : System.Attribute, Syst public ComExposedClassAttribute() { } public unsafe System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry* GetComInterfaceEntries(out int count) { throw null; } } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] + [System.CLSCompliantAttribute(false)] + [System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute(typeof(System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute.GenericPlaceholder), System.Runtime.InteropServices.Marshalling.MarshalMode.Default, typeof(System.Runtime.InteropServices.Marshalling.ComInterfaceMarshaller<>))] + public static unsafe class ComInterfaceMarshaller + { + public static void* ConvertToUnmanaged(T? managed) { throw null; } + public static T? ConvertToManaged(void* unmanaged) { throw null; } + } public sealed partial class ComObject : System.Runtime.InteropServices.IDynamicInterfaceCastable, System.Runtime.InteropServices.Marshalling.IUnmanagedVirtualMethodTableProvider { internal ComObject() { } @@ -451,6 +462,17 @@ public StrategyBasedComWrappers() { } protected virtual System.Runtime.InteropServices.Marshalling.IIUnknownStrategy GetOrCreateIUnknownStrategy() { throw null; } protected sealed override void ReleaseObjects(System.Collections.IEnumerable objects) { } } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] + [System.CLSCompliantAttribute(false)] + [System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute(typeof(System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute.GenericPlaceholder), System.Runtime.InteropServices.Marshalling.MarshalMode.Default, typeof(System.Runtime.InteropServices.Marshalling.UniqueComInterfaceMarshaller<>))] + public static unsafe class UniqueComInterfaceMarshaller + { + public static void* ConvertToUnmanaged(T? managed) { throw null; } + public static T? ConvertToManaged(void* unmanaged) { throw null; } + } [System.CLSCompliantAttribute(false)] public readonly partial struct VirtualMethodTableInfo { @@ -688,10 +710,10 @@ public ComSourceInterfacesAttribute(System.Type sourceInterface1, System.Type so public ComSourceInterfacesAttribute(System.Type sourceInterface1, System.Type sourceInterface2, System.Type sourceInterface3, System.Type sourceInterface4) { } public string Value { get { throw null; } } } - [System.Runtime.Versioning.UnsupportedOSPlatform("android")] - [System.Runtime.Versioning.UnsupportedOSPlatform("browser")] - [System.Runtime.Versioning.UnsupportedOSPlatform("ios")] - [System.Runtime.Versioning.UnsupportedOSPlatform("tvos")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] [System.CLSCompliantAttribute(false)] public abstract class ComWrappers { diff --git a/src/libraries/System.Runtime.InteropServices/src/System.Runtime.InteropServices.csproj b/src/libraries/System.Runtime.InteropServices/src/System.Runtime.InteropServices.csproj index 7d91ce185535d1..c92594f7218f97 100644 --- a/src/libraries/System.Runtime.InteropServices/src/System.Runtime.InteropServices.csproj +++ b/src/libraries/System.Runtime.InteropServices/src/System.Runtime.InteropServices.csproj @@ -32,6 +32,8 @@ + + diff --git a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComInterfaceMarshaller.cs b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComInterfaceMarshaller.cs new file mode 100644 index 00000000000000..382540a1039a88 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComInterfaceMarshaller.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.Versioning; + +namespace System.Runtime.InteropServices.Marshalling +{ + /// + /// COM interface marshaller using a StrategyBasedComWrappers instance + /// + /// + /// This marshaller will always pass the flag + /// to . + /// + [UnsupportedOSPlatform("android")] + [UnsupportedOSPlatform("browser")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [CLSCompliant(false)] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder), MarshalMode.Default, typeof(ComInterfaceMarshaller<>))] + public static unsafe class ComInterfaceMarshaller + { + private static readonly Guid? TargetInterfaceIID = StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(T).TypeHandle)?.Iid; + + public static void* ConvertToUnmanaged(T? managed) + { + if (managed == null) + { + return null; + } + if (!ComWrappers.TryGetComInstance(managed, out nint unknown)) + { + unknown = StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateComInterfaceForObject(managed, CreateComInterfaceFlags.None); + } + return CastIUnknownToInterfaceType(unknown); + } + + public static T? ConvertToManaged(void* unmanaged) + { + if (unmanaged == null) + { + return default; + } + return (T)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateObjectForComInstance((nint)unmanaged, CreateObjectFlags.Unwrap); + } + + internal static void* CastIUnknownToInterfaceType(nint unknown) + { + if (TargetInterfaceIID is null) + { + // If the managed type isn't a GeneratedComInterface-attributed type, we'll marshal to an IUnknown*. + return (void*)unknown; + } + Guid iid = TargetInterfaceIID.Value; + if (Marshal.QueryInterface(unknown, ref iid, out nint interfacePointer) != 0) + { + Marshal.Release(unknown); + throw new InvalidCastException($"Unable to cast the provided managed object to a COM interface with ID '{iid:B}'"); + } + Marshal.Release(unknown); + return (void*)interfacePointer; + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/StrategyBasedComWrappers.cs b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/StrategyBasedComWrappers.cs index d8074e17978c48..e5ba351641bfad 100644 --- a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/StrategyBasedComWrappers.cs +++ b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/StrategyBasedComWrappers.cs @@ -9,6 +9,8 @@ namespace System.Runtime.InteropServices.Marshalling [CLSCompliant(false)] public class StrategyBasedComWrappers : ComWrappers { + internal static StrategyBasedComWrappers DefaultMarshallingInstance { get; } = new(); + public static IIUnknownInterfaceDetailsStrategy DefaultIUnknownInterfaceDetailsStrategy { get; } = Marshalling.DefaultIUnknownInterfaceDetailsStrategy.Instance; public static IIUnknownStrategy DefaultIUnknownStrategy { get; } = FreeThreadedStrategy.Instance; diff --git a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/UniqueComInterfaceMarshaller.cs b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/UniqueComInterfaceMarshaller.cs new file mode 100644 index 00000000000000..1fdc5eafee35ba --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/UniqueComInterfaceMarshaller.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.Versioning; + +namespace System.Runtime.InteropServices.Marshalling +{ + /// + /// COM interface marshaller using a StrategyBasedComWrappers instance + /// that will only create unique native object wrappers (RCW). + /// + /// + /// This marshaller will always pass the and flags + /// to . + /// + [UnsupportedOSPlatform("android")] + [UnsupportedOSPlatform("browser")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [CLSCompliant(false)] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder), MarshalMode.Default, typeof(UniqueComInterfaceMarshaller<>))] + public static unsafe class UniqueComInterfaceMarshaller + { + public static void* ConvertToUnmanaged(T? managed) + { + if (managed == null) + { + return null; + } + if (!ComWrappers.TryGetComInstance(managed, out nint unknown)) + { + unknown = StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateComInterfaceForObject(managed, CreateComInterfaceFlags.None); + } + return ComInterfaceMarshaller.CastIUnknownToInterfaceType(unknown); + } + + public static T? ConvertToManaged(void* unmanaged) + { + if (unmanaged == null) + { + return default; + } + return (T)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateObjectForComInstance((nint)unmanaged, CreateObjectFlags.Unwrap | CreateObjectFlags.UniqueInstance); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComClassTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComClassTests.cs index 737a9f9986f4b4..b8dbf345e5eb76 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComClassTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComClassTests.cs @@ -16,6 +16,12 @@ unsafe partial class NativeExportsNE [LibraryImport(NativeExportsNE_Binary, EntryPoint = "get_com_object_data")] public static partial int GetComObjectData(void* obj); + + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "set_com_object_data")] + public static partial void SetComObjectData(IGetAndSetInt obj, int data); + + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "get_com_object_data")] + public static partial int GetComObjectData(IGetAndSetInt obj); } [GeneratedComClass] @@ -62,31 +68,23 @@ public void ComInstanceProvidesInterfaceForIndirectlyImplementedComInterface() } [Fact] - public void CallsToComInterfaceWriteChangesToManagedObject() + public void CallsToComInterfaceWithMarshallerWriteChangesToManagedObject() { ManagedObjectExposedToCom obj = new(); - StrategyBasedComWrappers wrappers = new(); - void* ptr = (void*)wrappers.GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.None); - Assert.NotEqual(0, (nint)ptr); obj.Data = 3; Assert.Equal(3, obj.Data); - NativeExportsNE.SetComObjectData(ptr, 42); + NativeExportsNE.SetComObjectData(obj, 42); Assert.Equal(42, obj.Data); - Marshal.Release((nint)ptr); } [Fact] - public void CallsToComInterfaceReadChangesFromManagedObject() + public void CallsToComInterfaceWithMarshallerReadChangesFromManagedObject() { ManagedObjectExposedToCom obj = new(); - StrategyBasedComWrappers wrappers = new(); - void* ptr = (void*)wrappers.GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.None); - Assert.NotEqual(0, (nint)ptr); obj.Data = 3; Assert.Equal(3, obj.Data); obj.Data = 12; - Assert.Equal(obj.Data, NativeExportsNE.GetComObjectData(ptr)); - Marshal.Release((nint)ptr); + Assert.Equal(obj.Data, NativeExportsNE.GetComObjectData(obj)); } } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IGetAndSetIntTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IGetAndSetIntTests.cs index 322c459ce1db99..ac1c3b5bfdee47 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IGetAndSetIntTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IGetAndSetIntTests.cs @@ -20,6 +20,13 @@ public unsafe partial class IGetAndSetIntTests [LibraryImport(NativeExportsNE.NativeExportsNE_Binary, EntryPoint = "new_get_and_set_int")] public static partial void* NewNativeObject(); + [LibraryImport(NativeExportsNE.NativeExportsNE_Binary, EntryPoint = "new_get_and_set_int")] + internal static partial IGetAndSetInt NewNativeObjectWithMarshaller(); + + [LibraryImport(NativeExportsNE.NativeExportsNE_Binary, EntryPoint = "new_get_and_set_int")] + [return:MarshalUsing(typeof(UniqueComInterfaceMarshaller))] + internal static partial IGetAndSetInt NewNativeObjectWithUniqueMarshaller(); + [Fact] public unsafe void CallRcwFromGeneratedComInterface() { @@ -32,5 +39,27 @@ public unsafe void CallRcwFromGeneratedComInterface() intObj.SetInt(2); Assert.Equal(2, intObj.GetInt()); } + + [Fact] + public unsafe void CallRcwFromGeneratedComInterfaceConstructedByMarshaller() + { + var intObj = NewNativeObjectWithMarshaller(); // new_native_object + + Assert.Equal(0, intObj.GetInt()); + intObj.SetInt(2); + Assert.Equal(2, intObj.GetInt()); + } + + [Fact] + public unsafe void CallRcwFromGeneratedComInterfaceConstructedByUniqueMarshaller() + { + var intObj = NewNativeObjectWithUniqueMarshaller(); // new_native_object + var intObj2 = NewNativeObjectWithUniqueMarshaller(); // new_native_object + Assert.NotSame(intObj, intObj2); + + Assert.Equal(0, intObj.GetInt()); + intObj.SetInt(2); + Assert.Equal(2, intObj.GetInt()); + } } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CodeSnippets.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CodeSnippets.cs index 9a1ab29d002195..38f9f9f8e262df 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CodeSnippets.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CodeSnippets.cs @@ -53,7 +53,7 @@ private string UnmanagedCallConv(Type[]? CallConvs = null) public string SpecifiedMethodIndexNoExplicitParameters => $$""" using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; - + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} {{GeneratedComInterface}} partial interface INativeAPI @@ -65,17 +65,17 @@ partial interface INativeAPI """; public string SpecifiedMethodIndexNoExplicitParametersNoImplicitThis => $$""" - + using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; - + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} {{GeneratedComInterface}} partial interface INativeAPI { {{VirtualMethodIndex(0, ImplicitThisParameter: false)}} void Method(); - + } {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} """; @@ -84,29 +84,29 @@ partial interface INativeAPI using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; - + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} {{GeneratedComInterface}} partial interface INativeAPI { - + {{UnmanagedCallConv(CallConvs: new[] { typeof(CallConvCdecl) })}} {{VirtualMethodIndex(0)}} void Method(); {{UnmanagedCallConv(CallConvs: new[] { typeof(CallConvCdecl), typeof(CallConvMemberFunction) })}} {{VirtualMethodIndex(1)}} void Method1(); - + [SuppressGCTransition] {{UnmanagedCallConv(CallConvs: new[] { typeof(CallConvCdecl), typeof(CallConvMemberFunction) })}} {{VirtualMethodIndex(2)}} void Method2(); - + [SuppressGCTransition] {{UnmanagedCallConv()}} {{VirtualMethodIndex(3)}} void Method3(); - + [SuppressGCTransition] {{VirtualMethodIndex(4)}} void Method4(); @@ -118,9 +118,9 @@ public string BasicParametersAndModifiers(string typeName, string methodModifier using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; {{preDeclaration}} - + [assembly:DisableRuntimeMarshalling] - + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} {{GeneratedComInterface}} partial interface INativeAPI @@ -136,9 +136,9 @@ public string BasicParametersAndModifiersManagedToUnmanaged(string typeName, str using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; {{preDeclaration}} - + [assembly:DisableRuntimeMarshalling] - + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} {{GeneratedComInterface}} partial interface INativeAPI @@ -146,7 +146,7 @@ partial interface INativeAPI {{VirtualMethodIndex(0, Direction: MarshalDirection.ManagedToUnmanaged)}} {{typeName}} Method({{typeName}} value, in {{typeName}} inValue, ref {{typeName}} refValue, out {{typeName}} outValue); } - + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} """; public string BasicParametersAndModifiers() => BasicParametersAndModifiers(typeof(T).FullName!); @@ -155,9 +155,9 @@ public string BasicParametersAndModifiersNoRef(string typeName, string preDeclar using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; {{preDeclaration}} - + [assembly:DisableRuntimeMarshalling] - + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} {{GeneratedComInterface}} partial interface INativeAPI @@ -165,7 +165,7 @@ partial interface INativeAPI {{VirtualMethodIndex(0)}} {{typeName}} Method({{typeName}} value, in {{typeName}} inValue, out {{typeName}} outValue); } - + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} """; @@ -173,7 +173,7 @@ public string BasicParametersAndModifiersNoImplicitThis(string typeName) => $$"" using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; - + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} {{GeneratedComInterface}} partial interface INativeAPI @@ -191,7 +191,7 @@ public string MarshalUsingCollectionCountInfoParametersAndModifiers(string colle using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; [assembly:DisableRuntimeMarshalling] - + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} {{GeneratedComInterface}} partial interface INativeAPI @@ -208,7 +208,7 @@ partial interface INativeAPI [MarshalUsing(CountElementName = "pOutSize")] out {{collectionType}} pOut, out int pOutSize); } - + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} """; @@ -217,7 +217,7 @@ public string BasicReturnTypeComExceptionHandling(string typeName, string preDec using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; {{preDeclaration}} - + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} {{GeneratedComInterface}} partial interface INativeAPI @@ -225,7 +225,7 @@ partial interface INativeAPI {{VirtualMethodIndex(0, ExceptionMarshalling: ExceptionMarshalling.Com)}} {{typeName}} Method(); } - + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} """; @@ -234,7 +234,7 @@ public string BasicReturnTypeCustomExceptionHandling(string typeName, string cus using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; {{preDeclaration}} - + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} {{GeneratedComInterface}} partial interface INativeAPI @@ -249,7 +249,7 @@ partial interface INativeAPI using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; - + {{GeneratedComInterface}} partial interface IComInterface { @@ -265,7 +265,7 @@ partial interface IComInterface2 : IComInterface using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; - + {{GeneratedComInterface}} partial interface IComInterface { @@ -282,6 +282,15 @@ partial interface {|#0:IComInterface2|} : IComInterface, IOtherComInterface void Method2(); } """; + + public string ComInterfaceParameters => BasicParametersAndModifiers("IComInterface2") + $$""" + {{GeneratedComInterface}} + partial interface IComInterface2 + { + void Method2(); + } + """; + public class ManagedToUnmanaged : IVirtualMethodIndexSignatureProvider { public MarshalDirection Direction => MarshalDirection.ManagedToUnmanaged; diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs index 89c1e84fd5ab80..c866713b9d402a 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs @@ -4,16 +4,13 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; -using Microsoft.CodeAnalysis; using Microsoft.Interop.UnitTests; using Xunit; using VerifyVTableGenerator = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; using VerifyComInterfaceGenerator = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; -using Microsoft.CodeAnalysis.Testing; namespace ComInterfaceGenerator.Unit.Tests { @@ -327,7 +324,6 @@ public static IEnumerable CustomCollectionsManagedToUnmanaged(Generato [MemberData(nameof(UnmanagedToManagedCodeSnippetsToCompile), GeneratorKind.VTableIndexStubGenerator)] [MemberData(nameof(CustomCollectionsManagedToUnmanaged), GeneratorKind.VTableIndexStubGenerator)] [MemberData(nameof(CustomCollections), GeneratorKind.VTableIndexStubGenerator)] - [MemberData(nameof(CustomCollections), GeneratorKind.VTableIndexStubGenerator)] public async Task ValidateVTableIndexSnippets(string id, string source) { _ = id; @@ -338,6 +334,7 @@ public static IEnumerable ComInterfaceSnippetsToCompile() { CodeSnippets codeSnippets = new(new GeneratedComInterfaceAttributeProvider()); yield return new object[] { ID(), codeSnippets.DerivedComInterfaceType }; + yield return new object[] { ID(), codeSnippets.ComInterfaceParameters }; } [Theory] diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs index 0eb98306934316..488be39a8e482c 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs @@ -426,7 +426,7 @@ public static string CustomStringMarshallingParametersAndModifiers() static class Marshaller { public static nint ConvertToUnmanaged({{typeName}} s) => default; - + public static {{typeName}} ConvertToManaged(nint i) => default; } """; @@ -754,13 +754,21 @@ public static string SafeHandleWithCustomDefaultConstructorAccessibility(bool pr class MySafeHandle : SafeHandle { {{(privateCtor ? "private" : "public")}} MySafeHandle() : base(System.IntPtr.Zero, true) { } - + public override bool IsInvalid => handle == System.IntPtr.Zero; - + protected override bool ReleaseHandle() => true; } """; + public static string GeneratedComInterface => BasicParametersAndModifiers("MyInterfaceType", "using System.Runtime.InteropServices.Marshalling;") + """ + [GeneratedComInterface] + interface MyInterfaceType + { + void Method(); + } + """; + public static string PreprocessorIfAroundFullFunctionDefinition(string define) => $$""" partial class Test diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs index 6a932ac60a1ce7..e231c36dc137dd 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs @@ -124,11 +124,20 @@ public static IEnumerable CodeSnippetsToCompile() yield return new[] { ID(), CodeSnippets.MarshalAsParametersAndModifiers(UnmanagedType.LPUTF8Str) }; yield return new[] { ID(), CodeSnippets.MarshalAsParametersAndModifiers(UnmanagedType.LPStr) }; yield return new[] { ID(), CodeSnippets.MarshalAsParametersAndModifiers(UnmanagedType.BStr) }; + yield return new[] { ID(), CodeSnippets.MarshalAsParametersAndModifiers(UnmanagedType.Interface) }; + // TODO: Do we want to limit support of UnmanagedType.Interface to a subset of types? + // TODO: Should we block delegate types as they use to have special COM interface marshalling that we have since + // blocked? Blocking it would help .NET Framework->.NET migration as there wouldn't be a silent behavior change. + yield return new[] { ID(), CodeSnippets.MarshalAsParametersAndModifiers(UnmanagedType.Interface) }; + yield return new[] { ID(), CodeSnippets.MarshalAsParametersAndModifiers(UnmanagedType.Interface) }; + + // MarshalAs with array element UnmanagedType yield return new[] { ID(), CodeSnippets.MarshalAsArrayParameterWithNestedMarshalInfo(UnmanagedType.LPWStr) }; yield return new[] { ID(), CodeSnippets.MarshalAsArrayParameterWithNestedMarshalInfo(UnmanagedType.LPUTF8Str) }; yield return new[] { ID(), CodeSnippets.MarshalAsArrayParameterWithNestedMarshalInfo(UnmanagedType.LPStr) }; yield return new[] { ID(), CodeSnippets.MarshalAsArrayParameterWithNestedMarshalInfo(UnmanagedType.BStr) }; + // [In, Out] attributes // By value non-blittable array yield return new[] { ID(), CodeSnippets.ByValueParameterWithModifier("S[]", "Out") @@ -236,6 +245,9 @@ public static IEnumerable CodeSnippetsToCompile() yield return new[] { ID(), CodeSnippets.MaybeBlittableGenericTypeParametersAndModifiers() }; yield return new[] { ID(), CodeSnippets.MaybeBlittableGenericTypeParametersAndModifiers() }; yield return new[] { ID(), CodeSnippets.GenericsStress }; + + // Type-level interop generator trigger attributes + yield return new[] { ID(), CodeSnippets.GeneratedComInterface }; } public static IEnumerable CustomCollections() diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs index d2bd400584f2fe..3288160f3ff1b8 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs @@ -36,7 +36,7 @@ public static IEnumerable NoMarshallingRequiredTypes() => new[] new object[] { typeof(bool*) }, new object[] { typeof(char*) }, // See issue https://github.com/dotnet/runtime/issues/71891 - // new object[] { typeof(delegate* ) }, + // new object[] { typeof(delegate* ) }, new object[] { typeof(IntPtr) }, new object[] { typeof(ConsoleKey) }, // enum }; @@ -77,10 +77,10 @@ unsafe partial class Test { [DllImport("DoesNotExist")] public static extern void {|#0:Method_In|}(in {{typeName}} p); - + [DllImport("DoesNotExist")] public static extern void {|#1:Method_Out|}(out {{typeName}} p); - + [DllImport("DoesNotExist")] public static extern void {|#2:Method_Ref|}(ref {{typeName}} p); } @@ -132,7 +132,6 @@ public async Task UnsupportedType_NoDiagnostic(Type type) } [Theory] - [InlineData(UnmanagedType.Interface)] [InlineData(UnmanagedType.IDispatch)] [InlineData(UnmanagedType.IInspectable)] [InlineData(UnmanagedType.IUnknown)] @@ -145,7 +144,7 @@ unsafe partial class Test { [DllImport("DoesNotExist")] public static extern void Method_Parameter([MarshalAs(UnmanagedType.{{unmanagedType}}, MarshalType = "DNE")]int p); - + [DllImport("DoesNotExist")] [return: MarshalAs(UnmanagedType.{{unmanagedType}}, MarshalType = "DNE")] public static extern int Method_Return(); @@ -154,6 +153,32 @@ unsafe partial class Test await VerifyCS.VerifyAnalyzerAsync(source); } + [Fact] + public async Task UnmanagedTypeInterfaceWithComImportType_NoDiagnostic() + { + string source = $$""" + using System.Runtime.InteropServices; + + [ComImport] + [Guid("8509bcd0-45bc-4b04-bb45-f3cac0b4cabd")] + interface IFoo + { + void Bar(); + } + + unsafe partial class Test + { + [DllImport("DoesNotExist")] + public static extern void Method_Parameter([MarshalAs(UnmanagedType.Interface)]IFoo p); + + [DllImport("DoesNotExist")] + [return: MarshalAs(UnmanagedType.Interface, MarshalType = "DNE")] + public static extern IFoo Method_Return(); + } + """; + await VerifyCS.VerifyAnalyzerAsync(source); + } + [Fact] public async Task LibraryImport_NoDiagnostic() { @@ -193,7 +218,7 @@ unsafe partial class Test { [DllImport("DoesNotExist")] public static extern void {|#0:Method_Parameter|}({{typeName}} p); - + [DllImport("DoesNotExist")] public static extern {{typeName}} {|#1:Method_Return|}(); } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 5362c12fcb659e..984f1b935426af 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -13461,7 +13461,7 @@ public enum MarshalMode ElementRef = 8, ElementOut = 9 } - [System.AttributeUsageAttribute(System.AttributeTargets.Struct | System.AttributeTargets.Class | System.AttributeTargets.Enum | System.AttributeTargets.Delegate)] + [System.AttributeUsageAttribute(System.AttributeTargets.Struct | System.AttributeTargets.Class | System.AttributeTargets.Enum | System.AttributeTargets.Interface | System.AttributeTargets.Delegate)] public sealed partial class NativeMarshallingAttribute : System.Attribute { public NativeMarshallingAttribute(System.Type nativeType) { } diff --git a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml index f744ff4c19376f..134958625a68ef 100644 --- a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml +++ b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml @@ -2761,6 +2761,12 @@ net7.0/System.Linq.Queryable.dll net8.0/System.Linq.Queryable.dll + + CP0015 + T:System.Runtime.InteropServices.Marshalling.NativeMarshallingAttribute:[T:System.AttributeUsageAttribute] + net7.0/System.Runtime.dll + net8.0/System.Runtime.dll + CP0015 M:System.Reflection.Metadata.MetadataUpdateHandlerAttribute.#ctor(System.Type)$0:[T:System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute]