Pitch: A vision for COM Interoperability in Swift

Yeah, Span is something that will likely get used, and particularly for sized arrays, it could be the right choice.

This layered approach seems reasonable to me, I looked a little bit into COM and it is very specific in how it does things while it will be possible to but a Distributed runtime wrapper on top of this, I think I agree this is a much higher level abstraction, and we don't need to marry the two things :+1:

Good luck making the groundwork here, I look forward to it!

Really excited to see this work happening!

I'm not sure I understand the necessity of a separate generated header. Windows users can already generate C++ module interfaces for Swift modules using C++ interop's machinery. Can the existing header generation mechanism be extended to support COM?

What would be the usage scenario for disabling this flag on Windows? Is this a configuration worth supporting?

+1. There should really only be a single notion of "emit a C-language-family header" and the compiler just fills it with whatever makes sense based on the Swift code. If you've got @c stuff, you get some C decls. @objc, you get some Obj-C decls. If using C++ interop you get that stuff. So it would make sense to have the compiler just emit the COM decls by default into that same file as well.

-emit-clang-header-path was added as an alias for -emit-objc-header-path rather than a separate flag altogether, so it seems like we already have the flags we need here.

2 Likes

I don't have anything technical to add at this point, but I am super excited to see this moving forward. It's a great path towards making Swift a first class language in the Windows ecosystem.

I am keeping an eye on Swift Package requirements that come out of this. We should be able to product build artifacts on each side of the interface.

5 Likes

COM uses a separate file written in a format called IDL. Shouldn’t the Swift compiler be generating that too?

Edit: I see that is mentioned as a future direction… but so is header generation:

Surely at least one of these is necessary for phase 1? Either directly generating the C++ header (which shuts out non-C++-interoperable languages) or an IDL file (which MIDL or other tools can turn into a header file or equivalent for your language).

Good point; I haven't used COM in a couple decades so my memory is definitely rusty about the details. (And even then I mostly just consumed existing COM interfaces, not defined my own.)

This makes me wonder, how much of this work needs to be added as custom special cases in the compiler itself vs. a tool/plug-in-based approach like swift-java is doing?

Could this vision be motivation to extend the macro system beyond simple syntactic transforms so that it doesn't have to be implemented deep in the compiler?

I understand that it's probably easier to implement this in the compiler today since it doesn't require building out generalized machinery, but it's a bit unsatisfying to have these interop cases coming up that have to keep needing deep compiler integration. Can we find ways of splitting the difference so that we build any runtime support directly that we might need but other tooling could deal with things like interface generation?

1 Like

The design notes will clarify that - but the point is that this really is the minimal amount of work that has to be done in the compiler and there is no way around that. We need to change the object layout (aka %refcounted in IRGen) will be altered, and we need to emit additional data tables. There is no way to do this outside of the compiler that I know of. We need to change the shape of swift_retain and swift_release which the compiler has intrinsic knowledge about. I don't think that macros are going to be a solution here. However, if you note, the WinRT layer is a macro based approach because I currently do not see the need for such invasive changes there.

2 Likes

What is swift-java doing, in fact? Does it parse .swiftinterface and/or Swift source in a separate build step?

I think this is great (but you already know that because we've been talking about it).

Agreed, I think this would be a useful thing to do. I had actually been thinking that we should prefix these with ISwiftObject methods (which, because it has to, extends Unknown). We don't really need a separate IUnknown vtable distinct from ISwiftObject.

Just to get the bikeshedding out of the way, I think we should spell this @com, not @COM, and iid: rather than IID:. A stanza like @COM(IID: "3887CABF-07FE-48F3-AB07-320BA348B445") seems entirely too shouty. Also, there is precedent in that we have @objc not @ObjC.

Also, in the "Future directions" section, I think we should add

Rich interface import

Direct import of MIDL IDL and .winmd metadata into the Swift compiler, the advantage being that IDL and .winmd contain additional, rich, metadata that isn't expressed in C++ COM declarations and that the compiler will have to infer using heuristics (which may not be 100% accurate).

It might also be worth mentioning that we intend to update the API notes to help with the above problem — inferring attributes like [out] or [inout], property inference and so on.

Yes, the uppercase initialisms should all be lowercase in Swift (unless the names of types).

I would also consider making some of it more verbose, e.g. interfaceID instead of IID. This is Swift, not C in 1993, after all.

2 Likes

@COM I feel is right - "COM" is the official name for the technology and the Swift guidelines to indicate that is the right way to name it. COM developers would expect IID and CLSID.

What about @com(interface:) and @com(implementation:) as aliases for @COM(IID:) and @COM(CLSID:) might be a good compromise?

Agreed, that does belong there.

I'm torn on the addition here - does that belong in the vision? I think that belongs more in the design notes.

Attributes and attached macros seem to follow the function naming guidelines, though it hasn't been entirely formalized since we don't update the naming guidelines anymore, so @com(clsid:) and @com(iid:) seem the most correct. COM is lowercased at the beginning, even though it's an acronym, but the parameters can keep their initials, since that's what COM developers use. Or do they actually try to pronounce them?

Regardless of the case of the attribute, I think stylistic aliases for attributes are only going to bring confusion.

I would like to question the need to have a label for the parameter. We know if it's a class or an interface from the declaration that follows (class or protocol). So this should be enough:

@COM("3887CABF-07FE-48F3-AB07-320BA348B445")
class MyThing { ... }

There's perhaps a more general discussion to be had about how to import Windows method names to Swift. Windows APIs tend to use UpperCamelCase for everything including method names and this does not align with the language conventions. Swift uses lowerCamelCase which aligns pretty well with Objective-C, with the exception of abbreviations at the beginning of a method name (such as UUIDString vs. uuidString). On Windows COM, if we were to align the method names, pretty much everything would have to be changed to lowerCamelCase on import, and back to UpperCamelCase on export. It's perhaps not worth the trouble doing, but maybe there's matter for a discussion. It seems to me lowerCamelCase would make the API more at home if your home is Swift, but perhaps keeping things UpperCamelCase would keep someone more at home if his home is Windows.

Whether this affects the name of @COM/@com as a language attribute is another matter though.

Yes, the parameter is import; this is veering into the design of the feature rather than the vision. If you want to read about the design notes, I've just posted that on the forums since the discussing is inevitably veering into the design.

1 Like

Attached macros follow type naming guidelines, not functions. @Test, @Suite, etc. Freestanding expression macros follow function naming patterns (e.g., #expect). I'm not sure if we have enough history to go on for freestanding declaration macros.

Compiler built-in attributes all tend to be lowerCamelCase (recent examples include @abi, @specialize, @c, etc.).

So if this ends up being a built-in attribute, @com is the right spelling. If it's a macro, then @COM is.

3 Likes

Initially, I was leaning towards the IID / CLSID spelling, because that's how most of the documentation is referring to these (example, example).

However, looking at an IDL example, a lowercase attribute is used to specify the interface id:

[
object,
uuid(a03d1420-b1ec-11d0-8c3a-00c04fc31d2f),
] interface IFace1 : IUnknown
{
...
};

And in the (C++) definition of IUnknown's QueryInterface

template<class Q>
HRESULT
STDMETHODCALLTYPE
QueryInterface(_COM_Outptr_ Q** pp)
{
    return QueryInterface(__uuidof(Q), (void **)pp);
}

the __uuidof(Q) spelling to refer to the IID of some COM interface, as also referred to by @compnerd here:

...that the protocol metatype extensions are what gives us the natural spelling for __uuidof(T) in Swift

makes a lowercase IFoo.iid spelling for Foo's interface ID like a good fit.

I'll chime in and say that the rules we followed for Swift Testing may not apply universally. *duck*

1 Like

Those were just the most convenient to cite, not the sole evidence.

If folks are looking for evidence from the standard library itself, there's @DebugDescription and @TaskLocal attached macros, and on the freestanding expression macro side we have #isolation, and all the #file/#line/#function/etc. entries (which were previously built-ins but are now declared as macros).

I'll also point out then that the name of this macro matches that of the previous property wrapper implementation (which of course was type-based).

Anyway, I'm not trying to argue either way, just trying to add, or possibly remove, some clarity. I'll go back to my swamp. :troll: