From 1dedddef7b8f389e3720b9c643306b98b092ad87 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 18 Jul 2023 13:27:37 -0700 Subject: [PATCH] Add opt-in support for GeneratedComInterface/ComImport RCW interop (#87583) Co-authored-by: Jan Kotas --- .../ComInterfaceGenerator/Compatibility.md | 27 +++ eng/testing/linker/project.csproj.template | 1 + .../src/ILLink/ILLink.Substitutions.xml | 10 ++ .../src/System.Runtime.InteropServices.csproj | 5 + .../ComImportInteropInterfaceDetailsStrategy.cs | 198 +++++++++++++++++++++ .../InteropServices/Marshalling/ComObject.cs | 20 ++- .../Marshalling/StrategyBasedComWrappers.cs | 20 ++- .../ComInterfaceGenerator.Tests.csproj | 2 +- .../GeneratedComInterfaceComImportInteropTests.cs | 79 ++++++++ ...GeneratedComInterfaceComImportInteropTrimmed.cs | 54 ++++++ ...stem.Runtime.InteropServices.TrimmingTests.proj | 23 +++ 11 files changed, 436 insertions(+), 3 deletions(-) create mode 100644 docs/design/libraries/ComInterfaceGenerator/Compatibility.md create mode 100644 src/libraries/System.Runtime.InteropServices/src/ILLink/ILLink.Substitutions.xml create mode 100644 src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComImportInteropInterfaceDetailsStrategy.cs create mode 100644 src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComInterfaceComImportInteropTests.cs create mode 100644 src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/GeneratedComInterfaceComImportInteropTrimmed.cs create mode 100644 src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/System.Runtime.InteropServices.TrimmingTests.proj diff --git a/docs/design/libraries/ComInterfaceGenerator/Compatibility.md b/docs/design/libraries/ComInterfaceGenerator/Compatibility.md new file mode 100644 index 0000000..8fbc707 --- /dev/null +++ b/docs/design/libraries/ComInterfaceGenerator/Compatibility.md @@ -0,0 +1,27 @@ +# Semantic Compatibility + +Documentation on compatibility guidance and the current state. The version headings act as a rolling delta between the previous version. + +## .NET 8 + +### Interface base types + +IUnknown-derived interfaces are supported. IDispatch-based interfaces are disallowed. The default is IUnknown-derived (in comparison to the built-in support's default of IDispatch-derived). + +### Marshalling rules + +The marshalling rules are identical to LibraryImportGenerator's support. + +### Interface inheritance + +Interface inheritance is supported for up to one COM-based interface type. Unlike the built-in COM interop system, base interface methods do **NOT** need to be redefined. The source generator discovers the members from the base interface and generates the derived interface members at appropriate offsets. + +The generator also generates shadow members in the derived interface for each base interface member. The shadow members have default implementations that call the base interface member, but the emitted code for the "COM Object Wrapper" implementation will override the shadow members with a call to the underlying COM interface member on the current interface. This shadow member support helps reduce `QueryInterface` overhead in interface inheritance scenarios. + +### Interop with `ComImport` + +Source-generated COM will provide limited opt-in interop with `ComImport`-based COM interop. In particular, the following scenarios are supported: + +- Casting a "Com Object Wrapper" created using `StrategyBasedComWrappers` to a `ComImport`-based interface type. + +This support is achieved through some internal interfaces and reflection-emit to shim a `DynamicInterfaceCastableImplementation` of a `ComImport` interface to use the built-in runtime interop marshalling support. The core of this experience is implemented by the `System.Runtime.InteropServices.Marshalling.ComImportInteropInterfaceDetailsStrategy` class. diff --git a/eng/testing/linker/project.csproj.template b/eng/testing/linker/project.csproj.template index 2295ab9..8258a21 100644 --- a/eng/testing/linker/project.csproj.template +++ b/eng/testing/linker/project.csproj.template @@ -9,6 +9,7 @@ {PublishAot} {AppHostSourcePath} {SingleFileHostSourcePath} + true {MonoAOTCompilerDir} diff --git a/src/libraries/System.Runtime.InteropServices/src/ILLink/ILLink.Substitutions.xml b/src/libraries/System.Runtime.InteropServices/src/ILLink/ILLink.Substitutions.xml new file mode 100644 index 0000000..958b0e0 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/src/ILLink/ILLink.Substitutions.xml @@ -0,0 +1,10 @@ + + + + + + + + + + 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 adc4319..393febd 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,7 @@ + @@ -73,6 +74,10 @@ + + + + diff --git a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComImportInteropInterfaceDetailsStrategy.cs b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComImportInteropInterfaceDetailsStrategy.cs new file mode 100644 index 0000000..e4579ac --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComImportInteropInterfaceDetailsStrategy.cs @@ -0,0 +1,198 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices.Marshalling +{ + /// + /// An interface details strategy that enables discovering both interfaces defined with source-generated COM (i.e. and ) and built-in COM (i.e. ). + /// + /// + /// This strategy is meant for intermediary adoption scenarios and is not compatible with trimming or NativeAOT by design. Since built-in COM is not trim friendly or AOT-compatible, these restrictions are okay. + /// This strategy only supports "COM Object Wrapper" scenarios, so casting a COM object wrapper to a -attributed type. It does not support exposing a -attributed type as an additional interface on a managed object wrapper. + /// The strategy provides -based implementations of -attributed interfaces by dynamically generating an interface using that has the following shape: + /// + /// [assembly:IgnoresAccessChecksTo("AssemblyContainingIComInterface")] + /// [assembly:IgnoresAccessChecksTo("AssemblyContainingRetType")] + /// [assembly:IgnoresAccessChecksTo("AssemblyContainingArgType1")] + /// [assembly:IgnoresAccessChecksTo("AssemblyContainingArgType2")] + /// // One attribute per containing assembly of each type used in each method signature of the interface. + /// + /// namespace System.Runtime.CompilerServices + /// { + /// [AssemblyUsage(AttributeTargets.Assembly, AllowMultiple = true)] + /// internal class IgnoresAccessChecksToAttribute : Attribute + /// { + /// public IgnoresAccessChecksToAttribute(string assemblyName) { } + /// } + /// } + /// + /// [DynamicInterfaceCastableImplementation] + /// interface InterfaceForwarder : IComInterface + /// { + /// RetType IComInterface.Method1(ArgType1 arg1, ArgType2 arg2, ...) + /// { + /// return ((IComInterface)((IComImportAdapter)this).GetRuntimeCallableWrapper())(arg1, arg2, ...); + /// } + /// } + /// + /// + /// This mechanism allows source-generated COM interop to allow using built-in COM interfaces with runtime-defined marshalling behavior with minimal work on the source-generated COM interop side. + /// Additionally, by scoping the majority of the logic to this class, we make this logic more easily trimmable. + /// + /// We emit the IgnoresAccessChecksToAttribute to enable casting to internal types, which is a very common scenario (most types are internal). + /// + [RequiresDynamicCode("Enabling interop between source-generated and built-in COM is not supported when trimming is enabled.")] + [RequiresUnreferencedCode("Enabling interop between source-generated and built-in COM requires dynamic code generation.")] + internal sealed class ComImportInteropInterfaceDetailsStrategy : IIUnknownInterfaceDetailsStrategy + { + public static readonly IIUnknownInterfaceDetailsStrategy Instance = new ComImportInteropInterfaceDetailsStrategy(); + + private readonly ConditionalWeakTable _forwarderInterfaceCache = new(); + + // TODO: Support exposing ComImport interfaces through StrategyBasedComWrappers? + public IComExposedDetails? GetComExposedTypeDetails(RuntimeTypeHandle type) => DefaultIUnknownInterfaceDetailsStrategy.Instance.GetComExposedTypeDetails(type); + + public IIUnknownDerivedDetails? GetIUnknownDerivedDetails(RuntimeTypeHandle type) + { + Type runtimeType = Type.GetTypeFromHandle(type)!; + if (!runtimeType.IsImport) + { + return DefaultIUnknownInterfaceDetailsStrategy.Instance.GetIUnknownDerivedDetails(type); + } + + Type implementationType = _forwarderInterfaceCache.GetValue(runtimeType, runtimeType => + { + AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("ComImportForwarder"), runtimeType.IsCollectible ? AssemblyBuilderAccess.RunAndCollect : AssemblyBuilderAccess.Run); + ModuleBuilder module = assembly.DefineDynamicModule("ComImportForwarder"); + + ConstructorInfo ignoresAccessChecksToAttributeConstructor = GetIgnoresAccessChecksToAttributeConstructor(module); + + assembly.SetCustomAttribute(new CustomAttributeBuilder(ignoresAccessChecksToAttributeConstructor, new object[] { typeof(IComImportAdapter).Assembly.GetName().Name! })); + + TypeBuilder implementation = module.DefineType("InterfaceForwarder", TypeAttributes.Interface | TypeAttributes.Abstract, parent: null, interfaces: runtimeType.GetInterfaces()); + implementation.AddInterfaceImplementation(runtimeType); + implementation.SetCustomAttribute(new CustomAttributeBuilder(typeof(DynamicInterfaceCastableImplementationAttribute).GetConstructor(Array.Empty())!, Array.Empty())); + + foreach (Type iface in implementation.GetInterfaces()) + { + assembly.SetCustomAttribute(new CustomAttributeBuilder(ignoresAccessChecksToAttributeConstructor, new object[] { iface.Assembly.GetName().Name! })); + foreach (MethodInfo method in iface.GetMethods()) + { + Type[] returnTypeOptionalModifiers = method.ReturnParameter.GetOptionalCustomModifiers(); + Type[] returnTypeRequiredModifiers = method.ReturnParameter.GetRequiredCustomModifiers(); + ParameterInfo[] parameters = method.GetParameters(); + var parameterTypes = new Type[parameters.Length]; + var parameterOptionalModifiers = new Type[parameters.Length][]; + var parameterRequiredModifiers = new Type[parameters.Length][]; + for (int i = 0; i < parameters.Length; i++) + { + parameterTypes[i] = parameters[i].ParameterType; + parameterOptionalModifiers[i] = parameters[i].GetOptionalCustomModifiers(); + parameterRequiredModifiers[i] = parameters[i].GetRequiredCustomModifiers(); + } + MethodBuilder builder = implementation.DefineMethod(method.Name, MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.Virtual, CallingConventions.HasThis, method.ReturnType, returnTypeRequiredModifiers, returnTypeOptionalModifiers, parameterTypes, parameterRequiredModifiers, parameterOptionalModifiers); + ILGenerator il = builder.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, typeof(IComImportAdapter)); + il.Emit(OpCodes.Callvirt, IComImportAdapter.GetRuntimeCallableWrapperMethod); + il.Emit(OpCodes.Castclass, iface); + for (int i = 0; i < parameters.Length; i++) + { + il.Emit(OpCodes.Ldarg, i + 1); + } + il.Emit(OpCodes.Callvirt, method); + il.Emit(OpCodes.Ret); + implementation.DefineMethodOverride(builder, method); + } + } + + return implementation.CreateType(); + }); + + return new ComImportDetails(runtimeType.GUID, implementationType); + } + + private static ConstructorInfo GetIgnoresAccessChecksToAttributeConstructor(ModuleBuilder moduleBuilder) + { + Type attributeType = EmitIgnoresAccessChecksToAttribute(moduleBuilder); + return attributeType.GetConstructor(new Type[] { typeof(string) })!; + } + + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + private static Type EmitIgnoresAccessChecksToAttribute(ModuleBuilder moduleBuilder) + { + var tb = moduleBuilder.DefineType( + "System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute", + TypeAttributes.NotPublic, + typeof(Attribute)); + + var attributeUsage = new CustomAttributeBuilder( + s_attributeUsageCtor, + new object[] { AttributeTargets.Assembly }, + new PropertyInfo[] { s_attributeUsageAllowMultipleProperty }, + new object[] { true }); + tb.SetCustomAttribute(attributeUsage); + + var cb = tb.DefineConstructor( + MethodAttributes.Public | + MethodAttributes.HideBySig | + MethodAttributes.SpecialName | + MethodAttributes.RTSpecialName, + CallingConventions.Standard, + new Type[] { typeof(string) }); + cb.DefineParameter(1, ParameterAttributes.None, "assemblyName"); + + var il = cb.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Call, s_attributeBaseClassCtor); + il.Emit(OpCodes.Ret); + + return tb.CreateType()!; + } + + /// + /// The constructor. + /// + private static readonly ConstructorInfo s_attributeBaseClassCtor = typeof(Attribute).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0]; + + /// + /// The constructor. + /// + private static readonly ConstructorInfo s_attributeUsageCtor = typeof(AttributeUsageAttribute).GetConstructor(new Type[] { typeof(AttributeTargets) })!; + + /// + /// The property. + /// + private static readonly PropertyInfo s_attributeUsageAllowMultipleProperty = typeof(AttributeUsageAttribute).GetProperty(nameof(AttributeUsageAttribute.AllowMultiple))!; + + private sealed class ComImportDetails(Guid iid, Type implementation) : IIUnknownDerivedDetails + { + public Guid Iid { get; } = iid; + + public Type Implementation { get; } = implementation; + + public unsafe void** ManagedVirtualMethodTable => null; + } + + /// + /// This interface enables a COM Object Wrapper (such as ) to provide a built-in COM object to enable integration between built-in COM objects and + /// other COM interop systems like source-generated COM. + /// + internal interface IComImportAdapter + { + internal static readonly MethodInfo GetRuntimeCallableWrapperMethod = typeof(IComImportAdapter).GetMethod(nameof(GetRuntimeCallableWrapper))!; + + /// + /// Gets the built-in COM object that corresponds to the same underlying COM object as this wrapper. + /// + /// The built-in RCW + /// The returned object must be an object such that a call to would return true. + object GetRuntimeCallableWrapper(); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComObject.cs b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComObject.cs index 71fca5a..fab2920 100644 --- a/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComObject.cs +++ b/src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/ComObject.cs @@ -5,16 +5,23 @@ // This API need to be exposed to implement the COM source generator in one form or another. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Versioning; namespace System.Runtime.InteropServices.Marshalling { /// /// Base class for all COM source generated Runtime Callable Wrapper (RCWs). /// - public sealed unsafe class ComObject : IDynamicInterfaceCastable, IUnmanagedVirtualMethodTableProvider + public sealed unsafe class ComObject : IDynamicInterfaceCastable, IUnmanagedVirtualMethodTableProvider, ComImportInteropInterfaceDetailsStrategy.IComImportAdapter { + internal static bool BuiltInComSupported { get; } = AppContext.TryGetSwitch("System.Runtime.InteropServices.BuiltInComInterop.IsSupported", out bool supported) ? supported : true; + internal static bool ComImportInteropEnabled { get; } = AppContext.TryGetSwitch("System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop", out bool enabled) ? enabled : false; + private readonly void* _instancePointer; + private readonly object? _runtimeCallableWrapper; + /// /// Initialize ComObject instance. /// @@ -28,6 +35,11 @@ namespace System.Runtime.InteropServices.Marshalling IUnknownStrategy = iunknownStrategy; CacheStrategy = cacheStrategy; _instancePointer = IUnknownStrategy.CreateInstancePointer(thisPointer); + if (OperatingSystem.IsWindows() && BuiltInComSupported && ComImportInteropEnabled) + { + _runtimeCallableWrapper = Marshal.GetObjectForIUnknown((nint)thisPointer); + Debug.Assert(Marshal.IsComObject(_runtimeCallableWrapper)); + } } /// @@ -138,5 +150,11 @@ namespace System.Runtime.InteropServices.Marshalling return new(result.ThisPtr, result.Table); } + + object ComImportInteropInterfaceDetailsStrategy.IComImportAdapter.GetRuntimeCallableWrapper() + { + Debug.Assert(_runtimeCallableWrapper != null); + return _runtimeCallableWrapper; + } } } 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 e5ba351..6838d51 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 @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; +using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Runtime.CompilerServices; namespace System.Runtime.InteropServices.Marshalling { @@ -17,7 +19,23 @@ namespace System.Runtime.InteropServices.Marshalling protected static IIUnknownCacheStrategy CreateDefaultCacheStrategy() => new DefaultCaching(); - protected virtual IIUnknownInterfaceDetailsStrategy GetOrCreateInterfaceDetailsStrategy() => DefaultIUnknownInterfaceDetailsStrategy; + protected virtual IIUnknownInterfaceDetailsStrategy GetOrCreateInterfaceDetailsStrategy() + { + if (OperatingSystem.IsWindows() && RuntimeFeature.IsDynamicCodeSupported && ComObject.BuiltInComSupported && ComObject.ComImportInteropEnabled) + { + return GetInteropStrategy(); + } + return DefaultIUnknownInterfaceDetailsStrategy; + + // This logic is split into a separate method, otherwise the trimmer will think that these suppressions are unnecessary on various platforms and error on them. + // The easiest way to handle this is to put the case that needs annotations into a separate method. + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "The usage is guarded, but the analyzer and the trimmer don't understand it.")] + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "The opt-in feature is documented to not work in trimming scenarios.")] + static IIUnknownInterfaceDetailsStrategy GetInteropStrategy() + { + return ComImportInteropInterfaceDetailsStrategy.Instance; + } + } protected virtual IIUnknownStrategy GetOrCreateIUnknownStrategy() => DefaultIUnknownStrategy; diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/ComInterfaceGenerator.Tests.csproj b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/ComInterfaceGenerator.Tests.csproj index 2d6a627..b0262f1 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/ComInterfaceGenerator.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/ComInterfaceGenerator.Tests.csproj @@ -4,7 +4,7 @@ $(NetCoreAppCurrent) true true - true + true true $(OutputPath)\Generated diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComInterfaceComImportInteropTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComInterfaceComImportInteropTests.cs new file mode 100644 index 0000000..bcc8186 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComInterfaceComImportInteropTests.cs @@ -0,0 +1,79 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using Microsoft.DotNet.RemoteExecutor; +using SharedTypes.ComInterfaces; +using Xunit; + +namespace ComInterfaceGenerator.Tests +{ + [ConditionalClass(typeof(GeneratedComInterfaceComImportInteropTests), nameof(IsSupported))] + public unsafe partial class GeneratedComInterfaceComImportInteropTests + { + public static bool IsSupported => + RemoteExecutor.IsSupported + && PlatformDetection.IsWindows + && PlatformDetection.IsNotMonoRuntime + && PlatformDetection.IsNotNativeAot; + + [LibraryImport(NativeExportsNE.NativeExportsNE_Binary, EntryPoint = "new_get_and_set_int")] + private static partial IGetAndSetInt NewNativeObject(); + + [ComImport] + [Guid(_guid)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [SuppressMessage("Interoperability", "SYSLIB1096:Convert to 'GeneratedComInterface'", Justification = "This interface is for us to test interop between GeneratedComInterface and ComImport.")] + internal interface IGetAndSetIntComImport + { + int GetInt(); + + public void SetInt(int x); + + public const string _guid = "2c3f9903-b586-46b1-881b-adfce9af47b1"; + } + + [Fact] + public void CallComImportInterfaceMethodsOnGeneratedComObject() + { + using var _ = RemoteExecutor.Invoke(() => + { + IGetAndSetInt obj = NewNativeObject(); +#pragma warning disable SYSLIB1099 // Casting between a 'ComImport' type and a source-generated COM type is not supported + IGetAndSetIntComImport runtimeObj = (IGetAndSetIntComImport)obj; +#pragma warning restore SYSLIB1099 // Casting between a 'ComImport' type and a source-generated COM type is not supported + obj.SetInt(1234); + Assert.Equal(1234, runtimeObj.GetInt()); + runtimeObj.SetInt(4321); + Assert.Equal(4321, obj.GetInt()); + + }, new RemoteInvokeOptions + { + RuntimeConfigurationOptions = + { + { "System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop", true } + } + }); + } + + [Fact] + public void CallComImportInterfaceMethodsOnGeneratedComObject_FeatureFalse_Fails() + { + using var _ = RemoteExecutor.Invoke(() => + { + IGetAndSetInt obj = NewNativeObject(); +#pragma warning disable SYSLIB1099 // Casting between a 'ComImport' type and a source-generated COM type is not supported + Assert.Throws(() => (IGetAndSetIntComImport)obj); +#pragma warning restore SYSLIB1099 // Casting between a 'ComImport' type and a source-generated COM type is not supported + }, new RemoteInvokeOptions + { + RuntimeConfigurationOptions = + { + { "System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop", false } + } + }); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/GeneratedComInterfaceComImportInteropTrimmed.cs b/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/GeneratedComInterfaceComImportInteropTrimmed.cs new file mode 100644 index 0000000..c5ba9a8 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/GeneratedComInterfaceComImportInteropTrimmed.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.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +Type comObject = RemoveTypeTrimAnalysis(typeof(ComObject)); + +// Ensure that the interop details strategy and all of its nested types are fully trimmed away. +if (GetTypeWithoutTrimAnalysis("System.Runtime.InteropServices.Marshalling.ComImportInteropInterfaceDetailsStrategy", comObject.Assembly) != null) +{ + return -1; +} + +// Ensure that the ComInterop object field is trimmed away as well. +if (comObject.GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Any(f => f.Name == "_runtimeCallableWrapper")) +{ + return -2; +} + +var comWrappers = new StrategyBasedComWrappers(); + +var managedObject = new ComClass(); +var nativeObject = comWrappers.GetOrCreateComInterfaceForObject(managedObject, CreateComInterfaceFlags.None); +var wrapper = (IComInterface)comWrappers.GetOrCreateObjectForComInstance(nativeObject, CreateObjectFlags.None); +Marshal.Release(nativeObject); + +return wrapper.Method(); + +[MethodImpl(MethodImplOptions.NoInlining)] +static Type RemoveTypeTrimAnalysis(Type type) => type; + +[MethodImpl(MethodImplOptions.NoInlining)] +static Type GetTypeWithoutTrimAnalysis(string typeName, Assembly assembly) +{ + return assembly.GetType(typeName, throwOnError: false); +} + +[GeneratedComInterface] +[Guid("ad358058-2b72-4801-8d98-043d44dc42c4")] +partial interface IComInterface +{ + int Method(); +} + +[GeneratedComClass] +partial class ComClass : IComInterface +{ + public int Method() => 100; +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/System.Runtime.InteropServices.TrimmingTests.proj b/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/System.Runtime.InteropServices.TrimmingTests.proj new file mode 100644 index 0000000..a812b39 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/System.Runtime.InteropServices.TrimmingTests.proj @@ -0,0 +1,23 @@ + + + + + + + System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop + osx-x64;linux-x64;browser-wasm + + + + System.Runtime.InteropServices.BuiltInComInterop.IsSupported + osx-x64;linux-x64;browser-wasm + + + + + win-x64;browser-wasm + + + + + -- 2.7.4