Hello All,
One thing that has often been discussed is the idea of a bi-directional COM interop for Swift. I think that we are finally at a point where we can consider what such a deisgn would look like. I'd like to kick off a discussion to build a shared model for a vision for COM interop.
Thanks to @al45tair for early feedback on this as a starting point.
I am sharing the document here, but will subsequently create a PR for swift-evolution to add this as a vision that will eventually need approval from PSG to be accepted. The subsequent design components will straddle the boundaries between the PSG and LSG and often require input from both. The goal here is to draw upon knowledge from the community and ensure that the design and vision integrate the broader thoughts.
A Vision for COM Interoperability in Swift
Introduction
The Component Object Model (COM) is the foundational binary interface standard underlying the Windows platform. Every significant Windows API surface, from Win32 shell extensions to Direct3D, from Office automation to the Windows Runtime (WinRT), is defined in terms of COM interfaces. COM is also used cross-platform: Mozilla's XPCOM and Apple's IOKit both implement COM-compatible binary interfaces, and projects like MiniCOM demonstrate that the binary model is genuinely portable across Linux, macOS, Android, iOS, and WebAssembly.
Swift on Windows currently has no first-class story for COM. Developers who need to call COM APIs from Swift must drop to unsafe C pointer manipulation, hand-write vtable structs, and manage reference counts manually alongside Swift ARC. This is fragile, verbose, and error-prone. This vision describes how Swift can make COM interoperability as natural as its existing Objective-C interoperability.
The guiding principle is that COM is the binary interface; Swift is the language. Swift developers should never have to think about vtable layout, reference counting, interface identity, or memory allocation domains. They write idiomatic Swift — protocols, classes, properties, throws, async/await, for/in — and the compiler handles the COM binary interface automatically.
Goals
Toll-free bidirectional bridging
Swift code should be able to consume existing COM interfaces, implement COM interfaces, declare new COM interfaces, and expose them to other COM-speaking languages, all with the same type fidelity and performance as native C++ COM code. A COM interface should feel like a Swift protocol. A COM coclass should feel like a Swift class. Crossing the language boundary should cost nothing beyond what the COM ABI itself requires.
Safety without ceremony
The ARC bridge should be the sole owner of the reference count. Swift developers should never call AddRef or Release. QueryInterface should be expressed as Swift's as? operator. Memory allocation domains (CoTaskMem, BSTR, HSTRING) should be handled automatically by synthesised wrappers. Error handling should use throws, not manual HRESULT checks.
ABI compatibility
The object layout and vtable structure should be binary-compatible with C++ COM objects. A Swift-implemented COM object should be callable from C++, C#, or any other COM-speaking language without an adapter layer. The ABI should be lightweight enough to be toll-free in both directions.
Cross-platform
The core @COM attribute and ARC bridge should not be Windows-specific. They should work wherever Swift runs and COM-like interfaces exist: Windows COM, XPCOM, IOKit, MiniCOM. Platform-specific features (apartments, activation, registration) should be layered on top without changing the core model.
Incremental adoption
Existing C/C++ COM code imported via the Clang importer should be automatically elevated to idiomatic Swift without requiring annotation changes to the original headers. HRESULT-returning methods should become throws. Property accessor pairs should become Swift properties. Hungarian-notation parameters should be renamed. Counted arrays should become [T].
Layered design
The interoperability is structured in three layers, each building on the previous:
Layer 1: Core COM (compiler and runtime)
The first layer provides the language-level support for COM's binary interface. This is the work that requires compiler changes:
- A
@COMattribute on protocols (declaring COM interfaces with IIDs) and classes (declaring COM coclasses with optional CLSIDs). - Compiler-synthesised
QueryInterface,AddRef, andReleasewith a unified ARC reference count (no separate COM refcount). - A compact object layout where COM vtable pointers precede the Swift object header, with a
vtable[−1]adjustment constant for pointer recovery. - An
ISwiftObjectCOM interface for recovering the Swift heap object from any COM interface pointer, enablingas?to work across the COM boundary. - Table-driven
QueryInterfacewith a shared implementation in theCOMmodule, so that theQueryInterfacelogic is not duplicated per class. - Shared
AddRef/Releasethunks in theCOMmodule, generic across all@COMclasses. - Clang importer integration: structural
IUnknowndetection, HRESULT-to-throwspromotion,[retval]promotion, property synthesis, name translation, and MIDL array annotation handling. - A
COMstandard library module providingIUnknown,ISwiftObject,COMError, activation overlays, threading types, and extensions on SDK-imported types (GUID,IID,CLSID,HRESULT).
This layer is platform-independent. It works with Windows COM, XPCOM, IOKit, and MiniCOM.
Layer 2: Windows platform integration (compiler and library)
The second layer adds Windows-specific features that are meaningful only when targeting the Windows COM runtime:
- Threading model declaration (
@COM(CLSID:, ThreadingModel:)) and theCOMThreadingModelenum. COMExecutorandCOMMultithreadedExecutorbridging COM apartments to Swift's concurrency model.withCOMContext,COMContext, and@COMMainfor COM initialisation lifecycle.withActivationContextfor scopedCLSCTXvia@TaskLocal.DllGetClassObject,IClassFactory,DllRegisterServer/DllUnregisterServersynthesis for@COM(CLSID:)classes.@COMInitfor designating the activation initialiser.ISupportErrorInfosynthesis andIErrorInfocapture/population.BSTRandLPWSTRstring bridging with correct allocation domains.
Layer 3: WinRT projection (pure library)
The third layer projects the Windows Runtime onto Swift. This layer requires no compiler changes. It is a pure library built on the @COM infrastructure from Layer 1:
IInspectablesynthesis (GetIids,GetRuntimeClassName,GetTrustLevel) implemented as library code reading compiler-emitted metadata.- Parameterised interface IID derivation via UUID v5 (SHA-1), using the
WindowsRuntimeTypeprotocol for type signatures. The concrete IID is computed from compile-time constant inputs and stored directly. IAsyncOperation<T>andIAsyncActionbridged to Swiftasync/awaitvia continuations.- WinRT events (
add_/remove_withEventRegistrationToken) bridged toAsyncSequence. - WinRT collections (
IVector<T>,IMap<K,V>,IIterable<T>) bridged to SwiftSequenceandCollection. HSTRINGbridging with theWindowsCreateStringReferencefast-pass optimisation.RoActivateInstance/RoGetActivationFactoryoverlays and the@WinRT(RuntimeName:)macro.
The fact that WinRT requires no compiler changes is a key architectural property. It means the WinRT projection can evolve independently of the Swift compiler, new WinRT patterns can be added as library code, and the compiler team's investment is focused on the COM layer that benefits all COM-family implementations.
What the developer sees
Consuming a COM interface
// COM: ISpVoice inherits ISpEventSource inherits IUnknown
// Swift developer sees a protocol with methods and properties:
let voice = try SpVoice()
try voice.speak("Hello, world")
if let eventSource = voice as? any ISpEventSource {
for await event in eventSource.events {
print(event)
}
}
No vtable structs. No QueryInterface calls. No AddRef/Release. No HRESULT checking. The COM binary interface is invisible.
Implementing a COM interface
@COM(CLSID: "...")
final class MyWidget: IWidget {
func render() throws { ... }
var name: String { get throws { "MyWidget" } }
}
The class is a COM coclass. It can be activated via CoCreateInstance from C++, C#, or any COM-speaking language. The compiler synthesises the vtable, the class factory, and the registration exports.
Declaring a new COM interface
@COM(IID: "...")
protocol ICanvas: IUnknown {
func drawRect(_ rect: Rect) throws
var background: Color { get throws set }
}
The protocol is a COM interface. Its IID and vtable layout are part of the module's ABI. Non-Swift consumers can use it through a generated C header or IDL file.
Relationship to existing interoperability
COM interoperability follows the model established by Objective-C and C++ interop in Swift:
- Like
-enable-objc-interop, COM interop is gated behind-enable-com-interop(enabled by default on Windows). - Like the
ObjectiveCmodule, theCOMmodule provides foundational types and overlays. - Like
-emit-objc-header, a future-emit-com-headerwould generate C/C++ interface descriptions from Swift@COMdeclarations. - Like Objective-C's
isapointer, COM's vtable pointers precede the Swift object header in a layout that is ABI-compatible with C++ COM objects. - Like
@objcon classes,@COMopts a type into the foreign type system while keeping it fully usable from Swift.
Both Objective-C interop and COM interop are platform-independent in principle (Objective-C depends on the ObjC runtime ABI, COM depends on the COM vtable ABI). The practical difference is that COM has multiple independent implementations across platforms (Windows COM, IOKit, XPCOM, MiniCOM), while Objective-C interop targets a single runtime family. The same @COM attribute works with all of these implementations. Platform-specific features (apartments, activation, WinRT) are layered on top.
Companion documents
The detailed design is split into two companion documents:
-
COM Interoperability Design (
com-interop-design.md) — covers the compiler and language-level work in Layers 1 and 2: the@COMattribute, object layout, ARC bridge,QueryInterfaceimplementation, Clang importer integration, threading model, activation, aggregation, and theCOMmodule contents. -
WinRT Projection Design (
winrt-projection-design.md) — covers the pure-library Layer 3:IInspectablesynthesis, parameterised interfaces, async bridging, event sequences, collection conformances,HSTRINGbridging, and WinRT activation.
Future directions
Automation (IDispatch, VARIANT, SAFEARRAY)
IDispatch enables late-binding COM access from scripting engines and Office VBA. VARIANT is COM's dynamically-typed value container. These are important for Office automation and scripting interop but are deferred from the initial design.
DCOM and Swift's Distributed module
DCOM extends COM with cross-process and cross-machine invocation. The current design accommodates DCOM as a natural extension: a future COMDistributedActorSystem could bridge COM's proxy/stub infrastructure to Swift's distributed actor model with no changes to the core @COM attribute or ABI.
Interface export tooling
Generating C/C++ headers, MIDL IDL, and .winmd metadata from Swift @COM declarations would complete the bidirectional story, allowing non-Swift consumers to call Swift COM objects without hand-written interface descriptions.
Automation type bridging
DECIMAL, CURRENCY, DATE, MIDL unions, and SAFEARRAY need Swift mappings for full fidelity with the OLE Automation type system.