Skip to content

Commit

Permalink
Support dynamic instantiation of derived activatable classes (#379)
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle authored Nov 1, 2024
1 parent 66aace3 commit 9a764d6
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 52 deletions.
2 changes: 1 addition & 1 deletion Generator/Sources/ProjectionModel/SupportModules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ extension SupportModules.WinRT {
public static var structBinding: SwiftType { .chain(moduleName, "StructBinding") }
public static var interfaceBinding: SwiftType { .chain(moduleName, "InterfaceBinding") }
public static var delegateBinding: SwiftType { .chain(moduleName, "DelegateBinding") }
public static var activatableClassBinding: SwiftType { .chain(moduleName, "ActivatableClassBinding") }
public static var runtimeClassBinding: SwiftType { .chain(moduleName, "RuntimeClassBinding") }
public static var composableClassBinding: SwiftType { .chain(moduleName, "ComposableClassBinding") }

public static var composableClass: SwiftType { .chain(moduleName, "ComposableClass") }
Expand Down
2 changes: 1 addition & 1 deletion Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ fileprivate func writeClassBindingType(

let projectionProtocol = try classDefinition.hasAttribute(ComposableAttribute.self)
? SupportModules.WinRT.composableClassBinding
: SupportModules.WinRT.activatableClassBinding
: SupportModules.WinRT.runtimeClassBinding

let bindingTypeName = try projection.toBindingTypeName(classDefinition)
try writer.writeClass(
Expand Down
32 changes: 15 additions & 17 deletions InteropTests/Tests/ClassInheritanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,26 @@ class ClassInheritanceTests : XCTestCase {
}

public func testWithUpcasting() throws {
struct UpcastingSwiftWrapperFactory: SwiftWrapperFactory {
func create<Binding: COMBinding>(
_ reference: consuming Binding.ABIReference,
binding: Binding.Type) -> Binding.SwiftObject {
struct UpcastableSwiftWrapperFactory: SwiftWrapperFactory {
func create<StaticBinding: COMBinding>(
_ reference: consuming StaticBinding.ABIReference,
staticBinding: StaticBinding.Type) -> StaticBinding.SwiftObject {
// Try from the runtime type first, then fall back to the statically known binding
if let object: Binding.SwiftObject = fromRuntimeType(
if let object: StaticBinding.SwiftObject = fromRuntimeType(
inspectable: IInspectablePointer(OpaquePointer(reference.pointer))) {
return object
} else {
return Binding._wrap(consume reference)
return StaticBinding._wrap(consume reference)
}
}

func fromRuntimeType<SwiftObject>(inspectable: IInspectablePointer) -> SwiftObject? {
guard let runtimeClassName = try? COMInterop(inspectable).getRuntimeClassName() else { return nil }
let swiftBindingQualifiedName = toBindingQualifiedName(runtimeClassName: consume runtimeClassName)
guard let bindingType = NSClassFromString(swiftBindingQualifiedName) as? any RuntimeClassBinding.Type else { return nil }
return try? bindingType._wrapObject(COMReference(addingRef: inspectable)) as? SwiftObject
}

func toBindingQualifiedName(runtimeClassName: String) -> String {
// Namespace.ClassName -> WinRTComponent.ClassNameBinding
var result = runtimeClassName
Expand All @@ -88,22 +95,13 @@ class ClassInheritanceTests : XCTestCase {
result += "Binding"
return result
}

func fromRuntimeType<SwiftObject>(inspectable: IInspectablePointer) -> SwiftObject? {
guard let runtimeClassName = try? COMInterop(inspectable).getRuntimeClassName() else { return nil }
let swiftBindingQualifiedName = toBindingQualifiedName(runtimeClassName: consume runtimeClassName)
guard let bindingType = NSClassFromString(swiftBindingQualifiedName) as? any ComposableClassBinding.Type else { return nil }
return bindingType._wrapObject(COMReference(addingRef: inspectable)) as? SwiftObject
}
}

let originalFactory = WindowsRuntime.swiftWrapperFactory
WindowsRuntime.swiftWrapperFactory = UpcastingSwiftWrapperFactory()
WindowsRuntime.swiftWrapperFactory = UpcastableSwiftWrapperFactory()
defer { WindowsRuntime.swiftWrapperFactory = originalFactory }

XCTAssertNotNil(try MinimalBaseClassHierarchy.createUnsealedDerivedAsBase() as? MinimalUnsealedDerivedClass)

// TODO: https://github.com/tristanlabelle/swift-winrt/issues/375
// XCTAssertNotNil(try MinimalBaseClassHierarchy.createSealedDerivedAsBase() as? MinimalSealedDerivedClass)
XCTAssertNotNil(try MinimalBaseClassHierarchy.createSealedDerivedAsBase() as? MinimalSealedDerivedClass)
}
}
26 changes: 6 additions & 20 deletions Support/Sources/WindowsRuntime/BindingProtocols+extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ extension DelegateBinding {
}
}

extension ActivatableClassBinding {
extension RuntimeClassBinding {
public static func _wrapObject(_ reference: consuming IInspectableReference) throws -> IInspectable {
try _wrap(reference.queryInterface(interfaceID)) as! IInspectable
}

// Shadow COMTwoWayBinding methods to use WinRTError instead of COMError
public static func _implement<This>(_ this: UnsafeMutablePointer<This>?, _ body: (SwiftObject) throws -> Void) -> SWRT_HResult {
guard let this else { return WinRTError.toABI(hresult: HResult.pointer, message: "WinRT 'this' pointer was null") }
Expand All @@ -88,28 +92,10 @@ extension ComposableClassBinding {
guard let pointer = value else { return nil }
let reference = COMReference(transferringRef: pointer)
if let swiftObject = _unwrap(reference.pointer) { return swiftObject }
return swiftWrapperFactory.create(reference, binding: Self.self)
return swiftWrapperFactory.create(reference, staticBinding: Self.self)
}

public static func _unwrap(_ pointer: ABIPointer) -> SwiftObject? {
COMEmbedding.getImplementation(pointer, type: SwiftObject.self)
}

public static func _wrapObject(_ reference: consuming IInspectableReference) -> IInspectable {
try! _wrap(reference.queryInterface(interfaceID)) as! IInspectable
}

// Shadow COMTwoWayBinding methods to use WinRTError instead of COMError
public static func _implement<This>(_ this: UnsafeMutablePointer<This>?, _ body: (SwiftObject) throws -> Void) -> SWRT_HResult {
guard let this else { return WinRTError.toABI(hresult: HResult.pointer, message: "WinRT 'this' pointer was null") }
let implementation: SwiftObject = COMEmbedding.getImplementationOrCrash(this)
return WinRTError.toABI { try body(implementation) }
}

public static func _getter<Value>(_ this: ABIPointer?, _ value: UnsafeMutablePointer<Value>?, _ code: (SwiftObject) throws -> Value) -> SWRT_HResult {
_implement(this) {
guard let value else { throw COMError.pointer }
value.pointee = try code($0)
}
}
}
13 changes: 7 additions & 6 deletions Support/Sources/WindowsRuntime/BindingProtocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ public protocol InterfaceBinding: ReferenceTypeBinding, COMTwoWayBinding {} // w
/// Protocol for bindings of WinRT delegates into Swift.
public protocol DelegateBinding: ReferenceTypeBinding, IReferenceableBinding, COMTwoWayBinding {}

/// Protocol for bindings of WinRT activatable classes into Swift.
public protocol ActivatableClassBinding: ReferenceTypeBinding {} // where SwiftObject: IInspectable
/// Protocol for bindings of non-static WinRT runtime classes into Swift.
/// Allows for dynamic instantiation of wrappers for WinRT objects.
/// Conforms to AnyObject so that conforming types must be classes, which can be looked up using NSClassFromString.
public protocol RuntimeClassBinding: ReferenceTypeBinding, AnyObject { // where SwiftObject: IInspectable
static func _wrapObject(_ reference: consuming IInspectableReference) throws -> IInspectable
}

/// Protocol for bindings of WinRT composable classes into Swift.
/// Conforms to AnyObject so that conforming types must be classes, which can be looked up using NSClassFromString.
public protocol ComposableClassBinding: ReferenceTypeBinding, AnyObject { // where SwiftObject: IInspectable
static func _wrapObject(_ reference: consuming IInspectableReference) -> IInspectable
}
public protocol ComposableClassBinding: RuntimeClassBinding {}
14 changes: 7 additions & 7 deletions Support/Sources/WindowsRuntime/SwiftWrapperFactory.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
public protocol SwiftWrapperFactory {
func create<Binding: COMBinding>(
_ reference: consuming Binding.ABIReference,
binding: Binding.Type) -> Binding.SwiftObject
func create<StaticBinding: COMBinding>(
_ reference: consuming StaticBinding.ABIReference,
staticBinding: StaticBinding.Type) -> StaticBinding.SwiftObject
}

public struct DefaultSwiftWrapperFactory: SwiftWrapperFactory {
public init() {}

public func create<Binding: COMBinding>(
_ reference: consuming Binding.ABIReference,
binding: Binding.Type) -> Binding.SwiftObject {
Binding._wrap(consume reference)
public func create<StaticBinding: COMBinding>(
_ reference: consuming StaticBinding.ABIReference,
staticBinding: StaticBinding.Type) -> StaticBinding.SwiftObject {
StaticBinding._wrap(consume reference)
}
}

Expand Down

0 comments on commit 9a764d6

Please sign in to comment.