Skip to content

Commit

Permalink
Introduce ComposableClass.OuterObject
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle committed Dec 17, 2024
1 parent 182fad3 commit d92398c
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 114 deletions.
70 changes: 62 additions & 8 deletions Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down
57 changes: 8 additions & 49 deletions Generator/Sources/SwiftWinRT/Writing/ClassDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,6 @@ fileprivate func writeClassMembers(

// var _lazyFoo: COM.COMReference<SWRT_IFoo>.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(
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -449,12 +405,15 @@ fileprivate func writeDelegatingWrappingInitializer(

fileprivate func writeDelegatingComposableInitializer(
defaultInterface: BoundInterface, projection: Projection, to writer: SwiftTypeDefinitionWriter) throws {
// public init<ABIStruct>(_factory: ComposableFactory<ABIStruct>) throws {
// public init<ABIStruct>(_outer: OuterObject.Type, _factory: ComposableFactory<ABIStruct>) 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)")
}
}
8 changes: 4 additions & 4 deletions Support/Sources/COM/COMDelegatingTearOff.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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: COMTwoWayBinding>(binding: Binding.Type, implementer: Binding.SwiftObject) {
self.init(virtualTable: Binding.virtualTablePointer, implementer: implementer as! IUnknown)
public convenience init<Binding: COMTwoWayBinding>(binding: Binding.Type, owner: Binding.SwiftObject) {
self.init(virtualTable: Binding.virtualTablePointer, owner: owner as! IUnknown)
}

public func toCOM() -> IUnknownReference { comEmbedding.toCOM() }
Expand Down
2 changes: 1 addition & 1 deletion Support/Sources/COM/COMExportBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ open class COMExportBase<PrimaryInterfaceBinding: COMTwoWayBinding>: 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
}
Expand Down
115 changes: 63 additions & 52 deletions Support/Sources/WindowsRuntime/ComposableClass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,98 +12,109 @@ 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<ABIStruct> = (
_ outer: IInspectablePointer?,
_ inner: inout IInspectablePointer?) 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 WinRT composable activation factory method.
public init<ABIStruct>(_factory: ComposableFactory<ABIStruct>) throws {
public init<ABIStruct>(_outer: OuterObject.Type, _factory: ComposableFactory<ABIStruct>) 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)
}
}
}

0 comments on commit d92398c

Please sign in to comment.