From efc7ffae28653e7842c1411721d73be993b2c70d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 11 May 2023 13:14:10 -0700 Subject: [PATCH 01/15] Allow NativeMarshalling on interfaces. --- .../InteropServices/Marshalling/NativeMarshallingAttribute.cs | 2 +- src/libraries/System.Runtime/ref/System.Runtime.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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) { } From e18c8e9df8fd2c467f81c66dc5d253b2a2f09891 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 11 May 2023 14:24:47 -0700 Subject: [PATCH 02/15] Implement new marshaller types --- .../ref/System.Runtime.InteropServices.cs | 30 ++++++++++-- .../src/System.Runtime.InteropServices.csproj | 2 + .../Marshalling/ComInterfaceMarshaller.cs | 45 ++++++++++++++++++ .../Marshalling/StrategyBasedComWrappers.cs | 2 + .../UniqueComInterfaceMarshaller.cs | 46 +++++++++++++++++++ 5 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComInterfaceMarshaller.cs create mode 100644 src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/UniqueComInterfaceMarshaller.cs 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..7ec07841e428f9 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComInterfaceMarshaller.cs @@ -0,0 +1,45 @@ +// 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 + { + public static void* ConvertToUnmanaged(T? managed) + { + if (managed == null) + { + return null; + } + if (ComWrappers.TryGetComInstance(managed, out nint unknown)) + { + return (void*)unknown; + } + return (void*)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateComInterfaceForObject(managed, CreateComInterfaceFlags.None); + } + + public static T? ConvertToManaged(void* unmanaged) + { + if (unmanaged == null) + { + return default; + } + return (T)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateObjectForComInstance((nint)unmanaged, CreateObjectFlags.Unwrap); + } + } +} 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..6c522420746772 --- /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)) + { + return (void*)unknown; + } + return (void*)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateComInterfaceForObject(managed, CreateComInterfaceFlags.None); + } + + public static T? ConvertToManaged(void* unmanaged) + { + if (unmanaged == null) + { + return default; + } + return (T)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateObjectForComInstance((nint)unmanaged, CreateObjectFlags.Unwrap | CreateObjectFlags.UniqueInstance); + } + } +} From c872e7d6f31a71571678609fa77f84320831ee03 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 11 May 2023 14:56:47 -0700 Subject: [PATCH 03/15] Make sure we cast the returned COM interface pointer to the correct interface. --- .../Marshalling/ComInterfaceMarshaller.cs | 31 +++++++++++++++++-- .../UniqueComInterfaceMarshaller.cs | 7 +++-- 2 files changed, 32 insertions(+), 6 deletions(-) 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 index 7ec07841e428f9..9cbbcdc6c955aa 100644 --- 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 @@ -26,11 +26,12 @@ public static unsafe class ComInterfaceMarshaller { return null; } - if (ComWrappers.TryGetComInstance(managed, out nint unknown)) + nint unknown; + if (!ComWrappers.TryGetComInstance(managed, out unknown)) { - return (void*)unknown; + unknown = StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateComInterfaceForObject(managed, CreateComInterfaceFlags.None); } - return (void*)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateComInterfaceForObject(managed, CreateComInterfaceFlags.None); + return CastIUnknownToInterfaceType(unknown); } public static T? ConvertToManaged(void* unmanaged) @@ -41,5 +42,29 @@ public static unsafe class ComInterfaceMarshaller } return (T)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateObjectForComInstance((nint)unmanaged, CreateObjectFlags.Unwrap); } + + private static readonly Guid? TargetInterfaceIID; + + static ComInterfaceMarshaller() + { + if (StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(T).TypeHandle) is { } interfaceDetails) + { + TargetInterfaceIID = interfaceDetails.Iid; + } + } + + internal static void* CastIUnknownToInterfaceType(nint unknown) + { + if (TargetInterfaceIID is null) + { + return unknown; + } + if (Marshal.QueryInterface(unknown, ref TargetInterfaceIID, out nint interfacePointer) != 0) + { + 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/UniqueComInterfaceMarshaller.cs b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/UniqueComInterfaceMarshaller.cs index 6c522420746772..1444208badbafd 100644 --- 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 @@ -27,11 +27,12 @@ public static unsafe class UniqueComInterfaceMarshaller { return null; } - if (ComWrappers.TryGetComInstance(managed, out nint unknown)) + nint unknown; + if (!ComWrappers.TryGetComInstance(managed, out unknown)) { - return (void*)unknown; + unknown = StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateComInterfaceForObject(managed, CreateComInterfaceFlags.None); } - return (void*)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateComInterfaceForObject(managed, CreateComInterfaceFlags.None); + return ComInterfaceMarshaller.CastIUnknownToInterfaceType(unknown); } public static T? ConvertToManaged(void* unmanaged) From 85725f5b64fa0efb7206c7653d4fb0b8e668586e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 11 May 2023 15:06:30 -0700 Subject: [PATCH 04/15] Implement support in the source generators to automatically use the ComInterfaceGenerator for GeneratedComInterface types and MarshalAs(UnmanagedType.Interface) for those that like to be explicit. --- .../Common/DefaultMarshallingInfoParser.cs | 9 ++-- .../ComInterfaceMarshallingInfoProvider.cs | 54 +++++++++++++++++++ .../MarshalAsAttributeParser.cs | 5 ++ .../StringMarshallingInfoProvider.cs | 1 - .../TypeNames.cs | 2 + 5 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ComInterfaceMarshallingInfoProvider.cs diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs b/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs index 2150eacf9689c2..84852f57099b4f 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs @@ -38,17 +38,18 @@ 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))); + 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..c3c1148850e794 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 @@ -98,6 +98,11 @@ UseSiteAttributeData IUseSiteAttributeParser.ParseAttribute(AttributeData attrib } } + if (unmanagedType == UnmanagedType.Interface) + { + 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..f52c659409db94 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 @@ -140,5 +140,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"; } } From 5ad983823170a1ab6fa98f145b89ecf062c15e87 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 12 May 2023 12:26:23 -0700 Subject: [PATCH 05/15] Fix ref leak. --- .../InteropServices/Marshalling/ComInterfaceMarshaller.cs | 1 + 1 file changed, 1 insertion(+) 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 index 9cbbcdc6c955aa..aa5e8850ca4ea8 100644 --- 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 @@ -61,6 +61,7 @@ static ComInterfaceMarshaller() } if (Marshal.QueryInterface(unknown, ref TargetInterfaceIID, 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); From 1198b1818f83249cb7c1e604b44b0a165927490f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 12 May 2023 13:03:52 -0700 Subject: [PATCH 06/15] Add some unit-test level tests for the interface marshalling logic. --- .../Common/DefaultMarshallingInfoParser.cs | 2 +- .../CodeSnippets.cs | 59 +++++++++++-------- .../Compiles.cs | 5 +- .../CodeSnippets.cs | 14 ++++- .../Compiles.cs | 12 ++++ .../ConvertToLibraryImportAnalyzerTests.cs | 11 ++-- 6 files changed, 64 insertions(+), 39 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs b/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs index 84852f57099b4f..7149fc40a6f639 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs @@ -49,7 +49,7 @@ public static MarshallingInfoParser Create(StubEnvironment env, IGeneratorDiagno new CharMarshallingInfoProvider(defaultInfo), new StringMarshallingInfoProvider(env.Compilation, diagnostics, unparsedAttributeData, defaultInfo), new BooleanMarshallingInfoProvider(), - new BlittableTypeMarshallingInfoProvider(env.Compilation)); + new BlittableTypeMarshallingInfoProvider(env.Compilation))); } } } 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..63db8d2c985171 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(); @@ -193,7 +192,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|}(); } From a28d9a09387606b86c4f195ff4136141ac03f799 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 12 May 2023 13:27:20 -0700 Subject: [PATCH 07/15] Add runtime tests for the interface marshallers, with some tests disabled due to issues in our design that need to be resolved. --- .../GeneratedComClassTests.cs | 22 ++++++------- .../IGetAndSetIntTests.cs | 31 +++++++++++++++++++ 2 files changed, 41 insertions(+), 12 deletions(-) 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..7285db3892bdb9 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,29 @@ public unsafe void CallRcwFromGeneratedComInterface() intObj.SetInt(2); Assert.Equal(2, intObj.GetInt()); } + + [Fact] + [ActiveIssue("When the unmanaged side uses ComWrappers, even when not using the marshaller or StrategyBasedComWrappers, the Unwrap flag causes the underlying object to be returned instead of an RCW.")] + public unsafe void CallRcwFromGeneratedComInterfaceConstructedByMarshaller() + { + var intObj = NewNativeObjectWithMarshaller(); // new_native_object + + Assert.Equal(0, intObj.GetInt()); + intObj.SetInt(2); + Assert.Equal(2, intObj.GetInt()); + } + + [Fact] + [ActiveIssue("When the unmanaged side uses ComWrappers, even when not using the marshaller or StrategyBasedComWrappers, the Unwrap flag causes the underlying object to be returned instead of an RCW.")] + 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()); + } } } From 4ac21c31ba451d481a8b52a900fb43e18356db96 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 12 May 2023 15:23:34 -0700 Subject: [PATCH 08/15] Fix compilation issues. --- .../InteropServices/Marshalling/ComInterfaceMarshaller.cs | 8 ++++---- .../Marshalling/UniqueComInterfaceMarshaller.cs | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) 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 index aa5e8850ca4ea8..d3533123854a0d 100644 --- 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 @@ -26,8 +26,7 @@ public static unsafe class ComInterfaceMarshaller { return null; } - nint unknown; - if (!ComWrappers.TryGetComInstance(managed, out unknown)) + if (!ComWrappers.TryGetComInstance(managed, out nint unknown)) { unknown = StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateComInterfaceForObject(managed, CreateComInterfaceFlags.None); } @@ -57,9 +56,10 @@ static ComInterfaceMarshaller() { if (TargetInterfaceIID is null) { - return unknown; + return (void*)unknown; } - if (Marshal.QueryInterface(unknown, ref TargetInterfaceIID, out nint interfacePointer) != 0) + 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}'"); 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 index 1444208badbafd..1fdc5eafee35ba 100644 --- 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 @@ -27,8 +27,7 @@ public static unsafe class UniqueComInterfaceMarshaller { return null; } - nint unknown; - if (!ComWrappers.TryGetComInstance(managed, out unknown)) + if (!ComWrappers.TryGetComInstance(managed, out nint unknown)) { unknown = StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateComInterfaceForObject(managed, CreateComInterfaceFlags.None); } From dedbecbf68e46ddb051a0bc894783bdcc0a44210 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 15 May 2023 13:48:56 -0700 Subject: [PATCH 09/15] Enable tests that were disabled due to old Unwrap behavior --- .../tests/ComInterfaceGenerator.Tests/IGetAndSetIntTests.cs | 2 -- 1 file changed, 2 deletions(-) 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 7285db3892bdb9..ac1c3b5bfdee47 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IGetAndSetIntTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IGetAndSetIntTests.cs @@ -41,7 +41,6 @@ public unsafe void CallRcwFromGeneratedComInterface() } [Fact] - [ActiveIssue("When the unmanaged side uses ComWrappers, even when not using the marshaller or StrategyBasedComWrappers, the Unwrap flag causes the underlying object to be returned instead of an RCW.")] public unsafe void CallRcwFromGeneratedComInterfaceConstructedByMarshaller() { var intObj = NewNativeObjectWithMarshaller(); // new_native_object @@ -52,7 +51,6 @@ public unsafe void CallRcwFromGeneratedComInterfaceConstructedByMarshaller() } [Fact] - [ActiveIssue("When the unmanaged side uses ComWrappers, even when not using the marshaller or StrategyBasedComWrappers, the Unwrap flag causes the underlying object to be returned instead of an RCW.")] public unsafe void CallRcwFromGeneratedComInterfaceConstructedByUniqueMarshaller() { var intObj = NewNativeObjectWithUniqueMarshaller(); // new_native_object From 06a0859310010f1431727c136a7a45e0c949813b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 15 May 2023 14:37:30 -0700 Subject: [PATCH 10/15] Use explicit type --- .../InteropServices/Marshalling/ComInterfaceMarshaller.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index d3533123854a0d..f87fa03d71bb4e 100644 --- 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 @@ -46,7 +46,7 @@ public static unsafe class ComInterfaceMarshaller static ComInterfaceMarshaller() { - if (StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(T).TypeHandle) is { } interfaceDetails) + if (StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(T).TypeHandle) is IIUnknownDerivedDetails interfaceDetails) { TargetInterfaceIID = interfaceDetails.Iid; } From 7093334699a7a42eda9fe16816f4be6d3d51a703 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 15 May 2023 14:38:48 -0700 Subject: [PATCH 11/15] Set the TargetInterfaceIID with an initializer instead of a static constructor due to analyzer --- .../Marshalling/ComInterfaceMarshaller.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) 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 index f87fa03d71bb4e..2758d410e5f776 100644 --- 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 @@ -20,6 +20,8 @@ namespace System.Runtime.InteropServices.Marshalling [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) @@ -42,16 +44,6 @@ public static unsafe class ComInterfaceMarshaller return (T)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateObjectForComInstance((nint)unmanaged, CreateObjectFlags.Unwrap); } - private static readonly Guid? TargetInterfaceIID; - - static ComInterfaceMarshaller() - { - if (StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(T).TypeHandle) is IIUnknownDerivedDetails interfaceDetails) - { - TargetInterfaceIID = interfaceDetails.Iid; - } - } - internal static void* CastIUnknownToInterfaceType(nint unknown) { if (TargetInterfaceIID is null) From 8673d4225ee953a01a8ac51bc47ef4717d050876 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 16 May 2023 10:05:40 -0700 Subject: [PATCH 12/15] Add comments on behavior with non-GeneratedComInterface types --- .../libraries/LibraryImportGenerator/Compatibility.md | 6 ++++++ .../InteropServices/Marshalling/ComInterfaceMarshaller.cs | 1 + 2 files changed, 7 insertions(+) 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.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComInterfaceMarshaller.cs b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComInterfaceMarshaller.cs index 2758d410e5f776..382540a1039a88 100644 --- 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 @@ -48,6 +48,7 @@ public static unsafe class ComInterfaceMarshaller { 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; From e8f238d5c87483cf5b6798691773ad2987b142c4 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 16 May 2023 13:41:25 -0700 Subject: [PATCH 13/15] Update ApiCompat baseline and exclude ComImport types from the new marshaller logic to avoid pre-emptive "migrate to LibraryImport" warnings. --- .../MarshalAsAttributeParser.cs | 8 +++++++- .../gen/Microsoft.Interop.SourceGeneration/TypeNames.cs | 2 ++ .../ApiCompatBaseline.NetCoreAppLatestStable.xml | 6 ++++++ 3 files changed, 15 insertions(+), 1 deletion(-) 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 c3c1148850e794..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,8 +97,15 @@ 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); } 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 f52c659409db94..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"; 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] From c3ca8e1f28c5ba4eae6c9e4d3eb1cf2530875d86 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 16 May 2023 16:04:42 -0700 Subject: [PATCH 14/15] Add test --- .../ConvertToLibraryImportAnalyzerTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) 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 63db8d2c985171..3dde26f67508f0 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs @@ -153,6 +153,32 @@ unsafe partial class Test await VerifyCS.VerifyAnalyzerAsync(source); } + [Fact] + public async Task UnmanagedTypeInterfaceWithComImportType_NoDiagnostic(UnmanagedType unmanagedType) + { + 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() { From 2d43ca9cd08f3d3c62e8da208990dadb22422e26 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 19 May 2023 15:39:23 -0700 Subject: [PATCH 15/15] Fix test build by removing unused parameter --- .../ConvertToLibraryImportAnalyzerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3dde26f67508f0..3288160f3ff1b8 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs @@ -154,7 +154,7 @@ unsafe partial class Test } [Fact] - public async Task UnmanagedTypeInterfaceWithComImportType_NoDiagnostic(UnmanagedType unmanagedType) + public async Task UnmanagedTypeInterfaceWithComImportType_NoDiagnostic() { string source = $$""" using System.Runtime.InteropServices;