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 proposed plan for the COM source generator #60143

Merged
merged 4 commits into from
Feb 23, 2022
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
381 changes: 381 additions & 0 deletions docs/design/features/source-generator-com.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,381 @@
# Source Generator COM

The interop team has decided to invest some time into creating a source generator to help support COM scenarios. The basic goals of this work are as follows, in general order of importance:

1. Enable developers who use MCG to be able to move to the new source generator.
2. Enable developers to interoperate with COM without needing to use linker-unsafe code (the built-in system is linker unsafe)
3. Enable WinForms to move to the source generated COM support to help make it NativeAOT-compatible.
4. Generate .NET 6-compatible code for internal partners

This plan includes checkpoints for when we complete the different goals above to help guide team planning. The order of checkpoints below is primarily of convenience; some of them may be implemented in a different order than provided.

## Architecture

The COM Source Generator should be designed as a Roslyn source generator that uses C# as the "source of truth". By using C# as the source of truth, we can use Roslyn's rich type system to inspect the various parameter and return types and determine the correct marshalling mechanism. Additionally, we would not have to encode policy for mapping non-.NET types to .NET types, which can be an error prone and opinionated process.

COM has a platform-agnostic source of truth with IDL files and TLBs (type libraries). We propose that conversions from these files to a C# source of truth that the COM source generator can consume should be implemented by .NET CLI tools. These tools can be manually invoked before a build if the source of truth changes rarely, or they can be included in the build pipeline for a project to produce the C# source of truth at build-time. Alternatively, we can provide some of the hooks for the COM source generator as an API, and another source generator that reads IDL or TLB files from the AdditionalFiles collection could re-use the core of the COM source generator to generate similar code directly from the IDL or TLBs. Since Roslyn source generators cannot have dependencies on other generators, we cannot sequence both an IDL/TLB to C# generator and a C# to C# generator together.
Copy link
Member

Choose a reason for hiding this comment

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

Just noting that while COM supports IDL and TLB, it is not the case that all COM starts out from IDL or TLB. There are a few cases of COM even in the Windows SDK (D2D1, for example) which only exist as C++ header files.

There are also cases, like most of DirectX, where its not the type of IDL/TLB that you find in "traditional COM". Instead, it uses what is sometimes referred to as "nano COM" which is a limited subset and which doesn't use CoTaskMemAlloc, CLSID (only using IID), or other functionality. Additionally, the IDL files don't have all the required contrsucts to work with the existing tlbgen.exe or related tooling that exists on Full Framework.

I think we should ensure that any tooling here can properly account for and handle these types of scenarios.

  • https://github.com/microsoft/clangsharp currently achieves this non-source generator based support by simply parsing and traversing the C/C++ AST, and while it can't handle everything that real C++ supports; it does support effectively all the COM constructs you'd encounter in nano-COM and several of the constructs from full COM.

Copy link
Member Author

Choose a reason for hiding this comment

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

Due to the issues with making IDL/TLB the source of truth (as you mention), the proposed plan is to leave TLB/IDL consumption and production for later work and develop a solution that uses a C# source of truth first. That way, projects like ClangSharp could produce C# that the source generator can consume even when the source of truth isn't IDL or a TLB.

Copy link
Member

Choose a reason for hiding this comment

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

I would prefer to avoid providing any additional tooling beyond what TlbImp or TlbExp could provide. Parsing C/C++ headers looks to be well covered by the clangsharp tool and I'd argue that tool should continue to do that. In this case the proposed source generator would be able to consume the output of clangsharp and treat that as the source of truth. What would be the benefit of supplanting this tool or am I missing some nuance here?

Copy link
Member

Choose a reason for hiding this comment

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

What would be the benefit of supplanting this tool or am I missing some nuance here?

That was somewhat my point. This section discusses IDL and TLB files and conversions from them to something that the source generator can handle.

However, they are not always available or usable and there exists other tooling that could already generate the required "stuff". ClangSharp for example already generates the full VTBLs and helper methods required to support COM today: https://source.terrafx.dev/#TerraFX.Interop.Windows/um/d3d12/ID3D12Device8.cs,56ac150c61296fc3

It just doesn't have the "stuff" to connect these raw COM views into the COM Wrappers support.

Copy link
Member Author

Choose a reason for hiding this comment

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

@AaronRobinsonMSFT thats exactly what my proposal is.

We as the runtime team would provide the “C# source of truth” generator.

Either our team or someone else would provide the TlbImp/Exp replacement, which could generate C# that our generator reads in.

ClangSharp (or another community project) would provide the “C++ to C#” tool that would generate code that our generator reads in to do the work.

The only work we need to do is the “C# source of truth” generator.

Copy link
Member

Choose a reason for hiding this comment

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

they are not always available or usable and there exists other tooling that could already generate the required "stuff"

I don't think I am getting the point here. The current TlbImp tools generate things that may not work with this if we use new attributes and to some degree we would like to have a single assembly that would contain both definition and goo - should be possible to not have that but that would ideally be P0 given the MCG replacement goal. Consuming a TLB as the source of truth is the canonical way for COM interop so that is the primary focus. DirectX scenarios are P2 at this point but once something has converted those definitions into C# we could consume that and clangsharp seems to be able to handle that so I am back to being confused I think. Is the point here that tools exist that already do part of the job?

The TlbExp tooling however does need a full replacement so that would be another reason TLB is called out.


> Open Question: Do we need a mechanism for cross-assembly compatibility? Specifically, given a COM object `c` that was created in assembly A, is casting `c` to a COM interface defined in assembly B (which A does not reference) a supported scenario?
> Providing this feature will require at least some shared types. I explored using type equivalence as a workaround here, but it doesn't work on non-COM interfaces, which would likely provide difficulties for using it to avoid shared types.

### Checkpoint 1: IUnknown compatibility

One of the primary use cases of MCG today is to provide a basic level of COM interop support on non-Windows platforms. Specifically, it provides enough of a compatibility layer to enable IUnknown-style ABIs to function as seamlessly as the built-in COM interop system. It does not enable IDispatch support, and none of the internal MCG customers use COM aggregation with MCG on non-Windows platforms. Additionally, COM activation is only well-defined on Windows, so it is also not applicable for non-Windows platforms.

As a result, to satisfy our first goal, we only need to provide basic support for IUnknown-style ABIs. We will use the `ComWrappers` and `IDynamicInterfaceCastable` APIs to create implementations of the provided COM interfaces. Since COM Activation is not supported, users will need to manually activate their COM objects or retrieve pointers to them in another fashion, and then use `ComWrappers.GetOrCreateObjectForComInstance` to create a wrapper object.

### Checkpoint 2: COM-focused marshaller types

Many COM APIs use types within the COM ecosystem to pass across the COM boundary.Many of these types, like `VARIANT`, `BSTR`, and `SAFEARRAY`, have built-in support in the runtime today. We should provide custom marshallers following the patterns defined in the custom type marshalling design in DllImportGenerator to implement these conversions. Users will be able to manually opt-into using them with the `MarshalUsingAttribute`.

We should also provide a marshaller that uses the generated `ComWrappers` type to easily enable marshalling a COM interface in method calls to another COM interface.

> Open Question: Do we want to enable using the `MarshalAsAttribute` as well for these types?
> This would require us to hard-code in support for these marshallers into the COM source generator, but would provide better backward compatibility and easier migration.
Copy link
Member

Choose a reason for hiding this comment

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

Migration might not be so bad depending on the users in question. I'd like to start with the premise the defaults, unadorned arguments, be the most common which results in COM interface definitions being void of MarshalAs.


### Checkpoint 3: Activation support

Currently, .NET provides some mechanisms to more easily support COM Activation. Although this feature is unused in MCG-like scenarios, it may be used in WinForms scenarios and other internal customer scenarios we are targeting. We should consider providing some support similar to the built-in support to streamline the "activate a COM object and get a .NET wrapper object" workflow.

### Checkpoint 4: IDispatch compatibility

Many APIs used in UI scenarios, in particular WinForms and Office APIs, use the late-binding provided by the IDispatch interface. To successfully support WinForms in full with a COM source generator solution, we will need to support IDispatch-based APIs.

> Open Question: How do we plan on supporting "Variants containing COM records"? Is this something that is required for supporting WinForms?

We do not plan on supporting `IDispatch` integration with C# `dynamic`, at least for the first release of the COM source generator. Although the built-in system currently supports it, the integration is primarily used with the PIAs provided for Office, which we do not plan on regenerating with this tooling. Additionally, System.Text.Json just backed out their `dynamic` integration for .NET 6.0, so we should consider following suit unless we get strong feedback otherwise. In any case, we should be sure to design the integration in a trimmable manner if possible to reduce overhead.

### Checkpoint 5: .NET 6-compatible output

A very important component of source generators is determining how to trigger them. For the DllImportGenerator, we trigger on a new attribute type, `GeneratedDllImportAttribute`, that is applied in place of the previous `DllImportAttribute`. For the JSON source generator, the team decided to have developers define an empty `JsonSerializerContext`-derived class and add `JsonSerializableAttribute` attribute on that context type that each point to a type that the generated serialization context should support. Below I've included some of the ideas that we've had for potential designs:

#### Option 1: Annotated ComWrappers stub

Option 1 is a similar design to the JSON generator, where the user defines a stub derived from a well-defined type and attributes it:

```csharp
// UserProvided.cs

[Guid("4b69d271-5c99-4f95-b1eb-381e6e689f1a")]
interface IMyComInterface
Copy link
Member

Choose a reason for hiding this comment

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

Is there an example of what an a more complex inheritance hierarchy might look like?

Will users continue needing to "duplicate" base interface members with the new slot keyword like with the built-in COM support?

How do you indicate this is IUnknown vs no base vs IDispatch or something else?

Copy link
Member Author

Choose a reason for hiding this comment

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

I will add some examples for these scenarios in my next iteration.

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure what no base means in this case because without it what is being generated? These tooling is specific to IUnknown based interfaces so that is where it would stop. The IDispatch base is interesting and I'd imagine we can reuse some existing attributes – ComInterfaceType has baggage but could be reused in this case, ignoring InterfaceIsIInspectable of course.

Copy link
Member

Choose a reason for hiding this comment

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

Likewise, what's the expected model for common patterns where managed objects (e.g. interfaces) don't translate cleanly to the native signature?

For example, there are often APIs that take the form of HRESULT M(..., void** ppOutput);

If ppOutput is null, the method returns S_FALSE if the passed in parameters would have allowed the method to succeed (it acts effectively as a validation method without allocating or doing other costly internal operations).

This means the signature is effectively out T output where Unsafe.NullRef<T>() is valid; but that has various difficulties among other things and is "traditionally" handled by out IntPtr output or IntPtr* output where you lose all real safety and support, so you aren't significantly "better off" than if you were simply using the underlying vtbls in the first place (outside of basic lifetime tracking).

Copy link
Member

@tannergooding tannergooding Oct 8, 2021

Choose a reason for hiding this comment

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

I'm not sure what no base means in this case because without it what is being generated? These tooling is specific to IUnknown based interfaces so that is where it would stop. The IDispatch base is interesting and I'd imagine we can reuse some existing attributes – ComInterfaceType has baggage but could be reused in this case, ignoring InterfaceIsIInspectable of course.

@AaronRobinsonMSFT, there exist COM interfaces in a few scenarios that don't inherit from IUnknown. One example is ID3DInclude, which is simply declared using DECLARE_INTERFACE(iface) and so it follows the COM ABI, but is not itself IUnknown. This interface is expected to be implemented by the consumer and passed into a consuming function provided by the library.

The tooling should likely not break down in the face of such an interface declaration; regardless of whether the user implements it alongside IUnknown on the derived type

Copy link
Member

@AaronRobinsonMSFT AaronRobinsonMSFT Oct 8, 2021

Choose a reason for hiding this comment

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

there exist COM interfaces in a few scenarios that don't inherit from IUnknown.

That isn't a COM interface then – by definition. The entire ComWrappers API is about providing IUnknown ABI support and removing that is, in my opinion, a non-starter as it breaks down lifetime and identity semantics fast. In fact exposing the ID3DInclude type seems like a "dead-end" because you can't go anywhere from it.

I guess one could attempt to use virtual inheritance to inherit from both IUnknown and ID3DInclude to "unify" them but I don't know if the order of that is actually defined so how would we enable this in a stable way? What am I missing?

Copy link
Member

Choose a reason for hiding this comment

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

That isn't a COM interface then – by definition.

Sorry, that was a misstatement by me. I meant "there exist interfaces in a few COM related scenarios".

In particular I'm just trying to call out some scenarios that I think are important to consider, at least from one aspect. I don't think they have to be handled the same way or even at all, as long as they are thought about and a decision is made about them.

I just struggle with finding the right words sometimes 😄

Copy link
Member

@AaronRobinsonMSFT AaronRobinsonMSFT Oct 8, 2021

Choose a reason for hiding this comment

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

@tannergooding I think this conversation revolves around two principles – (1) let's make sure we support as many scenarios as possible compared to the build-in and (2) how can we ensure our source gen provides maximum benefit for the .NET community. I agree with these principles and I think the tenor of the initial questions demonstrates a similar thinking. Instead of focusing on concrete examples like ID3DInclude, I'll try to adhere to the above principles.

The scenario above, I am sure there are others, demonstrates that we going to fail to please everyone. Therefore in an effort to bring (1) closer to reality we will need to be permissive in some manner. Principle (2) dictates saying "You can build your own source generator from these N assemblies" or "your special cases will require you to perform unnatural build system hacks" aren't acceptable. So, I propose the following:

  • We provide a way for users to define their own manually written/defined wrappers (RCW/CCW) for an interface. These would then be discoverable through some attribute or other gesture by the source generator. This would allow the majority of people to benefit from the simple well-defined COM that is likely the common support scenario (for example, WinForms) and at the same time permit special cases to be addressed while not degrading the overall experience if a small minority of types are an issue.

@jkoritzinsky I'd imagine we could have users attribute types for an RCW (a la, IDIC) and/or add attributes to static methods marked with UnmanagedCallersOnly as to where in a CCW generated vtable they wanted to place the method. There are lots of options here but in the end we can focus on typical COM support circa 2002 but permit anyone to say "I want the tool to generate 99 % of my interfaces, but these 2 I will define myself using all the fancy Marshal helpers written in .NET 7 and the tool will fold them into the process naturally."

{
void Foo();
}

[Guid("cb95a067-10f6-41cc-bef5-946aa018eb29")]
interface IMyOtherComInterface
{
void Baz();
}

[GenerateComWrapperFor(typeof(IMyComInterface))]
[GenerateComWrapperFor(typeof(IMyOtherComInterface))]
partial class MyComWrappers : ComWrappers
{
}
```

```csharp
// Generated.g.cs
partial class MyComWrappers : ComWrappers
{
protected override ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count)
{
if (obj is IMyComInterface)
{
// ...
}
if (obj is IMyOtherComInterface)
{
// ...
}
count = 0;
return null;
}

protected override object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags)
{
return new ComObject(externalComObject);
}

private class ComObject : IDynamicInterfaceCastable
{
private IntPtr iMyComInterface;
private IntPtr iMyOtherComInterface;

public ComObject(IntPtr externalComObject)
{
// ...
}
}

[DynamicInterfaceCastableImplementation]
private interface IMyComInterfaceImpl : IMyComInterface
{
// ...
}

[DynamicInterfaceCastableImplementation]
private interface IMyOtherComInterfaceImpl : IMyOtherComInterface
{
// ...
}

public static readonly MyComWrappers Instance = new();

public struct Marshaller<T>
{

}
}
```

In this model, the only attributes required are the built-in `GuidAttribute` on the interface, and a new `GenerateComWrappersForAttribute` which would be inserted into the compilation with the "post initialization sources" functionality in a source generator.

Pros:

- The user only has to annotate the ComWrappers type to enable the source generation.
- The ComWrappers type exists in the user's source.
- As the user can define multiple attributed ComWrappers-derived types, this design can be a little more linker optimized as it won't have type checks for every COM interface in the assembly.
- Well grouped sets of interfaces might all be able to be linked out completely.

Cons:

- This design with the runtime wrapper object being defined internally makes using the same runtime wrapper object between multiple attributed `ComWrappers`-derived types difficult as the wrappers are completely distinct types, even within the same assembly.
- This design causes difficulties with automatic integration with the "custom type marshalling" design used with DllImportGenerator as it becomes difficult to determine how to tie an interface to a ComWrappers that implements it.
- What if two different `ComWrappers`-derived types have a `GenerateComWrappersForAttribute` that point to the same interface? Which one do we use for default marshalling?
- There is no mechanism in this design to determine which interfaces to "export" with a C# -> TLB tool (such as a new implementation of TlbExp)

