Skip to content

IComIID is not emitted on net472 / netstandard2.0 #1704

@JeremyKuhne

Description

@JeremyKuhne

Summary

When a project multi-targets net10.0 and net472 (or netstandard2.0) and references Microsoft.Windows.CsWin32,
the generator emits the IComIID interface and attaches it to every generated COM struct only on the .NET 7+ TFMs. On net472:

  1. The Windows.Win32.IComIID interface itself is not generated.
  2. Generated COM struct wrappers (e.g. IRunningObjectTable, IMoniker, IUnknown, …) do not list IComIID in their base list, so generic helpers built around where T : unmanaged, IComIID cannot accept them.

This forces consumers to hand-author two polyfills per project:

  1. An instance-based IComIID interface declaration gated #if !NET.
  2. A partial struct Foo : IComIID { Guid IComIID.Guid => IID_Guid; } entry for every generated COM struct that flows through ComScope<T>, gated #if !NET.

Each new COM type added to NativeMethods.txt requires a corresponding partial. The pattern is mechanical and lends itself to generator support.

Steps to reproduce

Create the project as described below and build it.

The net10.0 build succeeds. The net472 build fails with:

error CS0246: The type or namespace name 'IComIID' could not be found
error CS0311: The type 'IRunningObjectTable' cannot be used as type parameter 'T' in
              the generic type or method 'Program.GetIID<T>()'. There is no implicit
              reference conversion from 'IRunningObjectTable' to 'IComIID'.

Expected

CsWin32 emits IComIID and attaches it to generated COM structs on all supported target frameworks, in a shape appropriate for the language version (static-abstract on .NET 7+, instance member on older targets, matching how dotnet/winforms polyfills the type today).

Actual

The interface and its partial attachments are only emitted on TFMs where static abstract interface members are supported, leaving older targets without any substitute.

Environment

Component Version
Microsoft.Windows.CsWin32 0.3.275
.NET SDK 10.0.100
TFMs in repro net10.0;net472

How the workaround looks in production

dotnet/msbuild multi-targets net10.0 and net472 and currently maintains two hand-written polyfill files:

  • src/Framework/Polyfills/IComIID.cs — declares the instance-based IComIID interface under #if !NET.
  • src/Framework/Polyfills/IComIIDPolyfills.cs — one internal partial struct Foo : IComIID { readonly Guid IComIID.Guid => IID_Guid; } entry per generated COM type used through ComScope<T>, all under #if !NET.

Every time a new COM interface name is added to NativeMethods.txt a matching partial must be added here. This is the same workaround applied in [dotnet/winforms] https://github.com/dotnet/winforms/blob/main/src/System.Private.Windows.Core/src/Framework/Windows/Win32/System/Com/IDataObject.cs), which is referenced from msbuild's polyfill source.

References

Suggested fix

Use #if NET to generate the existing IComIID signature and #else to generate the down-level implementation. In each struct generate the implementation in the same #if blocks. The .NET (core) implementation is optimized for a pattern that pulls straight from metadata but requires .NET runtime features.

Sources

Drop these four files into a fresh folder and run dotnet build.

IComIIDMissing.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net10.0;net472</TargetFrameworks>
    <OutputType>Exe</OutputType>
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.275" PrivateAssets="all" />
    <PackageReference Include="System.Memory" Version="4.5.5" Condition="'$(TargetFramework)' == 'net472'" />
  </ItemGroup>
</Project>

NativeMethods.txt

GetRunningObjectTable
IRunningObjectTable
IMoniker
CoCreateInstance

NativeMethods.json

{ "$schema": "https://aka.ms/CsWin32.schema.json", "allowMarshaling": false, "useSafeHandles": false }

Program.cs

using System;
using Windows.Win32;
using Windows.Win32.System.Com;

internal static unsafe class Program
{
    private static int Main()
    {
        // Constraint `T : IComIID` fails to bind on net472: neither IComIID nor
        // the partial attaching it to IRunningObjectTable are generated there.
        Guid iid = GetIID<IRunningObjectTable>();
        Console.WriteLine(iid);
        return 0;
    }

    private static Guid GetIID<T>() where T : unmanaged, IComIID => T.Guid;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions