Files
Saleem Abdulrasool 26a1abb4e8 docs: describe COM interoperability
Document the source model, existential and native object layouts, interface
maps, dispatch, metadata, serialization, and model-specific behavior for COM
interoperability.

Record the COM method function representation in the stable decoration grammar
and link the new guide from the documentation index.
2026-08-28 15:21:22 -07:00

17 KiB

COM Interoperability

Swift's COM interoperability maps COM interfaces to protocols, COM interface pointers to one-word protocol existentials, and native Swift classes to COM implementations. The model preserves COM's binary calling convention, identity, interface discovery, and reference-counting rules while presenting ordinary Swift protocols, casts, and ARC operations in source.

COM interoperability is experimental. Enable it with -enable-experimental-com-interop. A compilation may select the Microsoft object model with -com-interop-model=microsoft.

The implementation separates language support from the selected COM model. The compiler owns the common type-system and ABI mechanisms. The supplemental COM module defines model-specific identities, activation APIs, and the small set of functions used by compiler-emitted vtables.

Source model

Interfaces

An @com protocol declares a COM interface. Its interface: argument is the interface's stable 128-bit identity.

@com(interface: "17D6A539-7E13-4D88-8419-88CC2F3D15A1")
protocol IWidget {
  func GetValue(_ value: UnsafeMutablePointer<Int32>?) -> HRESULT
}

The identity is validated when the attribute is checked. It is stored in the protocol descriptor in target-native GUID layout and is available as IWidget.IID through the compiler-managed conformance of IWidget.Type to COMInterface.

The metatype conformance is intentionally distinct from conformance by an implementation type. Generic code that operates on interface declarations spells the requirement as follows:

func __uuidof<Interface>(of: Interface.Type) -> IID
    where Interface.Type: COMInterface {
  Interface.IID
}

COMInterface is a compiler-managed, metatype-only protocol. It cannot be used as an existential or explicitly adopted.

A COM interface requirement must have a C-representable ABI. Static, generic, async, and throws requirements are not supported, nor are constructors or associated types. Swift-only conveniences belong in an extension and do not add vtable entries:

extension IWidget {
  var value: Int32 {
    get throws {
      var result: Int32 = 0
      let hr = GetValue(&result)
      guard SUCCEEDED(hr) else {
        throw COMError(hr: hr)
      }
      return result
    }
  }
}

A protocol-extension implementation may also satisfy a declared COM requirement for a native Swift implementation. Its native COM method witness is serialized just like a witness supplied by the class itself.

Interface inheritance

Every interface declaration has one physical ABI chain. A derived interface may refine no COM interface or may refine multiple declarations only when they are all on the same refinement chain. Repeating comparable bases is harmless; incomparable bases are rejected because they cannot describe one interface pointer.

@com(interface: "10000000-0000-0000-0000-000000000001")
protocol IBase {
}

@com(interface: "10000000-0000-0000-0000-000000000002")
protocol IDerived: IBase {
}

@com(interface: "10000000-0000-0000-0000-000000000003")
protocol IIndependent {
}

// Invalid: IBase and IIndependent require different interface pointers.
@com(interface: "10000000-0000-0000-0000-000000000004")
protocol IInvalid: IBase, IIndependent {
}

An interface may additionally refine marker protocols. A non-marker Swift protocol cannot contribute requirements to a COM vtable. A protocol that refines a COM interface must itself have an @com(interface:) identity, and identities must be unique within an ABI chain.

COM conformances are class-only, even though a COM protocol does not need to spell AnyObject as an inherited protocol. A conformance must be declared in the implementation type's module and cannot be conditional.

Implementations

A @com class is a native Swift class that exposes one or more COM interfaces:

@com
final class Widget: IDerived, IIndependent {
  func GetValue(_ value: UnsafeMutablePointer<Int32>?) -> HRESULT {
    value?.pointee = 42
    return S_OK
  }
}

@com(implementation:) additionally gives the class an activation identity:

@com(implementation: "5B071CDC-961D-4BEA-A74A-5D4A7F24BE5A")
public final class Widget: IDerived {
}

The class metatype then has a compiler-managed conformance to COMActivatable. Under the Microsoft model its identity is exposed as Widget.CLSID. The 16 identity bytes are emitted once as a hidden, coalescable constant and are passed directly as the metatype conformance witness. A generic class may participate in COM but cannot have an activation identity.

COMActivatable is a model-independent concept. The spelling and use of its identity are supplied by the selected object model; activation is not required to use Microsoft's CLSID mechanism.

Under the Microsoft model, an activatable class may also declare its registry threading model:

@com(implementation: "5B071CDC-961D-4BEA-A74A-5D4A7F24BE5A", threading: .free)
final class FreeThreadedWidget: IDerived {
}

The supported values are .single, .apartment, .free, .both, and .neutral; .apartment is the default. This information describes activation and registration. It does not add storage to an instance or change the COM method calling convention.