#### Option 2: `GeneratedComImportAttribute` and `GeneratedComVisibleAttribute`

Option 2 has more parallels to the designs of the DllImportGenerator and the proposed design for custom native type marshalling. The developer would use the `GeneratedComImportAttribute` or the `GeneratedComVisibleAttribute` on their defined interfaces, and the source generator would generate a `ComWrappers`-derived type that handles all of the annotated interfaces. The name of this `ComWrappers` type would be supplied by an analyzer config option, possibly provided through MSBuild.

```csharp
// UserProvided.cs

[GeneratedComImport]
[Guid("4b69d271-5c99-4f95-b1eb-381e6e689f1a")]
partial interface IMyComInterface
{
void Foo();
}

[GeneratedComVisible]
[Guid("cb95a067-10f6-41cc-bef5-946aa018eb29")]
partial interface IMyOtherComInterface
{
void Baz();
}
```

```xml
<!-- UserProvided.csproj -->
<PropertyGroup>
<CsComGeneratedComWrappersName>MyComWrappers</CsComGeneratedComWrappersName>
</PropertyGroup>
```

```csharp
// Generated.g.cs

[NativeMarshalling(typeof(MyComWrappers.Marshaller<IMyComInterface>))]
partial interface IMyComInterface {}

[NativeMarshalling(typeof(MyComWrappers.Marshaller<IMyOtherComInterface>))]
partial interface IMyOtherComInterface {}

partial class MyComWrappers : ComWrappers
{
protected override ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count)
{
if (obj is IMyComInterface)
{
// ...
}
if (obj is IMyOtherComInterface)
{
// ...
}
count = 0;
return null;
}

protected override object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags)
{
return new ComObject(externalComObject);
}

private class ComObject : IDynamicInterfaceCastable
{
private IntPtr iMyComInterface;
private IntPtr iMyOtherComInterface;

public ComObject(IntPtr externalComObject)
{
// ...
}
}

[DynamicInterfaceCastableImplementation]
private interface IMyComInterfaceImpl : IMyComInterface
{
// ...
}

[DynamicInterfaceCastableImplementation]
private interface IMyOtherComInterfaceImpl : IMyOtherComInterface
{
// ...
}

public static readonly MyComWrappers Instance = new();

public struct Marshaller<T>
{

}
}
```

Pros:

- Similar experience to the `GeneratedDllImportAttribute`, where it basically replaces its built-in equivalent as a drop-in.
- Very easy to automatically hook up generated marshalling and to provide an easy process for other source generators to duplicate to support side-by-side as the policy is very simple.
- Since we only generate a single `ComWrappers`-derived type, we could also decide to make the `ComObject` type public for .NET 7+ scenarios and make it private for .NET 6 scenarios as we know there will only ever be one.
- The `GeneratedComImportAttribute` and `GeneratedComVisibleAttribute` attributes mirror the existing `ComImportAttribute` and `ComVisibleAttribute`, which will help provide a more intuitive view of the types and how to hook in tools that process C# -> TLB or TLB -> C# into the generator's flow.

Cons:

- This implementation may be slightly less linker-friendly as the single ComWrappers implementation will reference all annotated interfaces.
- The `ComWrappers`-derived type is not defined by the user in their source, instead being generated from other inputs.

#### Option 3: Annotated `ComWrappers`-derived type and `GeneratedComImportAttribute`/`GeneratedComVisibleAttribute`

In this design, the user would both annotate a `ComWrappers`-derived type and annotate the interfaces themselves.

```csharp
// UserProvided.cs

[GeneratedComImport]
[Guid("4b69d271-5c99-4f95-b1eb-381e6e689f1a")]
partial interface IMyComInterface
{
void Foo();
}

[GeneratedComVisible]
[Guid("cb95a067-10f6-41cc-bef5-946aa018eb29")]
partial interface IMyOtherComInterface
{
void Baz();
}

[GenerateComWrapperFor(typeof(IMyComInterface))]
[GenerateComWrapperFor(typeof(IMyOtherComInterface))]
partial class MyComWrappers : ComWrappers
{
}
```

