Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 14, 2025

Fix NativeMarshalling attribute override for COM interfaces

Plan

  • Understand the current code flow and identify where NativeMarshallingAttribute is not being respected for COM interfaces
  • Modify ComInterfaceMarshallingInfoProvider to check for NativeMarshallingAttribute on the type
  • Add test to verify NativeMarshallingAttribute is respected for COM interfaces in LibraryImport
  • Add test to verify NativeMarshallingAttribute is respected for COM interface parameters
  • Build and test the changes
  • Validate all tests pass
  • Add behavioral tests to ComInterfaceGenerator.Tests
  • Simplify native export implementation
  • Cache COM pointer and verify unique marshalling behavior
  • Address code review feedback
  • Move test to LibraryImportGenerator.Tests
  • Run code review
  • Run security scan

Changes Made

  1. MarshallingInfoParser.cs & ComInterfaceMarshallingInfoProvider.cs: Improved implementation by the maintainer to iterate through parsers in order (NativeMarshallingAttributeParser before ComInterfaceMarshallingInfoProvider) rather than iterating through attributes, ensuring NativeMarshallingAttribute is always checked first.

  2. Unit Tests: Added compilation tests to verify the generators handle NativeMarshalling correctly

  3. Behavioral Tests:

    • IUniqueMarshalling.cs: Created test interface with [NativeMarshalling(typeof(UniqueComInterfaceMarshaller<IUniqueMarshalling>))]
    • UniqueMarshalling.cs: Simplified native export using StrategyBasedComWrappers and GeneratedComClass, with cached pointer to return same COM object
    • NativeMarshallingAttributeTests.cs (LibraryImportGenerator.Tests): Tests LibraryImport marshalling - calling method twice returns different managed instances for the same COM object
    • NativeMarshallingAttributeTests.cs (ComInterfaceGenerator.Tests): Tests COM interface method behavior - methods returning interface type return new managed instances

Test Results

  • All ComInterfaceGenerator.Unit.Tests passed ✓
  • All LibraryImportGenerator.Unit.Tests passed ✓
  • LibraryImportGenerator.Tests NativeMarshallingAttributeTests passed ✓
  • ComInterfaceGenerator.Tests NativeMarshallingAttributeTests passed ✓

Issue Summary

When a COM interface has both [GeneratedComInterface] and [NativeMarshalling(typeof(UniqueComInterfaceMarshaller<IFoo>))], the generator should use the custom marshaller specified in NativeMarshallingAttribute. Previously, it always used the default ComInterfaceMarshaller<T>.

The fix ensures that when NativeMarshallingAttribute is present on a COM interface, it takes precedence over the default marshaller by ensuring the parser order checks NativeMarshallingAttributeParser before ComInterfaceMarshallingInfoProvider.

Original prompt

This section details on the original issue you should resolve

<issue_title>Interop source generators do not respect [NativeMarshalling(typeof(TMarshaller))] for COM interfaces</issue_title>
<issue_description>In the COM interface generator and LibraryImport generator, we should be able to specify [NativeMarshalling(typeof(UniqueComInterfaceMarshaller<IFoo>))] to instruct the generators to use that marshaller. However, it doesn't override the default marshaller for COM interfaces.

In the example below, the marshalling of foo parameters should use UniqueComInterfaceMarshaller, but use ComInterfaceMarshaller.

[GeneratedComInterface]
[GUID(...)]
[NativeMarshalling(typeof(UniqueComInterfaceMarshaller<IFoo>))]
internal partial interface IFoo
{
    string GetMessage();
    void DoWorkTogether(IFoo foo);
}

public static class PInvokes
{
    [LibraryImport("lib")]
    bool TryGetFoo(out IFoo foo);
}

This shouldn't require changing code outside of src/libraries/System.Runtime.InteropServices.</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Modified ComInterfaceMarshallingInfoProvider to check for NativeMarshallingAttribute on COM interfaces and defer to that parser when present. Added tests to verify the fix works for both ComInterfaceGenerator and LibraryImportGenerator.

Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com>
@jtschuster
Copy link
Member

