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

compilation gives error SYSLIB1094 for a GeneratedComInterface interface deriving from an interface in another project #102602

Open
smourier opened this issue May 23, 2024 · 3 comments · May be fixed by #105119

Comments

@smourier
Copy link

smourier commented May 23, 2024

Description

I have two projects, one console app project, referencing another class library project. One interface IMy is declared in the class library, and another interface IMy3 is declared in the console app and derives from IMy

Both projects are using ComWrappers source generation feature (GeneratedComInterface).

I cannot compile the console application.

Reproduction Steps

Here the class library project:

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<TargetFramework>net8.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
		<PublishAot>true</PublishAot>
	</PropertyGroup>
</Project>

Contains only one file IMy.cs:

using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

namespace AotTest
{
    [GeneratedComInterface, Guid("68207504-314f-492a-b4f2-fbc1838e6ac3")]
    public partial interface IMy
    {
    }

    [GeneratedComInterface, Guid("80511349-dff5-4c5e-839b-af4735977ebf")]
    public partial interface IMy2 : IMy // this derived one works fine
    {
    }
}

Here's the console app project:

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>net8.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<PublishAot>true</PublishAot>
		<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
		<InvariantGlobalization>true</InvariantGlobalization>
	</PropertyGroup>

	<ItemGroup>
		<ProjectReference Include="..\AotTest\AotTest.csproj" />
	</ItemGroup>
</Project>

contains only one file Program.cs:

using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

namespace AotTest.Cli
{
    internal class Program
    {
        static void Main()
        {
            Console.WriteLine("Hello, World!");
        }
    }

    [GeneratedComInterface, Guid("755293e8-fe80-4654-aee3-732fefaaf3e0")]
    public partial interface IMy3 : IMy
    {
    }
}

Expected behavior

I don't see why it shouldn't compile, I hope this simple scenario is supported.

Actual behavior

I get compilation error:

