Skip to content

Commit

Permalink
Remove COMExportBase and introduce COMImplements<Binding> (#411)
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle authored Dec 4, 2024
1 parent ea4ab07 commit 9aa4243
Show file tree
Hide file tree
Showing 22 changed files with 80 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extension Sequence {
}
}

fileprivate class SequenceIterable<S: Sequence>: WinRTPrimaryExport<IInspectableBinding>, WindowsFoundationCollections_IIterableProtocol {
fileprivate class SequenceIterable<S: Sequence>: WinRTExport<IInspectableBinding>, WindowsFoundationCollections_IIterableProtocol {
typealias T = S.Element

private let sequence: S
Expand All @@ -23,7 +23,7 @@ fileprivate class SequenceIterable<S: Sequence>: WinRTPrimaryExport<IInspectable
}
}

internal class SequenceIterator<I: IteratorProtocol>: WinRTPrimaryExport<IInspectableBinding>, WindowsFoundationCollections_IIteratorProtocol {
internal class SequenceIterator<I: IteratorProtocol>: WinRTExport<IInspectableBinding>, WindowsFoundationCollections_IIteratorProtocol {
typealias T = I.Element

private var iterator: I
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ extension Collection where Index == Int, Element: Equatable {
}
}

fileprivate class CollectionVectorView<C: Collection>: WinRTPrimaryExport<IInspectableBinding>,
fileprivate class CollectionVectorView<C: Collection>: WinRTExport<IInspectableBinding>,
WindowsFoundationCollections_IVectorViewProtocol
where C.Index == Int {
public typealias T = C.Element
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ extension Array where Element: Equatable {
}

/// Wraps a Swift array into a type implementing WinRT's Windows.Foundation.Collections.IVector<T>.
public class ArrayVector<T>: WinRTPrimaryExport<IInspectableBinding>,
public class ArrayVector<T>: WinRTExport<IInspectableBinding>,
WindowsFoundationCollections_IVectorProtocol, WindowsFoundationCollections_IVectorViewProtocol {
public var array: [T]
public var elementEquals: (T, T) -> Bool
Expand Down
2 changes: 1 addition & 1 deletion InteropTests/Tests/EventTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class EventTests: WinRTTestCase {
try eventSource.fire()
XCTAssertEqual(try counter.count, 1)

class EventSource: WinRTPrimaryExport<IEventSourceBinding>, IEventSourceProtocol {
class EventSource: WinRTExport<IEventSourceBinding>, IEventSourceProtocol {
private var invocationList: EventInvocationList<MinimalDelegate> = .init()

@discardableResult
Expand Down
2 changes: 1 addition & 1 deletion InteropTests/Tests/InterfaceImplementationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import WinRTComponent

class InterfaceImplementationTests: WinRTTestCase {
func testWithSwiftObject() throws {
class Exported: WinRTPrimaryExport<IInspectableBinding>, IMinimalInterfaceProtocol {
class Exported: WinRTExport<IInspectableBinding>, IMinimalInterfaceProtocol {
override class var queriableInterfaces: [any COMTwoWayBinding.Type] { [
IMinimalInterfaceBinding.self
] }
Expand Down
4 changes: 2 additions & 2 deletions InteropTests/Tests/ObjectExportingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import WindowsRuntime
import WinRTComponent

class ObjectExportingTests: WinRTTestCase {
class ExportedClass: WinRTPrimaryExport<IInspectableBinding> {}
class ExportedClass: WinRTExport<IInspectableBinding> {}

func testBalancedAddRefRelease() throws {
let obj: ExportedClass = .init()
Expand Down Expand Up @@ -42,7 +42,7 @@ class ObjectExportingTests: WinRTTestCase {
}

func testWeakReference() throws {
class Exported: WinRTPrimaryExport<IInspectableBinding> {}
class Exported: WinRTExport<IInspectableBinding> {}

var instance: Exported? = Exported()
let weakReferencer = try WeakReferencer(XCTUnwrap(instance))
Expand Down
2 changes: 1 addition & 1 deletion InteropTests/Tests/ValueOutParamRoundtripTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class ValueOutParamRoundtripTests: WinRTTestCase {
XCTAssertNil(roundtripped)
}

class OutputArgumentImplementation: WinRTPrimaryExport<IOutputArgumentBinding>, IOutputArgumentProtocol {
class OutputArgumentImplementation: WinRTExport<IOutputArgumentBinding>, IOutputArgumentProtocol {
func int32(_ value: Int32, _ result: inout Int32) throws { result = value }
func string(_ value: String, _ result: inout String) throws { result = value }
func object(_ value: WindowsRuntime.IInspectable?, _ result: inout WindowsRuntime.IInspectable?) throws { result = value }
Expand Down
2 changes: 1 addition & 1 deletion InteropTests/Tests/ValueReturnRoundtripTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class ValueReturnRoundtripTests: WinRTTestCase {
XCTAssertNil(try twoWay.reference(nil))
}

class ReturnArgumentImplementation: WinRTPrimaryExport<IReturnArgumentBinding>, IReturnArgumentProtocol {
class ReturnArgumentImplementation: WinRTExport<IReturnArgumentBinding>, IReturnArgumentProtocol {
func int32(_ value: Int32) throws -> Int32 { value }
func string(_ value: String) throws -> String { value }
func object(_ value: WindowsRuntime.IInspectable?) throws -> WindowsRuntime.IInspectable { try NullResult.unwrap(value) }
Expand Down
8 changes: 5 additions & 3 deletions Support/Sources/COM/COMEmbedding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ public struct COMEmbedding: ~Copyable {

public mutating func initialize(embedder: AnyObject, virtualTable: UnsafeRawPointer) {
comObject.virtualTable = virtualTable
comObject.swiftSelf = Unmanaged<AnyObject>.passUnretained(embedder).toOpaque()
comObject.swiftEmbedder = Unmanaged<AnyObject>.passUnretained(embedder).toOpaque()
}

public var isInitialized: Bool { comObject.swiftSelf != nil }
public var isInitialized: Bool { comObject.swiftEmbedder != nil }

public var embedder: AnyObject? { comObject.swiftEmbedder == nil ? nil : Unmanaged<AnyObject>.fromOpaque(comObject.swiftEmbedder).takeUnretainedValue() }

public var unknownPointer: IUnknownPointer {
mutating get {
Expand All @@ -41,7 +43,7 @@ public struct COMEmbedding: ~Copyable {

fileprivate static func toUnmanagedUnsafe<ABIStruct>(_ this: UnsafeMutablePointer<ABIStruct>) -> Unmanaged<AnyObject> {
this.withMemoryRebound(to: SWRT_SwiftCOMObject.self, capacity: 1) {
Unmanaged<AnyObject>.fromOpaque($0.pointee.swiftSelf)
Unmanaged<AnyObject>.fromOpaque($0.pointee.swiftEmbedder)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Support/Sources/COM/COMError+SwiftErrorInfo.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
extension COMError {
/// Wraps a Swift Error object into an `IErrorInfo` to preserve it across COM boundaries.
internal final class SwiftErrorInfo: COMPrimaryExport<IErrorInfoBinding>, IErrorInfoProtocol {
internal final class SwiftErrorInfo: COMExport<IErrorInfoBinding>, IErrorInfoProtocol {
public let error: Error

public init(error: Error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
/// Base for Swift classes that implement one or more COM interfaces and owns the COM object identity,
/// meaning that they own the object returned when using QueryInterface for IUnknown.
/// The generic Binding parameter determines the virtual table of the identity object.
open class COMPrimaryExport<Binding: COMTwoWayBinding>: COMExportBase<Binding> {
open class COMExport<PrimaryInterfaceBinding: COMTwoWayBinding>: IUnknownProtocol {
/// Gets the interfaces that can be queried for using queryInterface.
open class var queriableInterfaces: [any COMTwoWayBinding.Type] { [] }
open class var implementIAgileObject: Bool { true }
open class var implementFreeThreadedMarshaling: Bool { implementIAgileObject }

public override init() {
super.init()
/// The COM identity object exposing the implementation of the primary interface.
private var primaryImplements: COMImplements<PrimaryInterfaceBinding> = .init()

public init() {}

public func toCOM() -> PrimaryInterfaceBinding.ABIReference {
primaryImplements.toCOM(embedder: self)
}

open override func _queryInterface(_ id: COMInterfaceID) throws -> IUnknownReference {
open func _queryInterface(_ id: COMInterfaceID) throws -> IUnknownReference {
switch id {
case Binding.interfaceID:
case PrimaryInterfaceBinding.interfaceID:
return toCOM().cast()
case IUnknownBinding.interfaceID:
return toCOM().cast()
Expand Down
25 changes: 0 additions & 25 deletions Support/Sources/COM/COMExportBase.swift

This file was deleted.

24 changes: 24 additions & 0 deletions Support/Sources/COM/COMImplements.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/// Use as a stored property of a class to allow the object to be referenced
/// as a COM object exposing a given interface.
public struct COMImplements<InterfaceBinding: COMTwoWayBinding>: ~Copyable {
private var embedding: COMEmbedding = .uninitialized

public mutating func toCOM(embedder: InterfaceBinding.SwiftObject) -> InterfaceBinding.ABIReference {
// The embedder should conform to IUnknownProtocol and hence be an AnyObject,
// but this cannot be expressed in the type system.
toCOM(embedder: embedder as! IUnknown)
}

internal mutating func toCOM(embedder: AnyObject) -> InterfaceBinding.ABIReference {
if embedding.isInitialized {
assert(embedding.embedder === embedder, "COM object already embedded in another object.")
} else {
// Thread safe since every initialization will produce the same state and has no side-effects.
assert(embedder is InterfaceBinding.SwiftObject || embedder is COMEmbedderWithDelegatedImplementation,
"Embedder \(type(of: embedder)) does not conform to the expected interface \(InterfaceBinding.self).")
embedding.initialize(embedder: embedder, virtualTable: InterfaceBinding.virtualTablePointer)
}

return embedding.toCOM().cast()
}
}
11 changes: 8 additions & 3 deletions Support/Sources/COM/COMSecondaryExport.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
/// A COM-exported object providing a secondary interface to a primary exported object.
open class COMSecondaryExport<Binding: COMTwoWayBinding>: COMExportBase<Binding> {
open class COMSecondaryExport<InterfaceBinding: COMTwoWayBinding>: IUnknownProtocol {
private var implements: COMImplements<InterfaceBinding> = .init()
public let identity: IUnknown

public init(identity: IUnknown) {
self.identity = identity
}

open override func _queryInterface(_ id: COMInterfaceID) throws -> IUnknownReference {
public func toCOM() -> InterfaceBinding.ABIReference {
implements.toCOM(embedder: self)
}

open func _queryInterface(_ id: COMInterfaceID) throws -> IUnknownReference {
switch id {
case Binding.interfaceID: return toCOM().cast()
case InterfaceBinding.interfaceID: return toCOM().cast()
default: return try identity._queryInterface(id)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Support/Sources/COM_ABI/include/SwiftCOMObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ typedef struct SWRT_SwiftCOMObject {
const void* virtualTable;
// The Unmanaged<AnyObject> pointer to the Swift object that embeds this structure.
// This object is retained and released by AddRef/Release calls.
void* swiftSelf;
void* swiftEmbedder;
} SWRT_SwiftCOMObject;
2 changes: 1 addition & 1 deletion Support/Sources/WindowsRuntime/ExportedDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

/// A COM-exported object delegating its implementation to a Swift object.
public class ExportedDelegate<Binding: DelegateBinding>: COMPrimaryExport<Binding>, COMEmbedderWithDelegatedImplementation {
public class ExportedDelegate<Binding: DelegateBinding>: COMExport<Binding>, COMEmbedderWithDelegatedImplementation {
public let closure: Binding.SwiftObject

public init(_ closure: Binding.SwiftObject) {
Expand Down
2 changes: 1 addition & 1 deletion Support/Sources/WindowsRuntime/ReferenceArrayImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import WindowsRuntime_ABI

/// Implements IReferenceArray<T> for any boxable T not provided by the UWP PropertyValue class (value types and delegates).
internal class ReferenceArrayImpl<TBinding: IReferenceableBinding>
: WinRTPrimaryExport<WindowsFoundation_IReferenceArrayBinding<TBinding>>,
: WinRTExport<WindowsFoundation_IReferenceArrayBinding<TBinding>>,
WindowsFoundation_IReferenceArrayProtocol {
public typealias T = TBinding.SwiftValue

Expand Down
2 changes: 1 addition & 1 deletion Support/Sources/WindowsRuntime/ReferenceImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import WindowsRuntime_ABI

/// Implements IReference<T> for any boxable T not provided by the UWP PropertyValue class (value types and delegates).
internal class ReferenceImpl<TBinding: IReferenceableBinding>
: WinRTPrimaryExport<WindowsFoundation_IReferenceBinding<TBinding>>,
: WinRTExport<WindowsFoundation_IReferenceBinding<TBinding>>,
WindowsFoundation_IReferenceProtocol {
public typealias T = TBinding.SwiftValue

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
extension WinRTError {
/// Wraps a Swift Error object so it can be associated with an `IRestrictedErrorInfo`.
internal final class LanguageException: COMPrimaryExport<IUnknownBinding> {
internal final class LanguageException: COMExport<IUnknownBinding> {
public let error: Error

public init(error: Error) {
Expand Down
19 changes: 5 additions & 14 deletions Support/Sources/WindowsRuntime/WinRTExport.swift
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import COM

/// Base for classes exported to WinRT and COM consumers.
open class WinRTPrimaryExport<Binding: InterfaceBinding>: COMPrimaryExport<Binding>, IInspectableProtocol {
open class WinRTExport<PrimaryInterfaceBinding: InterfaceBinding>: COMExport<PrimaryInterfaceBinding>, IInspectableProtocol {
open class var _runtimeClassName: String { String(describing: Self.self) }
open class var _trustLevel: TrustLevel { .base }
open class var implementIStringable: Bool { true }
open class var implementIWeakReferenceSource: Bool { true }

public var inspectablePointer: IInspectableBinding.ABIPointer {
unknownPointer.withMemoryRebound(to: IInspectableBinding.ABIStruct.self, capacity: 1) { $0 }
}

open override func _queryInterface(_ id: COMInterfaceID) throws -> IUnknownReference {
switch id {
// QI for IInspectable should return the identity interface just like IUnknown.
case IInspectableBinding.interfaceID:
return .init(addingRef: unknownPointer)
return toCOM().cast()
case IWeakReferenceSourceBinding.interfaceID where Self.implementIWeakReferenceSource:
return ExportedWeakReferenceSource(target: self).toCOM().cast()
case WindowsFoundation_IStringableBinding.interfaceID where Self.implementIStringable:
Expand All @@ -29,16 +25,11 @@ open class WinRTPrimaryExport<Binding: InterfaceBinding>: COMPrimaryExport<Bindi
}

open func getIids() throws -> [COMInterfaceID] {
var iids = [COMInterfaceID]()
try _appendIids(&iids)
return iids
}

open func _appendIids(_ iids: inout [COMInterfaceID]) throws {
for interfaceBinding in Self.queriableInterfaces { iids.append(interfaceBinding.interfaceID) }
var iids = Self.queriableInterfaces.map { $0.interfaceID }
if Self.implementIAgileObject { iids.append(IAgileObjectBinding.interfaceID) }
if Self.implementIWeakReferenceSource { iids.append(IWeakReferenceSourceBinding.interfaceID) }
if Self.implementIStringable, self is CustomStringConvertible { iids.append(WindowsFoundation_IStringableBinding.interfaceID) }
return iids
}

public final func getRuntimeClassName() throws -> String { Self._runtimeClassName }
Expand Down Expand Up @@ -72,7 +63,7 @@ fileprivate class ExportedWeakReferenceSource: COMSecondaryExport<IWeakReference
func getWeakReference() throws -> IWeakReference { ExportedWeakReference(target: identity as! IInspectable) }
}

fileprivate class ExportedWeakReference: COMPrimaryExport<IWeakReferenceBinding>, IWeakReferenceProtocol {
fileprivate class ExportedWeakReference: COMExport<IWeakReferenceBinding>, IWeakReferenceProtocol {
weak var target: IInspectable?
init(target: IInspectable) { self.target = target }
func resolve() throws -> IInspectable? { target }
Expand Down
14 changes: 7 additions & 7 deletions Support/Tests/Tests/COMExportTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import XCTest
import WindowsRuntime

internal final class COMExportTests: XCTestCase {
final class TestObject: COMPrimaryExport<IUnknownBinding>, ICOMTestProtocol, ICOMTest2Protocol {
final class TestObject: COMExport<IUnknownBinding>, ICOMTestProtocol, ICOMTest2Protocol {
override class var queriableInterfaces: [any COMTwoWayBinding.Type] { [
ICOMTestBinding.self,
ICOMTest2Binding.self
Expand Down Expand Up @@ -38,11 +38,11 @@ internal final class COMExportTests: XCTestCase {
}

func testIAgileObject() throws {
final class AgileObject: COMPrimaryExport<IUnknownBinding> {
final class AgileObject: COMExport<IUnknownBinding> {
override class var implementIAgileObject: Bool { true }
}

final class NonAgileObject: COMPrimaryExport<IUnknownBinding> {
final class NonAgileObject: COMExport<IUnknownBinding> {
override class var implementIAgileObject: Bool { false }
}

Expand All @@ -51,11 +51,11 @@ internal final class COMExportTests: XCTestCase {
}

func testFreeThreadedMarshalability() throws {
final class Marshalable: COMPrimaryExport<IUnknownBinding> {
final class Marshalable: COMExport<IUnknownBinding> {
override class var implementFreeThreadedMarshaling: Bool { true }
}

final class NonMarshalable: COMPrimaryExport<IUnknownBinding> {
final class NonMarshalable: COMExport<IUnknownBinding> {
override class var implementFreeThreadedMarshaling: Bool { false }
}

Expand All @@ -65,7 +65,7 @@ internal final class COMExportTests: XCTestCase {
}

func testImplementsSecondaryInterface() throws {
final class CallCounter: COMPrimaryExport<IUnknownBinding>, ICOMTestProtocol {
final class CallCounter: COMExport<IUnknownBinding>, ICOMTestProtocol {
override class var queriableInterfaces: [any COMTwoWayBinding.Type] { [
ICOMTestBinding.self
] }
Expand All @@ -84,7 +84,7 @@ internal final class COMExportTests: XCTestCase {
}

func testEmbeddedSecondaryInterface() throws {
final class CallCounter: COMPrimaryExport<IUnknownBinding>, ICOMTestProtocol {
final class CallCounter: COMExport<IUnknownBinding>, ICOMTestProtocol {
override class var queriableInterfaces: [any COMTwoWayBinding.Type] { [
ICOMTestBinding.self
] }
Expand Down
Loading

0 comments on commit 9aa4243

Please sign in to comment.