@copilot you need to add behavioral tests to ComInterfaceGenerator.Tests.csproj. Add a test that gets the same ComInterface instance from a LibraryImportMethod twice and validate that they are not the same instance. Also add a method on the interface that returns a COM interface. The implementation should return 'this'. In the test, validate that the returned instance is a new object, not the same cached instance.

Added tests to ComInterfaceGenerator.Tests that verify:
1. Getting the same COM interface twice returns unique instances when using NativeMarshallingAttribute with UniqueComInterfaceMarshaller
2. COM interface methods returning the same interface type return new managed instances, not cached ones

Created IUniqueMarshalling interface with NativeMarshallingAttribute, native exports, and comprehensive tests.

Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 17, 2025

@copilot you need to add behavioral tests to ComInterfaceGenerator.Tests.csproj. Add a test that gets the same ComInterface instance from a LibraryImportMethod twice and validate that they are not the...

Added behavioral tests in commit 3d67609. The tests verify:

  1. GetSameComInterfaceTwiceReturnsUniqueInstances: Calls LibraryImport method twice and validates returned instances are different objects
  2. MethodReturningComInterfaceReturnsUniqueInstance: Tests a COM interface method that returns this, validates it returns a new managed object rather than the cached instance

Created IUniqueMarshalling interface with [NativeMarshalling(typeof(UniqueComInterfaceMarshaller<T>))], native exports, and test implementation.

@jtschuster
Copy link
Member

@copilot You should be able to use StrategyBasedComWrappers in UniqueMarshalling.CreateComObject without having to create a new ComWrappers implementation. Delete MyComWrapper and use StrategyBasedComWrappers instead. Create a SharedTypes.ComInterfaces.UniqueMarshalling instance instead of ImplementingObject.

Removed custom MyComWrapper implementation and ImplementingObject class. Now using StrategyBasedComWrappers directly with SharedTypes.ComInterfaces.UniqueMarshalling instance, which leverages the GeneratedComClass attribute to handle the COM vtable automatically.

Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 17, 2025

@copilot You should be able to use StrategyBasedComWrappers in UniqueMarshalling.CreateComObject without having to create a new ComWrappers implementation. Delete MyComWrapper and use StrategyBasedCom...

Simplified in commit 1a37329. Removed MyComWrapper and ImplementingObject classes. Now using StrategyBasedComWrappers directly with SharedTypes.ComInterfaces.UniqueMarshalling instance, which uses the GeneratedComClass attribute to automatically handle the COM vtable.

…parser order, not arbitrary attribute order.
@jtschuster jtschuster marked this pull request as ready for review November 17, 2025 21:17
Copilot AI review requested due to automatic review settings November 17, 2025 21:17
Updated comment to clarify that calling NewUniqueMarshalling() twice returns different managed instances for the same COM object, addressing code review feedback.

Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com>
Added IUniqueMarshalling.cs as a Compile item in LibraryImportGenerator.Tests.csproj to enable the generator to process the COM interface definition. Moved the GetSameComInterfaceTwiceReturnsUniqueInstances test to LibraryImportGenerator.Tests since it tests LibraryImport marshalling behavior. Kept MethodReturningComInterfaceReturnsUniqueInstance in ComInterfaceGenerator.Tests as it tests COM interface method behavior.

Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com>
@dotnet-policy-service
Copy link
Contributor

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

@jtschuster
Copy link
Member

@AaronRobinsonMSFT PTAL when you have a chance

@jtschuster jtschuster enabled auto-merge (squash) December 8, 2025 21:50
@jtschuster jtschuster merged commit e87fb6b into main Dec 11, 2025
93 of 95 checks passed
@github-project-automation github-project-automation bot moved this to Done in AppModel Dec 11, 2025
@jkotas jkotas deleted the copilot/fix-com-interface-marshalling branch December 22, 2025 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Runtime.InteropServices source-generator Indicates an issue with a source generator feature

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Interop source generators do not respect [NativeMarshalling(typeof(TMarshaller))] for COM interfaces

4 participants