From d92398c83d9acc2c78865a2bd3c7bf7014c71453 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Tue, 17 Dec 2024 06:50:48 -0500 Subject: [PATCH] Introduce ComposableClass.OuterObject --- .../SwiftWinRT/Writing/ABIBinding.swift | 70 +++++++++-- .../SwiftWinRT/Writing/ClassDefinition.swift | 57 ++------- .../Sources/COM/COMDelegatingTearOff.swift | 8 +- Support/Sources/COM/COMExportBase.swift | 2 +- .../WindowsRuntime/ComposableClass.swift | 115 ++++++++++-------- 5 files changed, 138 insertions(+), 114 deletions(-) diff --git a/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift b/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift index d44a1713..fe7d06b1 100644 --- a/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift +++ b/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift @@ -275,19 +275,73 @@ fileprivate func writeClassBindingType( projection: projection, to: writer) + if !classDefinition.isSealed { + try writeComposableClassOuterObject(classDefinition, projection: projection, to: writer) + } + } +} + +fileprivate func writeComposableClassOuterObject( + _ classDefinition: ClassDefinition, + projection: Projection, + to writer: SwiftTypeDefinitionWriter) throws { + let baseOuterObject: SwiftType + if let base = try classDefinition.base, try base.definition.base != nil { + // FIXME: Append .OuterObject + baseOuterObject = SwiftType.identifier(try projection.toBindingTypeName(base.definition)) + } else { + // FIXME: Append .OuterObject + baseOuterObject = SupportModules.WinRT.composableClass + } + + let outerObjectClassName = "OuterObject" + + try writer.writeClass( + visibility: .open, + name: outerObjectClassName, + base: baseOuterObject) { writer in let overridableInterfaces = try classDefinition.baseInterfaces.compactMap { try $0.hasAttribute(OverridableAttribute.self) ? $0.interface : nil } - if !overridableInterfaces.isEmpty { - try writer.writeEnum(visibility: .internal, name: "VirtualTables") { writer in - for interface in overridableInterfaces { - try writeVirtualTableProperty( - visibility: .internal, - name: Casing.pascalToCamel(interface.definition.nameWithoutGenericArity), - abiType: interface.asBoundType, swiftType: classDefinition.bindType(), - projection: projection, to: writer) + guard !overridableInterfaces.isEmpty else { return } + + // public override func _queryInterface(_ id: COM.COMInterfaceID) throws -> COM.IUnknownReference { + try writer.writeFunc( + visibility: .public, override: true, name: "_queryInterface", + params: [ .init(label: "_", name: "id", type: SupportModules.COM.comInterfaceID) ], throws: true, + returnType: SupportModules.COM.iunknownReference) { writer in + for interface in overridableInterfaces { + // if id == uuidof(SWRT_IFoo.self) { + let abiSwiftType = try projection.toABIType(interface.asBoundType) + writer.writeBracedBlock("if id == uuidof(\(abiSwiftType).self)") { writer in + let propertyName = SecondaryInterfaces.getPropertyName(interface) + + // _ifoo_outer.initEmbedder(self) + // return .init(_ifoo_outer.toCOM()) + writer.writeStatement("\(propertyName).initEmbedder(self)") + writer.writeReturnStatement(value: ".init(\(propertyName).toCOM())") } } + + writer.writeReturnStatement(value: "try super._queryInterface(id)") + } + + for interface in overridableInterfaces { + // private var _ifoo: COM.COMEmbedding = .init(virtualTable: &OuterObject.istringable, embedder: nil) + let vtablePropertyName = Casing.pascalToCamel(interface.definition.nameWithoutGenericArity) + writer.writeStoredProperty( + visibility: .private, declarator: .var, + name: SecondaryInterfaces.getPropertyName(interface), + type: SupportModules.COM.comEmbedding, + initialValue: ".init(virtualTable: &\(outerObjectClassName).\(vtablePropertyName), embedder: nil)") + } + + for interface in overridableInterfaces { + try writeVirtualTableProperty( + visibility: .internal, + name: Casing.pascalToCamel(interface.definition.nameWithoutGenericArity), + abiType: interface.asBoundType, swiftType: classDefinition.bindType(), + projection: projection, to: writer) } } } diff --git a/Generator/Sources/SwiftWinRT/Writing/ClassDefinition.swift b/Generator/Sources/SwiftWinRT/Writing/ClassDefinition.swift index c476c646..bb72104c 100644 --- a/Generator/Sources/SwiftWinRT/Writing/ClassDefinition.swift +++ b/Generator/Sources/SwiftWinRT/Writing/ClassDefinition.swift @@ -134,14 +134,6 @@ fileprivate func writeClassMembers( // var _lazyFoo: COM.COMReference.Optional = .none try writeSecondaryInterfaces(classDefinition, interfaces: interfaces, projection: projection, to: writer) - - if !classDefinition.isSealed { // Composable - let overridableInterfaces = interfaces.secondary.compactMap { $0.overridable ? $0.interface : nil } - if !overridableInterfaces.isEmpty { - writer.writeMarkComment("Override support") - try writeOverrideSupport(classDefinition, interfaces: overridableInterfaces, projection: projection, to: writer) - } - } } fileprivate func writeInterfaceImplementations( @@ -251,43 +243,6 @@ fileprivate func writeSecondaryInterfaces( } } -fileprivate func writeOverrideSupport( - _ classDefinition: ClassDefinition, interfaces: [BoundInterface], - projection: Projection, to writer: SwiftTypeDefinitionWriter) throws { - let outerPropertySuffix = "outer" - - for interface in interfaces { - // private var _ifoo_outer: COM.COMEmbedding = .init( - // virtualTable: &SWRT_IStringable.VirtualTables.IStringable, embedder: nil) - let bindingTypeName = try projection.toBindingTypeName(classDefinition) - let vtablePropertyName = Casing.pascalToCamel(interface.definition.nameWithoutGenericArity) - writer.writeStoredProperty( - visibility: .private, declarator: .var, - name: SecondaryInterfaces.getPropertyName(interface, suffix: outerPropertySuffix), - type: SupportModules.COM.comEmbedding, - initialValue: ".init(virtualTable: &\(bindingTypeName).VirtualTables.\(vtablePropertyName), embedder: nil)") - } - - // public override func _queryOverridesInterface(_ id: COM.COMInterfaceID) throws -> COM.IUnknownReference.Optional { - try writer.writeFunc( - visibility: .public, override: true, name: "_queryOverridesInterface", - params: [ .init(label: "_", name: "id", type: SupportModules.COM.comInterfaceID) ], throws: true, - returnType: SupportModules.COM.iunknownReference_Optional) { writer in - for interface in interfaces { - // if id == uuidof(SWRT_IFoo.self) { - let abiSwiftType = try projection.toABIType(interface.asBoundType) - writer.writeBracedBlock("if id == uuidof(\(abiSwiftType).self)") { writer in - let outerPropertyName = SecondaryInterfaces.getPropertyName(interface, suffix: outerPropertySuffix) - - // _ifoo_outer.initEmbedder(self) - // return .init(_ifoo_outer.toCOM()) - writer.writeStatement("\(outerPropertyName).initEmbedder(self)") - writer.writeReturnStatement(value: ".init(\(outerPropertyName).toCOM())") - } - } - writer.writeReturnStatement(value: ".none") - } -} fileprivate func writeMarkComment(forInterface interface: BoundInterface, to writer: SwiftTypeDefinitionWriter) throws { let interfaceName = try WinRTTypeName.from(type: interface.asBoundType).description @@ -304,6 +259,7 @@ fileprivate func writeComposableInitializers( let propertyName = SecondaryInterfaces.getPropertyName(factoryInterface.bind()) let baseClassDefinition = try getRuntimeClassBase(classDefinition) + let outerObjectType = SwiftType.identifier(try projection.toBindingTypeName(classDefinition)) for method in factoryInterface.methods { // Swift requires "override" on initializers iff the same initializer is defined in the direct base class @@ -323,7 +279,7 @@ fileprivate func writeComposableInitializers( params: params.dropLast(2).map { $0.toSwiftParam() }, // Drop inner and outer pointer params throws: true) { writer in let output = writer.output - try output.writeLineBlock(header: "try super.init {", footer: "}") { + try output.writeLineBlock(header: "try super.init(_outer: \(outerObjectType).self) {", footer: "}") { let outerObjectParamName = params[params.count - 2].name let innerObjectParamName = params[params.count - 1].name output.writeFullLine("(\(outerObjectParamName), \(innerObjectParamName): inout IInspectablePointer?) in") @@ -449,12 +405,15 @@ fileprivate func writeDelegatingWrappingInitializer( fileprivate func writeDelegatingComposableInitializer( defaultInterface: BoundInterface, projection: Projection, to writer: SwiftTypeDefinitionWriter) throws { - // public init(_factory: ComposableFactory) throws { + // public init(_outer: OuterObject.Type, _factory: ComposableFactory) throws { writer.writeInit(visibility: .public, override: true, genericParams: [ "ABIStruct" ], - params: [ SwiftParam(name: "_factory", type: .identifier("ComposableFactory", genericArgs: [ .identifier("ABIStruct") ])) ], + params: [ + SwiftParam(name: "_outer", type: .chain("OuterObject", "Type")), + SwiftParam(name: "_factory", type: .identifier("ComposableFactory", genericArgs: [ .identifier("ABIStruct") ])) + ], throws: true) { writer in - writer.writeStatement("try super.init(_factory: _factory)") + writer.writeStatement("try super.init(_outer: _outer, _factory: _factory)") } } diff --git a/Support/Sources/COM/COMDelegatingTearOff.swift b/Support/Sources/COM/COMDelegatingTearOff.swift index d976581e..7c450807 100644 --- a/Support/Sources/COM/COMDelegatingTearOff.swift +++ b/Support/Sources/COM/COMDelegatingTearOff.swift @@ -4,14 +4,14 @@ import COM_ABI public final class COMDelegatingTearOff: COMEmbedderEx { private var comEmbedding: COMEmbedding - public init(virtualTable: UnsafeRawPointer, implementer: IUnknown) { + public init(virtualTable: UnsafeRawPointer, owner: IUnknown) { comEmbedding = .init(virtualTable: virtualTable, embedder: nil) - super.init(implementer: implementer) + super.init(implementer: owner) comEmbedding.initEmbedder(self) } - public convenience init(binding: Binding.Type, implementer: Binding.SwiftObject) { - self.init(virtualTable: Binding.virtualTablePointer, implementer: implementer as! IUnknown) + public convenience init(binding: Binding.Type, owner: Binding.SwiftObject) { + self.init(virtualTable: Binding.virtualTablePointer, owner: owner as! IUnknown) } public func toCOM() -> IUnknownReference { comEmbedding.toCOM() } diff --git a/Support/Sources/COM/COMExportBase.swift b/Support/Sources/COM/COMExportBase.swift index bccd940f..bfad746b 100644 --- a/Support/Sources/COM/COMExportBase.swift +++ b/Support/Sources/COM/COMExportBase.swift @@ -26,7 +26,7 @@ open class COMExportBase: IUnknownPro return try FreeThreadedMarshal(self).toCOM().cast() default: if let interfaceBinding = Self.queriableInterfaces.first(where: { $0.interfaceID == id }) { - return COMDelegatingTearOff(virtualTable: interfaceBinding.virtualTablePointer, implementer: self).toCOM() + return COMDelegatingTearOff(virtualTable: interfaceBinding.virtualTablePointer, owner: self).toCOM() } throw COMError.noInterface } diff --git a/Support/Sources/WindowsRuntime/ComposableClass.swift b/Support/Sources/WindowsRuntime/ComposableClass.swift index c993e176..82017930 100644 --- a/Support/Sources/WindowsRuntime/ComposableClass.swift +++ b/Support/Sources/WindowsRuntime/ComposableClass.swift @@ -12,19 +12,20 @@ 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 innerWithRef: IInspectablePointer // Strong ref'd (not a COMReference<> because of initialization order issues) + private var innerObjectWithRef: IInspectablePointer // Strong ref'd (not a COMReference<> because of initialization order issues) - /// The outer object, which brokers QueryInterface calls between the inner object - /// and any Swift overrides. This is only initialized for derived Swift classes. - private var outer: COMEmbedding + /// The outer object, if we are a composed object created from Swift. + private var outerObject: OuterObject? /// Initializer for instances created in WinRT - public init(_wrapping inner: consuming IInspectableReference) { - innerWithRef = inner.detach() + 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). - outer = .null + outerObject = nil } public typealias ComposableFactory = ( @@ -32,78 +33,88 @@ open class ComposableClass: IInspectableProtocol { _ inner: inout IInspectablePointer?) 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 WinRT composable activation factory method. - public init(_factory: ComposableFactory) throws { + public init(_outer: OuterObject.Type, _factory: ComposableFactory) throws { if Self.supportsOverrides { - // Workaround Swift initialization rules: - // - Factory needs an initialized outer pointer pointing to self - // - self.inner needs to be initialized before being able to reference self - self.outer = .init(virtualTable: IInspectableBinding.virtualTablePointer, embedder: nil) - self.innerWithRef = IInspectablePointer(OpaquePointer(bitPattern: 0xDEADBEEF)!) // We need to assign inner to something, it doesn't matter what. - self.outer.initEmbedder(self) + // 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 inner: IInspectablePointer? = nil - _ = try _factory(IInspectablePointer(OpaquePointer(outer.asUnknownPointer())), &inner) - guard let inner else { throw COMError.fail } - self.innerWithRef = inner + 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're not overriding any methods so we don't need to provide an outer object. - outer = .null - // We don't care about the inner object since WinRT provides us with the composed object. - var inner: IInspectablePointer? = nil - defer { IInspectableBinding.release(&inner) } - self.innerWithRef = try _factory(nil, &inner).cast().detach() + 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 } } deinit { - COMInterop(innerWithRef).release() + COMInterop(innerObjectWithRef).release() } - open class var queriableInterfaces: [any COMTwoWayBinding.Type] { [] } - public func _queryInnerInterface(_ id: COM.COMInterfaceID) throws -> COM.IUnknownReference { - try COMInterop(innerWithRef).queryInterface(id) + try COMInterop(innerObjectWithRef).queryInterface(id) } open func _queryInterface(_ id: COM.COMInterfaceID) throws -> COM.IUnknownReference { - // If we are a composed object created from Swift, act as such - if outer.virtualTable != nil { - // We own the identity, don't delegate to the inner object. - if id == IUnknownBinding.interfaceID || id == IInspectableBinding.interfaceID { - return outer.toCOM() - } - - // Check for overrides. - if let overrides = try _queryOverridesInterface(id).finalDetach() { - return .init(transferringRef: overrides) - } - - // Check for additional implemented interfaces. - if let interfaceBinding = Self.queriableInterfaces.first(where: { $0.interfaceID == id }) { - return COMDelegatingTearOff(virtualTable: interfaceBinding.virtualTablePointer, implementer: self).toCOM() - } + if let outerObject { + return try outerObject._queryInterface(id) + } else { + return try _queryInnerInterface(id) } - - // Delegate to the inner object. - return try _queryInnerInterface(id) } - open func _queryOverridesInterface(_ id: COM.COMInterfaceID) throws -> COM.IUnknownReference.Optional { .none } - open func getIids() throws -> [COM.COMInterfaceID] { - try COMInterop(innerWithRef).getIids() + Self.queriableInterfaces.map { $0.interfaceID } + try COMInterop(innerObjectWithRef).getIids() + Self.queriableInterfaces.map { $0.interfaceID } } open func getRuntimeClassName() throws -> String { - try COMInterop(innerWithRef).getRuntimeClassName() + try COMInterop(innerObjectWithRef).getRuntimeClassName() } open func getTrustLevel() throws -> WindowsRuntime.TrustLevel { - try COMInterop(innerWithRef).getTrustLevel() + try COMInterop(innerObjectWithRef).getTrustLevel() + } + + /// Base class for the outer object, which brokers QueryInterface calls to the inner object. + open class OuterObject: IUnknownProtocol { + // The embedder pointer points to the owner ComposableClass object, + // which transitively keeps us alive. + fileprivate var comEmbedding: COMEmbedding + + public required init(owner: ComposableClass) { + self.comEmbedding = .init(virtualTable: IInspectableBinding.virtualTablePointer, embedder: nil) + self.comEmbedding.initEmbedder(owner) + } + + open 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() + } + + let owner = comEmbedding.embedder as! ComposableClass + + // Check for additional implemented interfaces. + if let interfaceBinding = type(of: owner).queriableInterfaces.first(where: { $0.interfaceID == id }) { + return COMDelegatingTearOff(virtualTable: interfaceBinding.virtualTablePointer, owner: owner).toCOM() + } + + return try owner._queryInnerInterface(id) + } } } \ No newline at end of file