Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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
Add proposed plan for the COM source generator #60143
Changes from 1 commit
72afee5
eff6447
fcc3d3d
aac793d
File filter
Filter by extension
Conversations
Jump to
There are no files selected for viewing
There was a problem hiding this comment.
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 usingIID
), or other functionality. Additionally, the IDL files don't have all the required contrsucts to work with the existingtlbgen.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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
orTlbExp
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?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
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
.There was a problem hiding this comment.
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
vsno base
vsIDispatch
or something else?There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 toIUnknown
based interfaces so that is where it would stop. TheIDispatch
base is interesting and I'd imagine we can reuse some existing attributes –ComInterfaceType
has baggage but could be reused in this case, ignoringInterfaceIsIInspectable
of course.There was a problem hiding this comment.
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
isnull
, the method returnsS_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
whereUnsafe.NullRef<T>()
is valid; but that has various difficulties among other things and is "traditionally" handled byout IntPtr output
orIntPtr* 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).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AaronRobinsonMSFT, there exist COM interfaces in a few scenarios that don't inherit from
IUnknown
. One example isID3DInclude
, which is simply declared usingDECLARE_INTERFACE(iface)
and so it follows the COM ABI, but is not itselfIUnknown
. 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 typeThere was a problem hiding this comment.
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. The entire
ComWrappers
API is about providingIUnknown
ABI support and removing that is, in my opinion, a non-starter as it breaks down lifetime and identity semantics fast. In fact exposing theID3DInclude
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
andID3DInclude
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?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 😄
There was a problem hiding this comment.
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:
@jkoritzinsky I'd imagine we could have users attribute types for an RCW (a la, IDIC) and/or add attributes to
static
methods marked withUnmanagedCallersOnly
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 fancyMarshal
helpers written in .NET 7 and the tool will fold them into the process naturally."