Native Swift inheritance preserves inherited interface pointer positions. A subclass may refine an inherited interface chain or append an independent chain. An open implementation and its native COM method witnesses are serialized so a client module can construct the correct vtables for a subclass. An inherited native entry is reused unless the subclass overrides the corresponding requirement.

Actors and classes using a non-native Swift object model cannot provide native COM implementations.

Object models

The selected object model supplies a logical root interface. The Microsoft model supplies IUnknown as the root of every implementation. Another model, such as one based on XPCOM, can instead supply its own root interface and runtime APIs without changing the common language and ABI mechanisms.

Every native Swift COM implementation also exposes the compiler-managed ISwiftObject interface. It allows a cast from a COM interface pointer back to the native Swift heap object. Users cannot explicitly provide or replace this conformance.

Source can select Microsoft-specific APIs with #if $_MicrosoftCOM. This condition is enabled only when COM interoperability and the Microsoft model are both enabled. It is part of a module's compilation identity and therefore participates in module-cache and serialized-interface compatibility.

COM existentials

Representation and lifetime

An existential such as any IWidget is exactly one machine word containing an interface pointer. It carries neither Swift type metadata nor a Swift protocol witness table. The null pointer is the representation of an absent optional interface.

Copying the existential calls AddRef through vtable slot 1. Destroying it calls Release through vtable slot 2. Borrowing it performs neither operation. The representation and operations are independent of whether the object was implemented in Swift or by foreign code.

COM interface existentials are class-constrained by their conformance rules, but they are not represented as native Swift AnyObject references. An existential composition may contain one most-derived COM interface and any number of marker protocols. It cannot combine unrelated COM interfaces, AnyObject, a superclass constraint, or a non-marker protocol.

Independent interface constraints remain expressible in generic code:

func use<T: IReadable & IWritable>(_ value: borrowing T) {
  value.read()
  value.write()
}

This is a constraint on a native implementation, not a request to represent both interfaces with one existential pointer.

COM existentials support ordinary storage, collection elements, closure captures, inout access, and addressable member-access chains. type(of:) produces the dynamic existential metatype without requiring native Swift object metadata.

Reference storage

Managed weak and checked unowned storage require a runtime weak-reference facility. The base COM model does not define one, so these forms are rejected for COM interface existentials. unowned(unsafe) is supported as a trivial, non-owning interface pointer. Loading it into a strong value performs the required AddRef.

Concrete native @com class references continue to use Swift's normal weak, unowned, and unmanaged reference-storage operations because they point at the Swift heap object rather than at a COM interface address.

Model-specific weak-reference protocols, such as the WinRT weak-reference interfaces, can be layered on this base model in the future without changing the one-word existential representation.

C-representable parameters

A COM existential is C-representable as an interface pointer in a COM method signature. A pointer to an optional existential represents an interface out-parameter, so the following Swift code has the expected IItem ** ABI:

@com(interface: "45000000-0000-0000-0000-000000000001")
protocol IProvider {
  func GetItem(_ item: UnsafeMutablePointer<(any IItem)?>?) -> HRESULT
}

var item: (any IItem)?
let hr = provider.GetItem(&item)

The address passed to the method is the address of the one-word optional existential. A successful foreign call initializes it with an owned interface reference, which ARC subsequently manages.

Raw pointer ownership

Low-level APIs distinguish two conversions:

  • Converting a borrowed interface to a raw pointer does not transfer ownership.
  • Converting an owned +1 raw interface pointer to a Swift existential adopts that reference without calling AddRef.

Creating an owned existential from a borrowed +0 pointer must first acquire a reference. ManagedObject<Interface> makes the adopting and borrowing forms explicit without requiring unsafeBitCast in client code:

let widget = ManagedObject<IWidget>.takeRetainedValue(result)
let pointer = ManagedObject<IWidget>.passUnretained(widget)

takeRetainedValue(_:) consumes the ownership represented by a +1 pointer without calling AddRef. passUnretained(_:) returns the existing interface pointer at +0. ManagedObject is a stateless conversion namespace; it adds no wrapper allocation or storage and does not alter the existential or vtable ABI described here.

Dispatch and casting

Calling an interface requirement loads the target directly from the interface vtable and performs an indirect call. Foreign objects therefore have the same dispatch sequence as C or C++ and require no Swift adapter thunk.

A native Swift implementation has one COM ABI entry for each method witness. The entry:

  1. Uses the fixed adjustment stored immediately before the vtable address point to recover the native Swift heap object.
  2. Adapts the COM ABI arguments to the Swift implementation's calling convention.
  3. Tail-calls the Swift implementation when the target permits it.

The adjustment is constant for a given class and interface address point.

The operators is, as?, and as! use QueryInterface when the target is a COM interface. A cast to a native Swift class first queries ISwiftObject and then applies Swift's native type check. The common interface-cast path therefore does not require Swift metadata in the source existential.

Under the Microsoft model, === and ObjectIdentifier follow COM identity semantics. They query the canonical IUnknown pointer, so two distinct interface pointers for the same object compare as identical.

