Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add opt-in support for GeneratedComInterface/ComImport RCW interop #87583

Merged
merged 21 commits into from Jul 18, 2023

Conversation

jkoritzinsky
Copy link
Member

Add opt-in support for casting a source-generated COM object wrapper to a [ComImport]-attributed interface. This support is opt-in only and not compatible with trimming or AOT and will never be so as it needs to use System.Reflection.Emit and it is interoperating with runtime-based COM, neither of which are AOT compatible and one of which is not trimming compatible at all.

The trimming test validates that the support for integrating with ComImport is fully trimmed away when the feature flag is not set.

TODO/Help wanted:

  • We need a property name for the .NET SDK for enabling this feature flag as we also want the analyzers for casting between ComImport and GeneratedComInterface types to not fire when this feature flag is on in the project.
  • I want to confirm that we have this configured such that the default experience in the .NET SDK is to trim away all of this code.
  • Do we need a "Native AOT App" test to validate the trimming behavior for NativeAOT as well, or is the existing test good enough?

cc: @dotnet/illink-contrib for the trimming test

Contributes to #87350

@ghost
Copy link

ghost commented Jun 14, 2023

Tagging subscribers to this area: @dotnet/interop-contrib
See info in area-owners.md if you want to be subscribed.

Issue Details

Add opt-in support for casting a source-generated COM object wrapper to a [ComImport]-attributed interface. This support is opt-in only and not compatible with trimming or AOT and will never be so as it needs to use System.Reflection.Emit and it is interoperating with runtime-based COM, neither of which are AOT compatible and one of which is not trimming compatible at all.

The trimming test validates that the support for integrating with ComImport is fully trimmed away when the feature flag is not set.

TODO/Help wanted:

  • We need a property name for the .NET SDK for enabling this feature flag as we also want the analyzers for casting between ComImport and GeneratedComInterface types to not fire when this feature flag is on in the project.
  • I want to confirm that we have this configured such that the default experience in the .NET SDK is to trim away all of this code.
  • Do we need a "Native AOT App" test to validate the trimming behavior for NativeAOT as well, or is the existing test good enough?

cc: @dotnet/illink-contrib for the trimming test

Contributes to #87350

Author: jkoritzinsky
Assignees: -
Labels:

area-System.Runtime.InteropServices

Milestone: -

Copy link
Member

@vitek-karas vitek-karas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trim test looks good.
I'm a bit surprised we suppress trim analysis warnings in trim tests, but that's not in scope for this change.

@@ -19,13 +20,12 @@ public class StrategyBasedComWrappers : ComWrappers

protected static IIUnknownCacheStrategy CreateDefaultCacheStrategy() => new DefaultCaching();

[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "The member with the attribute is in a IsDynamicCodeSupported block.")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is weird - you should not need this - you would need the pragma, but you should not need UnconditionalSuppressMessage - that means the feature switch is not working correctly for some reason.
Do you get warnings from trimmer/AOT compiler if the feature switch is off ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I get the warning without applying it.

jkoritzinsky and others added 2 commits June 19, 2023 20:23
…update the trimming test to use the generated code for a better example of real-world trimming scenarios.
…e/InteropServices/Marshalling/ComImportInteropInterfaceDetailsStrategy.cs

Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@jkoritzinsky jkoritzinsky requested a review from agocke June 26, 2023 22:05
Copy link
Member

@AaronRobinsonMSFT AaronRobinsonMSFT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM, consider me signed off at present. I'll wait a bit for the other commenters before I mark "Approve".

{
internal static bool BuiltInComSupported { get; } = AppContext.TryGetSwitch("System.Runtime.InteropServices.BuiltInComInterop.IsSupported", out bool supported) ? supported : true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this use Marshal.IsBuiltInComSupported? That is implemented such that runtimes that don't support built-in COM at all return false regardless of the feature switch.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That API isn't exposed outside of CoreLib (we never made it public). I should be able to make it exposed enough for System.Runtime.InteropServices to see it I think without making it public if we would prefer.

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM otherwise

namespace System.Runtime.InteropServices.Marshalling
{
/// <summary>
/// An interface details strategy that enables discovering both interfaces defined with source-generated COM (i.e. <see cref="GeneratedComInterfaceAttribute"/> and <see cref="IUnknownDerivedAttribute{T, TImpl}"/>) and runtime-implemented COM (i.e. <see cref="ComImportAttribute"/>).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// An interface details strategy that enables discovering both interfaces defined with source-generated COM (i.e. <see cref="GeneratedComInterfaceAttribute"/> and <see cref="IUnknownDerivedAttribute{T, TImpl}"/>) and runtime-implemented COM (i.e. <see cref="ComImportAttribute"/>).
/// An interface details strategy that enables discovering both interfaces defined with source-generated COM (i.e. <see cref="GeneratedComInterfaceAttribute"/> and <see cref="IUnknownDerivedAttribute{T, TImpl}"/>) and built-in COM (i.e. <see cref="ComImportAttribute"/>).

/// An interface details strategy that enables discovering both interfaces defined with source-generated COM (i.e. <see cref="GeneratedComInterfaceAttribute"/> and <see cref="IUnknownDerivedAttribute{T, TImpl}"/>) and runtime-implemented COM (i.e. <see cref="ComImportAttribute"/>).
/// </summary>
/// <remarks>
/// This strategy is meant for intermediary adoption scenarios and is not compatible with trimming or NativeAOT by design. Since runtime-implemented COM is not trim friendly or AOT-compatible, these restrictions are okay.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// This strategy is meant for intermediary adoption scenarios and is not compatible with trimming or NativeAOT by design. Since runtime-implemented COM is not trim friendly or AOT-compatible, these restrictions are okay.
/// 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(not commenting on all of these)


object ComImportInteropInterfaceDetailsStrategy.IComImportAdapter.GetRuntimeCallableWrapper()
{
return _runtimeCallableWrapper!;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return _runtimeCallableWrapper!;
Debug.Assert(_runtimeCallableWrapper != null);
return _runtimeCallableWrapper;

Is this guaranteed to be non-zero when this method is called? If it is the case, Debug.Assert would be better than !.


Type implementationType = _forwarderInterfaceCache.GetValue(runtimeType, runtimeType =>
{
AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("ComImportForwarder"), AssemblyBuilderAccess.RunAndCollect);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("ComImportForwarder"), AssemblyBuilderAccess.RunAndCollect);
AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("ComImportForwarder"), runtimeType.IsCollectible ? AssemblyBuilderAccess.RunAndCollect : AssemblyBuilderAccess.Run);

This should reduce the overhead quite a bit for typical non-collectible use case.

@jkoritzinsky
Copy link
Member Author

Failures are known

@jkoritzinsky jkoritzinsky merged commit 1deddde into dotnet:main Jul 18, 2023
173 of 177 checks passed
@jkoritzinsky jkoritzinsky deleted the com-adoption-configuration-rcw branch July 18, 2023 20:27
@ghost ghost locked as resolved and limited conversation to collaborators Aug 18, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants