Skip to content

Commit

Permalink
Extracted COMAggregableBase from ComposableClass (#446)
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle authored Dec 26, 2024
1 parent 0c93b3e commit 90922c6
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 85 deletions.
114 changes: 114 additions & 0 deletions Support/Sources/COM/COMAggregableBase.swift
Original file line number Diff line number Diff line change
@@ -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<SWRT_IUnknown> { .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<ABIStruct> = (
_ outer: IUnknownPointer?,
_ inner: inout IUnknownPointer?) throws -> COMReference<ABIStruct>

/// 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<ABIStruct>(_outer: OuterObject.Type, _factory: Factory<ABIStruct>) 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)
}
}
}
107 changes: 22 additions & 85 deletions Support/Sources/WindowsRuntime/ComposableClass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<ABIStruct> = (
Expand All @@ -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<ABIStruct>(_outer: OuterObject.Type, _factory: ComposableFactory<ABIStruct>) 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<SWRT_IInspectable> { .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)
}
}
}

0 comments on commit 90922c6

Please sign in to comment.