diff --git a/Support/Sources/COM/COMAggregableBase.swift b/Support/Sources/COM/COMAggregableBase.swift new file mode 100644 index 0000000..234ae46 --- /dev/null +++ b/Support/Sources/COM/COMAggregableBase.swift @@ -0,0 +1,114 @@ +import COM_ABI + +/// Base class for aggregable COM objects. +/// +/// There are three scenarios to support: +/// - Wrapping an existing COM object pointer +/// - Creating a new COM object, which does not need to support method overrides +/// - Creating a derived Swift class that can override methods +open class COMAggregableBase: IUnknownProtocol { + /// Return true to support overriding COM methods in Swift (incurs a size and perf overhead). + open class var supportsOverrides: Bool { true } + + open class var queriableInterfaces: [any COMTwoWayBinding.Type] { [] } + + /// The inner pointer, which comes from COM and implements the base behavior (without overriden methods). + private var _innerObjectWithRef: IUnknownPointer // Strong ref'd (not a COMReference<> because of initialization order issues) + + public var _innerObject: COMInterop { .init(_innerObjectWithRef) } + + /// The outer object, if we are a composed object created from Swift. + public private(set) var _outerObject: OuterObject? + + /// Initializer for instances created by COM + public init(_wrapping innerObject: consuming IUnknownReference) { + _innerObjectWithRef = innerObject.detach() + // The pointer comes from COM so we don't have any overrides and there is no outer object. + // All methods will delegate to the inner object (in this case the full object). + _outerObject = nil + } + + public typealias Factory = ( + _ outer: IUnknownPointer?, + _ inner: inout IUnknownPointer?) throws -> COMReference + + /// Initializer for instances created in Swift + /// - Parameter _outer: The outer object, which brokers QueryInterface calls to the inner object. + /// - Parameter _factory: A closure calling the COM factory method. + public init(_outer: OuterObject.Type, _factory: Factory) throws { + if Self.supportsOverrides { + // Dummy initialization to satisfy Swift's initialization rules + self._outerObject = nil + self._innerObjectWithRef = IUnknownPointer(OpaquePointer(bitPattern: 0xDEADBEEF)!) // We need to assign inner to something, it doesn't matter what. + + let outerObject = _outer.init(owner: self) + + // Like C++/WinRT, discard the returned composed object and only use the inner object + // The composed object is useful only when not providing an outer object. + var innerObjectWithRef: IUnknownPointer? = nil + _ = try _factory(IUnknownPointer(OpaquePointer(outerObject.comEmbedding.asUnknownPointer())), &innerObjectWithRef) + guard let innerObjectWithRef else { throw COMError.fail } + self._innerObjectWithRef = innerObjectWithRef + self._outerObject = outerObject + } + else { + // We don't care about the inner object since COM provides us with the composed object. + var innerObjectWithRef: IUnknownPointer? = nil + defer { IUnknownBinding.release(&innerObjectWithRef) } + self._innerObjectWithRef = try _factory(nil, &innerObjectWithRef).cast().detach() + + // We're not overriding any methods so we don't need to provide an outer object. + self._outerObject = nil + } + } + + deinit { + _innerObject.release() + } + + public func _queryInnerInterface(_ id: COM.COMInterfaceID) throws -> COM.IUnknownReference { + try _innerObject.queryInterface(id) + } + + open func _queryInterface(_ id: COM.COMInterfaceID) throws -> COM.IUnknownReference { + if let _outerObject { + return try _outerObject._queryInterface(id) + } else { + return try _queryInnerInterface(id) + } + } + + /// Base class for the outer object, which brokers QueryInterface calls to the inner object. + open class OuterObject: IUnknownProtocol { + open class var virtualTable: UnsafeRawPointer { IUnknownBinding.virtualTablePointer } + + // The owner pointer points to the ComposableClass object, + // which transitively keeps us alive. + fileprivate var comEmbedding: COMEmbedding + + public var owner: COMAggregableBase { comEmbedding.owner as! COMAggregableBase } + + public required init(owner: COMAggregableBase) { + self.comEmbedding = .init(virtualTable: Self.virtualTable, owner: nil) + self.comEmbedding.initOwner(owner) + } + + public func toCOM() -> COM.IUnknownReference { + comEmbedding.toCOM() + } + + open func _queryInterface(_ id: COM.COMInterfaceID) throws -> COM.IUnknownReference { + // We own the identity, don't delegate to the inner object. + if id == IUnknownBinding.interfaceID { + return toCOM() + } + + // Check for additional implemented interfaces. + if let interfaceBinding = type(of: owner).queriableInterfaces.first(where: { $0.interfaceID == id }) { + return COMDelegatingTearOff(owner: owner, virtualTable: interfaceBinding.virtualTablePointer).toCOM() + } + + return try owner._queryInnerInterface(id) + } + } +} \ No newline at end of file diff --git a/Support/Sources/WindowsRuntime/ComposableClass.swift b/Support/Sources/WindowsRuntime/ComposableClass.swift index e8b41dc..7c64038 100644 --- a/Support/Sources/WindowsRuntime/ComposableClass.swift +++ b/Support/Sources/WindowsRuntime/ComposableClass.swift @@ -3,29 +3,10 @@ import COM_ABI import WindowsRuntime_ABI /// Base class for composable (unsealed) WinRT classes, implemented using COM aggregration. -/// -/// There are three scenarios to support: -/// - Wrapping an existing WinRT object pointer -/// - Creating a new WinRT object, which does not need to support method overrides -/// - Creating a derived Swift class that can override methods -open class ComposableClass: IInspectableProtocol { - /// Return true to support overriding WinRT methods in Swift (incurs a size and perf overhead). - open class var supportsOverrides: Bool { true } - - open class var queriableInterfaces: [any COMTwoWayBinding.Type] { [] } - - /// The inner pointer, which comes from WinRT and implements the base behavior (without overriden methods). - private var innerObjectWithRef: IInspectablePointer // Strong ref'd (not a COMReference<> because of initialization order issues) - - /// The outer object, if we are a composed object created from Swift. - private var outerObject: OuterObject? - - /// Initializer for instances created in WinRT +open class ComposableClass: COMAggregableBase, IInspectableProtocol { + /// Initializer for instances created in WinRT. public init(_wrapping innerObject: consuming IInspectableReference) { - innerObjectWithRef = innerObject.detach() - // The pointer comes from WinRT so we don't have any overrides and there is no outer object. - // All methods will delegate to the inner object (in this case the full object). - outerObject = nil + super.init(_wrapping: innerObject.cast()) } public typealias ComposableFactory = ( @@ -36,85 +17,41 @@ open class ComposableClass: IInspectableProtocol { /// - Parameter _outer: The outer object, which brokers QueryInterface calls to the inner object. /// - Parameter _factory: A closure calling the WinRT composable activation factory method. public init(_outer: OuterObject.Type, _factory: ComposableFactory) throws { - if Self.supportsOverrides { - // Dummy initialization to satisfy Swift's initialization rules - self.outerObject = nil - self.innerObjectWithRef = IInspectablePointer(OpaquePointer(bitPattern: 0xDEADBEEF)!) // We need to assign inner to something, it doesn't matter what. - - let outerObject = _outer.init(owner: self) - - // Like C++/WinRT, discard the returned composed object and only use the inner object - // The composed object is useful only when not providing an outer object. - var innerObjectWithRef: IInspectablePointer? = nil - _ = try _factory(IInspectablePointer(OpaquePointer(outerObject.comEmbedding.asUnknownPointer())), &innerObjectWithRef) - guard let innerObjectWithRef else { throw COMError.fail } - self.innerObjectWithRef = innerObjectWithRef - self.outerObject = outerObject - } - else { - // We don't care about the inner object since WinRT provides us with the composed object. - var innerObjectWithRef: IInspectablePointer? = nil - defer { IInspectableBinding.release(&innerObjectWithRef) } - self.innerObjectWithRef = try _factory(nil, &innerObjectWithRef).cast().detach() - - // We're not overriding any methods so we don't need to provide an outer object. - outerObject = nil + try super.init(_outer: _outer) { + var inner: IInspectablePointer? = nil + do { + let result = try _factory($0.map { IInspectablePointer(OpaquePointer($0)) }, &inner) + $1 = IUnknownPointer(OpaquePointer(inner)) + return result + } catch { + $1 = IUnknownPointer(OpaquePointer(inner)) + throw error + } } } - deinit { - COMInterop(innerObjectWithRef).release() - } - - public func _queryInnerInterface(_ id: COM.COMInterfaceID) throws -> COM.IUnknownReference { - try COMInterop(innerObjectWithRef).queryInterface(id) - } - - open func _queryInterface(_ id: COM.COMInterfaceID) throws -> COM.IUnknownReference { - if let outerObject { - return try outerObject._queryInterface(id) - } else { - return try _queryInnerInterface(id) - } - } + private var _innerInspectable: COMInterop { .init(casting: _innerObject) } open func getIids() throws -> [COM.COMInterfaceID] { - try COMInterop(innerObjectWithRef).getIids() + Self.queriableInterfaces.map { $0.interfaceID } + try _innerInspectable.getIids() + Self.queriableInterfaces.map { $0.interfaceID } } open func getRuntimeClassName() throws -> String { - try COMInterop(innerObjectWithRef).getRuntimeClassName() + try _innerInspectable.getRuntimeClassName() } open func getTrustLevel() throws -> WindowsRuntime.TrustLevel { - try COMInterop(innerObjectWithRef).getTrustLevel() + try _innerInspectable.getTrustLevel() } /// Base class for the outer object, which brokers QueryInterface calls to the inner object. - open class OuterObject: IUnknownProtocol { - // The owner pointer points to the ComposableClass object, - // which transitively keeps us alive. - fileprivate var comEmbedding: COMEmbedding - - public var owner: ComposableClass { comEmbedding.owner as! ComposableClass } + open class OuterObject: COMAggregableBase.OuterObject { + open override class var virtualTable: UnsafeRawPointer { IInspectableBinding.virtualTablePointer } - public required init(owner: ComposableClass) { - self.comEmbedding = .init(virtualTable: IInspectableBinding.virtualTablePointer, owner: nil) - self.comEmbedding.initOwner(owner) - } - - open func _queryInterface(_ id: COM.COMInterfaceID) throws -> COM.IUnknownReference { + open override func _queryInterface(_ id: COM.COMInterfaceID) throws -> COM.IUnknownReference { // We own the identity, don't delegate to the inner object. - if id == IUnknownBinding.interfaceID || id == IInspectableBinding.interfaceID { - return comEmbedding.toCOM() - } - - // Check for additional implemented interfaces. - if let interfaceBinding = type(of: owner).queriableInterfaces.first(where: { $0.interfaceID == id }) { - return COMDelegatingTearOff(owner: owner, virtualTable: interfaceBinding.virtualTablePointer).toCOM() - } - - return try owner._queryInnerInterface(id) + if id == IInspectableBinding.interfaceID { return toCOM() } + return try super._queryInterface(id) } } } \ No newline at end of file