```csharp
// Generated.g.cs
partial interface IMyComInterface {}

partial interface IMyOtherComInterface {}

partial class MyComWrappers : ComWrappers
{
protected override ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count)
{
if (obj is IMyComInterface)
{
// ...
}
if (obj is IMyOtherComInterface)
{
// ...
}
count = 0;
return null;
}

protected override object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags)
{
return new ComObject(externalComObject);
}

private class ComObject : IDynamicInterfaceCastable
{
private IntPtr iMyComInterface;
private IntPtr iMyOtherComInterface;

public ComObject(IntPtr externalComObject)
{
// ...
}
}

[DynamicInterfaceCastableImplementation]
private interface IMyComInterfaceImpl : IMyComInterface
{
// ...
}

[DynamicInterfaceCastableImplementation]
private interface IMyOtherComInterfaceImpl : IMyOtherComInterface
{
// ...
}

public static readonly MyComWrappers Instance = new();

public struct Marshaller<T>
{

}
}
```

Pros:

- Similar experience to the `GeneratedDllImportAttribute`, where it basically replaces its built-in equivalent as a drop-in.
- The `GeneratedComImportAttribute` and `GeneratedComVisibleAttribute` attributes mirror the existing `ComImportAttribute` and `ComVisibleAttribute`, which will help provide a more intuitive view of the types and how to hook in tools that process C# -> TLB or TLB -> C# into the generator's flow.

Cons:

- Multiple attributes would be required to actually trigger the code generation. There would be more scenarios that require error diagnostics.
- This design with the runtime wrapper object being defined internally makes using the same runtime wrapper object between multiple attributed `ComWrappers`-derived types difficult as the wrappers are completely distinct types, even within the same assembly.
- This design causes difficulties with automatic integration with the "custom type marshalling" design used with DllImportGenerator as it becomes difficult to determine how to tie an interface to a ComWrappers that implements it.
- What if two different `ComWrappers`-derived types have a `GenerateComWrappersForAttribute` that point to the same interface? Which one do we use for default marshalling?

#### Option 4: Reuse built-in COM attributes with Generated `ComWrappers`-derived type

The built-in COM interop system doesn't enforce usage only with COM scenarios, so one option would be to re-use all existing attributes, `ComImportAttribute`, `ComVisibleAttribute`, `CoClassAttribute`, etc. to act as triggers for the source generator. Then, using either a model from Option 2 or 3 to define the `ComWrappers`-derived type, the generator would generate all the support code for using these COM interfaces without using the built-in system.

Pros:

- Can use de-compiled TlbImp-generated COM Interop code with minimal changes.
- Can be designed such that no new types are required to ship with the generator or that the generator must add to the compilation.

Cons:

- Some changes from the TlbImp-generated code would still be required to avoid falling into the built-in system.
- We wouldn't be able to easily fix the warts of the existing system because that would break possible back-compat.
- The runtime might have cases where it makes assumptions based on how types are marked that this may interact with.
- It would be much easier to accidentally use the old system and get in a place where 2 objects, one from the built-in system and one from the generated system, both represent the same native object.

#### Option 5: Something Else?

I'm open to any other ideas people have on how to trigger the generator.

### Checkpoint 6: Aggregation support

COM supports a concept of aggregation, which the built-in .NET COM system supports. We currently don't have plans to support aggregation in the COM source generator, but we take care to avoid designing ourselves into a corner where implementing support is difficult if we decide to support it.

### Checkpoint 7: COM Event support

COM with IDispatch has a pattern that supports events. Supporting COM "events" requires quite a bit of support code, so we should consider only providing support if and when a feature request comes in for it, and possibly not supporting it in a .NET 6 compatibility mode where all support code needs to be source-included in the assembly.

### Extra: Analyzers to help write COM Interop code

The built-in COM system has quite a few gotchas and rough corners. We should consider writing some analyzers to assist developers with writing their COM-interop APIs/interfaces. If we decide to implement the COM source generator by using the built-in COM attributes as our triggering mechanism, then we could ship these analyzers with the generator since their diagnostics would apply to both the built-in and source-generated scenarios.