1>------ Build started: Project: AotTest, Configuration: Debug Any CPU ------
1>AotTest -> E:\smo\work\AotTest\AotTest\bin\Debug\net8.0\AotTest.dll
2>------ Build started: Project: AotTest.Cli, Configuration: Debug Any CPU ------
2>E:\smo\work\AotTest\AotTest.Cli\Program.cs(15,30,15,34): error SYSLIB1094: COM interface AotTest.Cli.IMy3 inherits from AotTest.IMy, which has errors. ComInterfaceGenerator will not generate source for AotTest.Cli.IMy3. (https://learn.microsoft.com/dotnet/fundamentals/syslib-diagnostics/syslib1094)
2>Done building project "AotTest.Cli.csproj" -- FAILED.
========== Build: 1 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

Also, independently from the SYSLIB1094 error, Roslyn seems to crash (but not always, seems more a background thing) this is what I see (sometimes) in Visual Studio and it seems somehow related (I have a bigger project where these top error lines popup continuously):

image

Here is the stack trace from the first error line:

StreamJsonRpc.RemoteInvocationException: SyntaxTree is not part of the compilation (Parameter 'syntaxTree')
   at StreamJsonRpc.JsonRpc.<InvokeCoreAsync>d__154`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.CodeAnalysis.Remote.BrokeredServiceConnection`1.<TryInvokeAsync>d__20`1.MoveNext()
RPC server exception:
System.ArgumentException: SyntaxTree is not part of the compilation (Parameter 'syntaxTree')
      at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetSemanticModel(SyntaxTree syntaxTree, SemanticModelOptions options)
      at Microsoft.CodeAnalysis.Diagnostics.SuppressMessageAttributeState.IsDiagnosticSuppressed(Diagnostic diagnostic, SuppressMessageInfo& info)
      at Microsoft.CodeAnalysis.Diagnostics.SuppressMessageAttributeState.ApplySourceSuppressions(Diagnostic diagnostic)
      at Microsoft.CodeAnalysis.GeneratorDriver.FilterDiagnostics(Compilation compilation, ImmutableArray`1 generatorDiagnostics, DiagnosticBag driverDiagnostics, CancellationToken cancellationToken)
      at Microsoft.CodeAnalysis.GeneratorDriver.RunGeneratorsCore(Compilation compilation, DiagnosticBag diagnosticsBag, CancellationToken cancellationToken)
      at Microsoft.CodeAnalysis.GeneratorDriver.RunGenerators(Compilation compilation, CancellationToken cancellationToken)
      at Microsoft.CodeAnalysis.SolutionCompilationState.CompilationTracker.ComputeNewGeneratorInfoInCurrentProcessAsync(ISourceGeneratorTelemetryCollectorWorkspaceService telemetryCollector, Compilation compilationWithoutGeneratedFiles, TextDocumentStates`1 oldGeneratedDocuments, GeneratorDriver generatorDriver, Compilation compilationWithStaleGeneratedTrees, CancellationToken cancellationToken)
      at Microsoft.CodeAnalysis.SolutionCompilationState.CompilationTracker.ComputeNewGeneratorInfoAsync(SolutionCompilationState compilationState, Compilation compilationWithoutGeneratedFiles, TextDocumentStates`1 oldGeneratedDocuments, GeneratorDriver oldGeneratorDriver, Compilation compilationWithStaleGeneratedTrees, CancellationToken cancellationToken)
      at Microsoft.CodeAnalysis.SolutionCompilationState.CompilationTracker.AddExistingOrComputeNewGeneratorInfoAsync(CreationPolicy creationPolicy, SolutionCompilationState compilationState, Compilation compilationWithoutGeneratedFiles, CompilationTrackerGeneratorInfo generatorInfo, Compilation compilationWithStaleGeneratedTrees, CancellationToken cancellationToken)
      at Microsoft.CodeAnalysis.SolutionCompilationState.CompilationTracker.<>c__DisplayClass23_0.<<GetOrBuildFinalStateAsync>g__FinalizeCompilationWorkerAsync|4>d.MoveNext()
   --- End of stack trace from previous location ---
      at Microsoft.CodeAnalysis.SolutionCompilationState.CompilationTracker.<>c__DisplayClass23_0.<<GetOrBuildFinalStateAsync>g__FinalizeCompilationAsync|3>d.MoveNext()
   --- End of stack trace from previous location ---
      at Microsoft.CodeAnalysis.SolutionCompilationState.CompilationTracker.<>c__DisplayClass23_0.<<GetOrBuildFinalStateAsync>g__BuildFinalStateAsync|0>d.MoveNext()
   --- End of stack trace from previous location ---
      at Microsoft.CodeAnalysis.SolutionCompilationState.CompilationTracker.GetOrBuildFinalStateAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken)
      at Microsoft.CodeAnalysis.SolutionCompilationState.CompilationTracker.GetSourceGeneratedDocumentStatesAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken)
      at Microsoft.CodeAnalysis.Remote.RemoteSourceGenerationService.<>c__DisplayClass2_0.<<GetSourceGenerationInfoAsync>b__0>d.MoveNext()
   --- End of stack trace from previous location ---
      at Microsoft.CodeAnalysis.Remote.RemoteWorkspace.<>c__DisplayClass8_0`1.<<RunWithSolutionAsync>g__ProcessSolutionAsync|1>d.MoveNext()
   --- End of stack trace from previous location ---
      at Microsoft.CodeAnalysis.Remote.RemoteWorkspace.RunWithSolutionAsync[T](AssetProvider assetProvider, Checksum solutionChecksum, Int32 workspaceVersion, Boolean updatePrimaryBranch, Func`2 implementation, CancellationToken cancellationToken)
      at Microsoft.CodeAnalysis.Remote.RemoteWorkspace.RunWithSolutionAsync[T](AssetProvider assetProvider, Checksum solutionChecksum, Int32 workspaceVersion, Boolean updatePrimaryBranch, Func`2 implementation, CancellationToken cancellationToken)
      at Microsoft.CodeAnalysis.Remote.BrokeredServiceBase.RunWithSolutionAsync[T](Checksum solutionChecksum, Func`2 implementation, CancellationToken cancellationToken)
      at Microsoft.CodeAnalysis.Remote.BrokeredServiceBase.RunServiceImplAsync[T](Func`2 implementation, CancellationToken cancellationToken)

Regression?

I don't tink so but I can't be sure.

Known Workarounds

I don't have any.

Configuration

.NET 8.0.5, Visual Studio 17.10.0 (latest), Windows 11 x64

Here's a reproducing solution
AotTest.zip

Other information

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label May 23, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label May 23, 2024
@jkotas jkotas added area-System.Runtime.InteropServices and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels May 23, 2024
Copy link
Contributor

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

@agocke agocke added this to the 9.0.0 milestone Jul 9, 2024
@agocke agocke removed the untriaged New issue has not been triaged by the area owner label Jul 9, 2024
@jkoritzinsky
Copy link
Member

We can't reliably support cross-project COM interface inheritance as we can't determine vtable offsets won't change.

Let's take the following example:

A.dll has the following type:

    [GeneratedComInterface, Guid("68207504-314f-492a-b4f2-fbc1838e6ac3")]
    public partial interface IMy
    {
           void MyMethod();
    }

B.dll has the following source code and references A.dll:

    [GeneratedComInterface, Guid("755293e8-fe80-4654-aee3-732fefaaf3e0")]
    public partial interface IMy3 : IMy
    {
          void MyMethod3();
    }

At build time, A.dll's IMy.MyMethod would take slot 3 and B.dll's IMy3.MyMethod3 would take slot 4.

But, if the developer of A.dll updates IMy to include another method, as follows, without recompiling B.dll:

    [GeneratedComInterface, Guid("68207504-314f-492a-b4f2-fbc1838e6ac3")]
    public partial interface IMy
    {
           void MyMethod();
           void MyMethod2();
    }

Then IMy.MyMethod2() would take slot 4.

However, B.dll's IMy3.MyMethod3 would still take slot 4 until B.dll is recompiled.

It is a breaking change to add methods without default implementations to .NET interfaces and it is a breaking change to add methods to a COM interface (which is why we have things like ID3D11Device5), but for usages entirely internal to a company, these sorts of breaks are not entirely uncommon.

As a result, we have decided initially to not support cross-assembly inheritance for COM interfaces.

The exception in VS shouldn't be happening though, so I'll definitely investigate that. Once that's fixed, I'll keep this open as a tracking issue for supporting cross-assembly COM inheritance.

@smourier
Copy link
Author

Hi,

I know some people have always been giving that "adding a method is kinda ok" hack advice, but still, changing (adding or removing methods on a COM interface) is not only a breaking change, it's disallowed and has always very clearly been https://learn.microsoft.com/en-us/windows/win32/com/interface-pointers-and-interfaces:

COM interfaces are immutable. You cannot define a new version of an old interface and give it the same identifier. Adding or removing methods of an interface or changing semantics creates a new interface, not a new version of an old interface.

So, you decide to disallow a perfectly valid (and common) COM usage to enable a perfectly invalid one? That seems a strange decision IMHO (and kinda contradicts the way you handle what could be considered a "niche feature" like in this other thread #101242 althought here, adding a method is not even a feature, it's a hack).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment