Skip to content

Emit IComIID on downlevel TFMs (net472 / netstandard2.0)#1705

Open
JeremyKuhne wants to merge 1 commit into
microsoft:mainfrom
JeremyKuhne:fix/icomiid-downlevel-1704
Open

Emit IComIID on downlevel TFMs (net472 / netstandard2.0)#1705
JeremyKuhne wants to merge 1 commit into
microsoft:mainfrom
JeremyKuhne:fix/icomiid-downlevel-1704

Conversation

@JeremyKuhne
Copy link
Copy Markdown
Member

Fixes #1704.

Summary

On target frameworks that do not support static abstract interface members (net472, netstandard2.0), CsWin32 previously skipped emitting the IComIID interface entirely and did not attach it to generated COM struct wrappers. Consumers multi-targeting (e.g. net10.0;net472) had to hand-author two polyfill files per project — an instance-form IComIID interface plus one partial struct Foo : IComIID { Guid IComIID.Guid => IID_Guid; } per generated COM type used through ComScope<T> — and add a new partial every time a COM type was added to NativeMethods.txt. See dotnet/msbuild's IComIIDPolyfills.cs and dotnet/winforms's IDataObject.cs for the workarounds in production today.

Fix

The generator now emits IComIID on every TFM. On TFMs that support static abstract interface members the existing optimized form is unchanged. On downlevel TFMs the generator emits an instance-form polyfill matching the shape used by dotnet/winforms and dotnet/msbuild byte-for-byte:

// Interface
internal interface IComIID
{
    internal ref readonly Guid Guid { get; }
}

// Per-struct attachment
internal unsafe partial struct IRunningObjectTable : IComIID
{
    internal static readonly Guid IID_Guid = new Guid(...);

    readonly ref readonly Guid IComIID.Guid
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => ref Unsafe.AsRef(in IID_Guid);
    }
}

The uniform ref readonly Guid Guid { get; } signature across both forms means a single consumer signature works on every TFM — only the static abstract modifiers differ. Adopters can delete their hand-authored polyfills entirely on upgrade.

Tests

  • New COMInterfaceIIDInterfaceOnDownlevelTFMs test asserts the interface declaration, the explicit-interface implementation, and the ref Unsafe.AsRef(in IID_Guid) body shape on net472 and netstandard2.0.
  • New IComIID_MultiTargeting_Issue1704 theory mirrors the exact NativeMethods.txt from issue IComIID is not emitted on net472 / netstandard2.0 #1704 against net472, netstandard2.0, net8.0, net9.0, and net10.0. It generates GetRunningObjectTable, IRunningObjectTable, IMoniker, CoCreateInstance, asserts every COM struct lists IComIID in its base list, and compiles a consumer snippet using where T : unmanaged, IComIID as a generic constraint on every TFM.
  • Full suite: 742 passed, 0 failed.

Manual repro verification

Built the exact repro project from #1704 (TargetFrameworks=net10.0;net472):

  • Against published 0.3.275error CS0246: 'IComIID' could not be found on net472 ✅ (reproduced)
  • Against locally-packed build of this branch → both TFMs build clean, dotnet run on net10.0 prints 00000010-0000-0000-c000-000000000046 ✅ (fixed)

Inspected the per-TFM generated output:

  • net10.0: internal static abstract ref readonly Guid Guid { get; } (unchanged optimal form)
  • net472: internal ref readonly Guid Guid { get; } + readonly ref readonly Guid IComIID.Guid => ref Unsafe.AsRef(in IID_Guid); (new)

Fixes microsoft#1704.

On TFMs that do not support static abstract interface members, CsWin32 previously skipped emitting the IComIID interface and did not attach it to generated COM struct wrappers. Consumers multi-targeting net10.0 + net472 had to hand-author per-struct partial declarations to polyfill the missing pieces.

Now the generator emits an instance-form polyfill that matches the dotnet/winforms IDataObject.cs / dotnet/msbuild IComIIDPolyfills.cs shape exactly:

  internal interface IComIID { internal ref readonly Guid Guid { get; } }

  internal partial struct Foo : IComIID { readonly ref readonly Guid IComIID.Guid { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref Unsafe.AsRef(in IID_Guid); } }

On net7.0+ the optimized static-abstract form (with the permanent-address Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(...)) pattern) is unchanged.

Adds regression tests covering net472, netstandard2.0, net8.0, net9.0, and net10.0, including a multi-targeting consumer that uses IComIID as a generic constraint on every TFM.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IComIID is not emitted on net472 / netstandard2.0

1 participant