Generic code constrained by a COM interface receives an interface-adjustment witness rather than a Swift protocol witness table. It uses this witness to project the native object to the required interface pointer. Refinements in one ABI chain share an address point; independent interfaces have distinct adjustments.

Native object layout

A native Swift COM object remains a normal Swift heap object. Its allocation has a compiler-generated prefix containing one vtable pointer for each physical interface address point:

lower addresses

  +-------------------------------+
  | lpVtbl for independent slot   |  <- interface pointer
  +-------------------------------+
  | lpVtbl for primary user slot  |  <- interface pointer
  +-------------------------------+
  | lpVtbl for ISwiftObject       |  <- interface pointer
  +-------------------------------+
  | native Swift heap object      |  <- Swift object pointer
  +-------------------------------+

higher addresses

The implementation's interface frontier is the set of most-derived, independent interface chains it implements. Each frontier member receives one physical address point. All logical interfaces in the same refinement chain map to that address point. ISwiftObject occupies the stable slot closest to the native object.

The allocator copies a constant prefix template into each allocation. The per-instance cost is therefore one word per physical address point; the vtables, interface map, and prefix template are read-only data shared by every instance of the class.

Vtable address point

An interface pointer loads lpVtbl, which points at callable slot 0. Two implementation fields precede that public address point:

lpVtbl[-2]  pointer to the class-wide interface map
lpVtbl[-1]  byte adjustment from this interface pointer to the Swift object
lpVtbl[ 0]  QueryInterface
lpVtbl[ 1]  AddRef
lpVtbl[ 2]  Release
lpVtbl[ 3]  first interface requirement
...         remaining requirements, base-most interface first

The negative slots are an implementation contract between Swift-emitted vtables and the supplemental runtime functions. A foreign COM vtable has only the public slots and is never inspected at negative indices.

An implementation conforming to COMAggregatable uses the aggregating forms of QueryInterface, AddRef, and Release in slots 0 through 2. Aggregation does not add a word to every object; the controller is supplied by the conformance's borrowing controller property.

Interface map

All native vtables for a class share one read-only interface map:

struct Header {
  uint32_t count;
  uint32_t reserved;
};

struct Entry {
  int32_t descriptor;
  uint32_t index;
};

descriptor is a field-relative reference to the logical interface's protocol descriptor. Its low bit indicates an indirect reference. index selects the physical word in the object prefix. The map has one entry for every logical interface in every frontier chain, including a root interface supplied by the selected object model. reserved is currently zero and leaves room for version or flag information without disturbing the eight-byte alignment of entries.

QueryInterface first compares the requested IID with the IID trailing each protocol descriptor in this map. It computes the result from the native object pointer and the selected index. A COMInterfaceResolver may provide conditional interfaces and tear-offs after this fixed-map fast path.

Metadata and emitted identities

A COM protocol descriptor is marked as a special COM protocol and carries its 16-byte IID inline immediately after the fixed protocol-descriptor header. The compiler-managed COMInterface metatype witness is the address of these bytes; there is no second IID global or synthesized accessor.

An activatable class currently emits one target-native 16-byte identity named with the CLSID_ prefix followed by its nominal type descriptor mangling. It has hidden, coalescable linkage so clients may materialize the class identity without duplicating it in one linked image.

Native COM method witnesses are represented structurally in serialized SIL witness tables. A method entry records both the ordinary Swift witness and its COM ABI witness. This lets a client construct native vtables for public and open classes without discovering functions from ad hoc symbol names or serializing unrelated function bodies.

Calling convention and mangling

SIL uses the @convention(com_method) function representation for COM method entries and the com_method instruction for vtable lookup. The physical ABI is the platform COM calling convention. On 32-bit x86 Windows this is stdcall; on other supported targets it is the platform C calling convention.

The stable Swift mangling uses:

  • V for the COM method function representation.
  • TWV for a native COM method witness thunk. TW remains the ordinary Swift protocol witness thunk for the same conformance.

The compiler also gives private class-local data descriptive suffixes such as .com.interface_map, .com.vtable.<protocol>, and .com.prefix. These names are debugger aids for private implementation objects. They are guaranteed to remain local and are not part of the public Swift decoration grammar or a client ABI contract.

Supplemental modules

The COM supplemental module contains the identity types, compiler-recognized metatype protocols, activation support, aggregation hooks, and the C ABI functions referenced by native vtables. The compiler validates only the minimum runtime entries it needs and does not prescribe unrelated contents of the module, allowing the library to evolve independently.

The _COM_Concurrency cross-import overlay adds asynchronous context helpers when both COM and _Concurrency are imported. Core COM use does not require Swift concurrency.

Under the Microsoft model, the module supplies IUnknown, CLSID-based activation, apartment initialization, canonical identity comparison, and aggregation support. These facilities are layered over the model-independent interface, existential, dispatch, and native object-layout mechanisms.