From 6e53db9d6fcdf537f5f47c17c3643f3417ec2a01 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Sun, 5 Jan 2025 14:38:06 -0500 Subject: [PATCH 1/8] Refactor SwiftWrapperFactory to InspectableTypeBindingResolver --- ...on from thebrowsercompany's swift-winrt.md | 2 +- Docs/Projection Design.md | 2 +- .../ProjectionModel/SupportModules.swift | 2 +- .../Tests/ClassInheritanceTests.swift | 41 ++-------------- Support/Sources/COM/COMBinding.swift | 6 +-- .../BindingProtocols+extensions.swift | 9 +++- .../WindowsRuntime/BindingProtocols.swift | 17 ++++--- ...efaultInspectableTypeBindingResolver.swift | 48 +++++++++++++++++++ .../WindowsRuntime/SwiftWrapperFactory.swift | 17 ------- .../WindowsRuntime/wrapInspectable.swift | 24 ++++++++++ 10 files changed, 102 insertions(+), 66 deletions(-) create mode 100644 Support/Sources/WindowsRuntime/DefaultInspectableTypeBindingResolver.swift delete mode 100644 Support/Sources/WindowsRuntime/SwiftWrapperFactory.swift create mode 100644 Support/Sources/WindowsRuntime/wrapInspectable.swift diff --git a/Docs/Migration from thebrowsercompany's swift-winrt.md b/Docs/Migration from thebrowsercompany's swift-winrt.md index 2fb324a1..ce18da6f 100644 --- a/Docs/Migration from thebrowsercompany's swift-winrt.md +++ b/Docs/Migration from thebrowsercompany's swift-winrt.md @@ -52,7 +52,7 @@ E.g. when `getBase()` returns `IBase` and the application code wants to convert **v2**: Optional. Pluggable. -**Migration path (medium)**: Provide `swiftWrapperFactory` implementation. +**Migration path (medium)**: Set up a `InspectableTypeBindingResolver`. ### Constructor error handling diff --git a/Docs/Projection Design.md b/Docs/Projection Design.md index dd1fef3e..5660fb91 100644 --- a/Docs/Projection Design.md +++ b/Docs/Projection Design.md @@ -74,7 +74,7 @@ Swift protocols generated for COM/WinRT interfaces have a "Protocol" suffix. The **Example**: `class CustomVector: IVectorProtocol { func getView() throws -> IVectorView }` ### Upcasting support -Given a `getObject() -> Base` that actually returns a `Derived`, there is opt-in support for casting `Base` to `Derived` through implementing `SwiftObjectWrapperFactory`. +Given a `getObject() -> Base` that actually returns a `Derived`, there is opt-in support for casting `Base` to `Derived` through setting up an `InspectableTypeBindingResolver`. **Rationale**: The C# projection supports this and it makes for a more natural use of projections, however it requires costly dynamic wrapper type lookup and instantiation on every method return. A built-in implementation would require lower-level assemblies to know about module names of higher-level assemblies. diff --git a/Generator/Sources/ProjectionModel/SupportModules.swift b/Generator/Sources/ProjectionModel/SupportModules.swift index a8b70902..d5aaa452 100644 --- a/Generator/Sources/ProjectionModel/SupportModules.swift +++ b/Generator/Sources/ProjectionModel/SupportModules.swift @@ -115,7 +115,7 @@ extension SupportModules.WinRT { public static var iactivationFactoryBinding: SwiftType { moduleType.member("IActivationFactoryBinding") } public static var activationFactoryResolverGlobal: String { "\(moduleName).activationFactoryResolver" } - public static var swiftWrapperFactoryGlobal: String { "\(moduleName).swiftWrapperFactory" } + public static var wrapInspectableGlobal: String { "\(moduleName).wrapInspectable" } } public enum BuiltInTypeKind { diff --git a/InteropTests/Tests/ClassInheritanceTests.swift b/InteropTests/Tests/ClassInheritanceTests.swift index ec1c212f..db03c4fc 100644 --- a/InteropTests/Tests/ClassInheritanceTests.swift +++ b/InteropTests/Tests/ClassInheritanceTests.swift @@ -76,42 +76,11 @@ class ClassInheritanceTests : XCTestCase { } public func testWithUpcasting() throws { - struct UpcastableSwiftWrapperFactory: SwiftWrapperFactory { - func create( - _ 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: StaticBinding.SwiftObject = fromRuntimeType( - inspectable: IInspectablePointer(OpaquePointer(reference.pointer))) { - return object - } else { - return StaticBinding._wrap(consume reference) - } - } - - func fromRuntimeType(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 { - // Name.Space.ClassName -> WinRTComponent.NameSpace_ClassNameBinding - var result = runtimeClassName.replacingOccurrences(of: ".", with: "_") - if let lastDotIndex = runtimeClassName.lastIndex(of: ".") { - result = result.replacingCharacters(in: lastDotIndex...lastDotIndex, with: "_") - } - result = result.replacingOccurrences(of: ".", with: "") - result.insert(contentsOf: "WinRTComponent.", at: result.startIndex) - result += "Binding" - return result - } - } - - let originalFactory = WindowsRuntime.swiftWrapperFactory - WindowsRuntime.swiftWrapperFactory = UpcastableSwiftWrapperFactory() - defer { WindowsRuntime.swiftWrapperFactory = originalFactory } + let originalBindingResolver = WindowsRuntime.inspectableTypeBindingResolver + WindowsRuntime.inspectableTypeBindingResolver = DefaultInspectableTypeBindingResolver( + namespacesToModuleNames: ["WinRTComponent": "WinRTComponent"], + ) + defer { WindowsRuntime.inspectableTypeBindingResolver = originalBindingResolver } XCTAssertNotNil(try WinRTComponent_MinimalBaseClassHierarchy.createUnsealedDerivedAsBase() as? WinRTComponent_MinimalUnsealedDerivedClass) XCTAssertNotNil(try WinRTComponent_MinimalBaseClassHierarchy.createSealedDerivedAsBase() as? WinRTComponent_MinimalSealedDerivedClass) diff --git a/Support/Sources/COM/COMBinding.swift b/Support/Sources/COM/COMBinding.swift index 8c348cce..80f3e42a 100644 --- a/Support/Sources/COM/COMBinding.swift +++ b/Support/Sources/COM/COMBinding.swift @@ -17,13 +17,13 @@ public protocol COMBinding: ABIBinding where SwiftValue == SwiftObject?, ABIValu /// Gets the COM interface identifier. static var interfaceID: COMInterfaceID { get } - // Non-nullable overload + /// Converts a Swift object to its COM ABI representation. static func toCOM(_ object: SwiftObject) throws -> ABIReference - // Attempts un unwrap a COM pointer into an existing Swift object. + /// Attempts un unwrap a COM pointer into an existing Swift object. static func _unwrap(_ pointer: ABIPointer) -> SwiftObject? - // Wraps a COM object into a new Swift object, without attempting to unwrap it first. + /// Wraps a COM object into a new Swift object, without attempting to unwrap it first. static func _wrap(_ reference: consuming ABIReference) -> SwiftObject } diff --git a/Support/Sources/WindowsRuntime/BindingProtocols+extensions.swift b/Support/Sources/WindowsRuntime/BindingProtocols+extensions.swift index 5263fdfa..f9056225 100644 --- a/Support/Sources/WindowsRuntime/BindingProtocols+extensions.swift +++ b/Support/Sources/WindowsRuntime/BindingProtocols+extensions.swift @@ -36,6 +36,13 @@ extension ReferenceTypeBinding { } extension InterfaceBinding { + public static func fromABI(consuming value: inout ABIValue) -> SwiftValue { + guard let pointer = value else { return nil } + let reference = COMReference(transferringRef: pointer) + if let swiftObject = _unwrap(reference.pointer) { return swiftObject } + return wrapInspectable(reference, staticBinding: Self.self) + } + // Shadow COMTwoWayBinding methods to use WinRTError instead of COMError public static func _implement(_ this: UnsafeMutablePointer?, _ body: (SwiftObject) throws -> Void) -> SWRT_HResult { guard let this else { return WinRTError.toABI(hresult: HResult.pointer, message: "WinRT 'this' pointer was null") } @@ -92,7 +99,7 @@ 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, staticBinding: Self.self) + return wrapInspectable(reference, staticBinding: Self.self) } public static func _unwrap(_ pointer: ABIPointer) -> SwiftObject? { diff --git a/Support/Sources/WindowsRuntime/BindingProtocols.swift b/Support/Sources/WindowsRuntime/BindingProtocols.swift index 4daa9668..cd9a9f96 100644 --- a/Support/Sources/WindowsRuntime/BindingProtocols.swift +++ b/Support/Sources/WindowsRuntime/BindingProtocols.swift @@ -33,18 +33,23 @@ public protocol StructBinding: ValueTypeBinding {} // POD structs will also conf /// Protocol for bindings of WinRT reference types into Swift. public protocol ReferenceTypeBinding: WinRTBinding, COMBinding {} -/// Protocol for bindings of WinRT interfaces into Swift. -public protocol InterfaceBinding: ReferenceTypeBinding, COMTwoWayBinding {} // where SwiftObject: any IInspectable - /// Protocol for bindings of WinRT delegates into Swift. public protocol DelegateBinding: ReferenceTypeBinding, IReferenceableBinding, COMTwoWayBinding {} -/// Protocol for bindings of non-static WinRT runtime classes into Swift. +/// Protocol for bindings of WinRT types implementing IInspectable into Swift (interfaces and runtime classes). /// 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 +public protocol InspectableTypeBinding: ReferenceTypeBinding, AnyObject { // where SwiftObject: IInspectable + static func _wrapInspectable(_ reference: consuming IInspectableReference) throws -> IInspectable } +/// Protocol for bindings of WinRT interfaces into Swift. +public protocol InterfaceBinding: InspectableTypeBinding, COMTwoWayBinding {} // where SwiftObject: any 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: InspectableTypeBinding {} + /// Protocol for bindings of WinRT composable classes into Swift. public protocol ComposableClassBinding: RuntimeClassBinding {} \ No newline at end of file diff --git a/Support/Sources/WindowsRuntime/DefaultInspectableTypeBindingResolver.swift b/Support/Sources/WindowsRuntime/DefaultInspectableTypeBindingResolver.swift new file mode 100644 index 00000000..a4b2b57c --- /dev/null +++ b/Support/Sources/WindowsRuntime/DefaultInspectableTypeBindingResolver.swift @@ -0,0 +1,48 @@ +/// Creates Swift wrappers for COM objects based on runtime type information, +/// which allows for instantiating subclass wrappers and upcasting. +public class DefaultInspectableTypeBindingResolver: InspectableTypeBindingResolver { + private let namespacesToModuleNames: [Substring: String] // Store keys as substrings for lookup by substring + private var bindingTypeCache: [String: any InspectableTypeBinding.Type?] = [:] + private let cacheFailedLookups: Bool + + public init(namespacesToModuleNames: [String: String], cacheFailedLookups: Bool = false) { + // Convert keys to substrings for lookup by substring (won't leak a larger string) + var namespaceSubstringsToModuleNames = .init(minimumCapacity: namespacesToModuleNames.count) + for (namespace, module) in namespacesToModuleNames { + namespaceSubstringsToModuleNames[namespace[...]] = module + } + + self.namespacesToModuleNames = namespaceSubstringsToModuleNames + self.cacheFailedLookups = cacheFailedLookups + } + + public func resolve(typeName: String) -> (any InspectableTypeBinding.Type)? { + if let cachedBindingType = bindingTypeCache[typeName] { return cachedBindingType } + + let bindingType = lookup(typeName: typeName) + if bindingType != nil || cacheFailedLookups { + bindingTypeCache[typeName] = bindingType + } + + return bindingType + } + + private func lookup(typeName: String) -> (any InspectableTypeBinding.Type)? { + guard let lastDotIndex = typeName.lastIndex(of: ".") else { return nil } + guard var bindingClassName = toModuleName(namespace: typeName[.. String? { + var namespace = namespace + while true { + guard !namespace.isEmpty else { return nil } + if let module = namespacesToModuleNames[namespace] { return module } + guard let lastDotIndex = runtimeClassName.lastIndex(of: ".") else { return nil } + namespace = namespace[..( - _ reference: consuming StaticBinding.ABIReference, - staticBinding: StaticBinding.Type) -> StaticBinding.SwiftObject -} - -public struct DefaultSwiftWrapperFactory: SwiftWrapperFactory { - public init() {} - - public func create( - _ reference: consuming StaticBinding.ABIReference, - staticBinding: StaticBinding.Type) -> StaticBinding.SwiftObject { - StaticBinding._wrap(consume reference) - } -} - -public var swiftWrapperFactory: any SwiftWrapperFactory = DefaultSwiftWrapperFactory() \ No newline at end of file diff --git a/Support/Sources/WindowsRuntime/wrapInspectable.swift b/Support/Sources/WindowsRuntime/wrapInspectable.swift new file mode 100644 index 00000000..bebaa8ae --- /dev/null +++ b/Support/Sources/WindowsRuntime/wrapInspectable.swift @@ -0,0 +1,24 @@ +/// A strategy for resolving a type binding from runtime type information and static context. +/// For example, if a Foo class instance as an IFoo interface, we can look up FooBinding from the runtime type name. +public protocol InspectableTypeBindingResolver { + func resolve(typeName: String) -> (any InspectableTypeBinding.Type)? +} + +/// The global runtime binding resolver. +public var inspectableTypeBindingResolver: (any InspectableTypeBindingResolver)? = nil + +/// Creates a Swift wrapper object for a COM object reference. +public func wrapInspectable( + _ reference: consuming StaticBinding.ABIReference, + staticBinding: StaticBinding.Type) -> StaticBinding.SwiftObject { + let inspectablePointer = IInspectablePointer(OpaquePointer(reference.pointer)) + if let inspectableTypeBindingResolver, + let typeName = COMInterop(inspectablePointer).getRuntimeClassName(), + let inspectableTypeBinding = inspectableTypeBindingResolver.resolve(typeName), + let wrapper = inspectableTypeBinding._wrapInspectable(COMReference(addingRef: inspectablePointer)) as? StaticBinding.SwiftObject { + return wrapper + } + else { + return StaticBinding._wrap(consume reference) + } +} \ No newline at end of file From 0d137de5846230490fd13c24eaafe8c63cb093e7 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Sun, 5 Jan 2025 14:56:54 -0500 Subject: [PATCH 2/8] Generate interface binding types as classes --- .../SwiftWinRT/Writing/ABIBinding.swift | 114 ++++++++++-------- .../WindowsRuntime/wrapInspectable.swift | 4 +- 2 files changed, 68 insertions(+), 50 deletions(-) diff --git a/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift b/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift index c48f25fe..6677a279 100644 --- a/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift +++ b/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift @@ -48,7 +48,8 @@ internal func writeABIBindingConformance(_ typeDefinition: TypeDefinition, gener if typeDefinition.genericArity == 0 { // Non-generic type, create a standard projection type. // enum IVectorBinding: WinRTBinding... {} - try writeInterfaceOrDelegateBindingType(typeDefinition.bindType(), + try writeInterfaceOrDelegateBindingType( + typeDefinition.bindType(genericArgs: genericArgs), name: try projection.toBindingTypeName(typeDefinition), projection: projection, to: writer) } @@ -370,59 +371,76 @@ fileprivate func writeInterfaceOrDelegateBindingType( name: String, projection: Projection, to writer: some SwiftDeclarationWriter) throws { - precondition(type.definition is InterfaceDefinition || type.definition is DelegateDefinition) - let bindingProtocol = type.definition is InterfaceDefinition - ? SupportModules.WinRT.interfaceBinding : SupportModules.WinRT.delegateBinding - - // Projections of generic instantiations are not owned by any specific module. - // Making them internal avoids clashes between redundant definitions across modules. - try writer.writeEnum( - attributes: [ Projection.getAvailableAttribute(type.definition) ].compactMap { $0 }, - visibility: type.genericArgs.isEmpty ? Projection.toVisibility(type.definition.visibility) : .internal, - name: name, - protocolConformances: [ bindingProtocol ]) { writer throws in - - let importClassName = "Import" - - if type.definition is InterfaceDefinition { - try writeReferenceTypeBindingConformance( - apiType: type, abiType: type, - wrapImpl: { writer, paramName in - writer.writeStatement("\(importClassName)(_wrapping: consume \(paramName))") - }, - projection: projection, - to: writer) + if type.definition is InterfaceDefinition { + // Implement binding as a class so it can be looked up using NSClassFromString. + try writer.writeClass( + attributes: [ Projection.getAvailableAttribute(typeDefinition) ].compactMap { $0 }, + visibility: Projection.toVisibility(typeDefinition.visibility), + name: name, + protocolConformances: [ SupportModules.WindowsRuntime.interfaceBinding ]) { writer throws in + try writeInterfaceOrDelegateBindingMembers( + typeDefinition.bindType(), bindingType: .named(name), + projection: projection, to: writer) } - else { - assert(type.definition is DelegateDefinition) - try writeReferenceTypeBindingConformance( - apiType: type, abiType: type, - wrapImpl: { writer, paramName in - writer.writeStatement("\(importClassName)(_wrapping: consume \(paramName)).invoke") - }, - toCOMImpl: { writer, paramName in - // Delegates have no identity, so create one for them - writer.writeStatement("ExportedDelegate(\(paramName)).toCOM()") - }, - projection: projection, - to: writer) + } + else { + try writer.writeEnum( + attributes: [ Projection.getAvailableAttribute(type.definition) ].compactMap { $0 }, + visibility: Projection.toVisibility(type.definition.visibility), + name: name, + protocolConformances: [ SupportModules.WindowsRuntime.delegateBinding ]) { writer throws in + try writeInterfaceOrDelegateBindingMembers( + typeDefinition.bindType(), bindingType: .named(name), + projection: projection, to: writer) } + } +} - try writeCOMImportClass( - type, visibility: .private, name: importClassName, - bindingType: .named(name), - projection: projection, to: writer) +fileprivate func writeInterfaceOrDelegateBindingMembers( + _ type: BoundType, + bindingType: SwiftType, + projection: Projection, + to writer: some SwiftDeclarationWriter) throws { + precondition(type.definition is InterfaceDefinition || type.definition is DelegateDefinition) - // public static var exportedVirtualTable: VirtualTablePointer { .init(&virtualTable) } - writer.writeComputedProperty( - visibility: .public, static: true, name: "exportedVirtualTable", - type: SupportModules.COM.virtualTablePointer) { writer in - writer.writeStatement(".init(&virtualTable)") - } + let importClassName = "Import" + + if type.definition is InterfaceDefinition { + try writeReferenceTypeBindingConformance( + apiType: type, abiType: type, + wrapImpl: { writer, paramName in + writer.writeStatement("\(importClassName)(_wrapping: consume \(paramName))") + }, + projection: projection, + to: writer) + } + else { + assert(type.definition is DelegateDefinition) + try writeReferenceTypeBindingConformance( + apiType: type, abiType: type, + wrapImpl: { writer, paramName in + writer.writeStatement("\(importClassName)(_wrapping: consume \(paramName)).invoke") + }, + toCOMImpl: { writer, paramName in + // Delegates have no identity, so create one for them + writer.writeStatement("ExportedDelegate(\(paramName)).toCOM()") + }, + projection: projection, + to: writer) + } - // private static var virtualTable = SWRT_IFoo_VirtualTable(...) - try writeVirtualTableProperty(name: "virtualTable", abiType: type, swiftType: type, projection: projection, to: writer) + try writeCOMImportClass( + type, visibility: .private, name: importClassName, bindingType: bindingType, projection: projection, to: writer) + + // public static var exportedVirtualTable: VirtualTablePointer { .init(&virtualTable) } + writer.writeComputedProperty( + visibility: .public, static: true, name: "exportedVirtualTable", + type: SupportModules.COM.virtualTablePointer) { writer in + writer.writeStatement(".init(&virtualTable)") } + + // private static var virtualTable = SWRT_IFoo_VirtualTable(...) + try writeVirtualTableProperty(name: "virtualTable", abiType: type, swiftType: type, projection: projection, to: writer) } internal func writeTypeNameProperty(type: BoundType, to writer: SwiftTypeDefinitionWriter) throws { diff --git a/Support/Sources/WindowsRuntime/wrapInspectable.swift b/Support/Sources/WindowsRuntime/wrapInspectable.swift index bebaa8ae..70dddf79 100644 --- a/Support/Sources/WindowsRuntime/wrapInspectable.swift +++ b/Support/Sources/WindowsRuntime/wrapInspectable.swift @@ -13,8 +13,8 @@ public func wrapInspectable( staticBinding: StaticBinding.Type) -> StaticBinding.SwiftObject { let inspectablePointer = IInspectablePointer(OpaquePointer(reference.pointer)) if let inspectableTypeBindingResolver, - let typeName = COMInterop(inspectablePointer).getRuntimeClassName(), - let inspectableTypeBinding = inspectableTypeBindingResolver.resolve(typeName), + let typeName = try? COMInterop(inspectablePointer).getRuntimeClassName(), + let inspectableTypeBinding = inspectableTypeBindingResolver.resolve(typeName: typeName), let wrapper = inspectableTypeBinding._wrapInspectable(COMReference(addingRef: inspectablePointer)) as? StaticBinding.SwiftObject { return wrapper } From bf41d4bd6458575386e936a48e7b4fd1d841d527 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Sun, 5 Jan 2025 15:02:48 -0500 Subject: [PATCH 3/8] Missing try --- .../Sources/WindowsRuntime/wrapInspectable.swift | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Support/Sources/WindowsRuntime/wrapInspectable.swift b/Support/Sources/WindowsRuntime/wrapInspectable.swift index 70dddf79..3845121a 100644 --- a/Support/Sources/WindowsRuntime/wrapInspectable.swift +++ b/Support/Sources/WindowsRuntime/wrapInspectable.swift @@ -11,14 +11,17 @@ public var inspectableTypeBindingResolver: (any InspectableTypeBindingResolver)? public func wrapInspectable( _ reference: consuming StaticBinding.ABIReference, staticBinding: StaticBinding.Type) -> StaticBinding.SwiftObject { + // The resolver can give us a more derived type so consult it first. let inspectablePointer = IInspectablePointer(OpaquePointer(reference.pointer)) if let inspectableTypeBindingResolver, let typeName = try? COMInterop(inspectablePointer).getRuntimeClassName(), - let inspectableTypeBinding = inspectableTypeBindingResolver.resolve(typeName: typeName), - let wrapper = inspectableTypeBinding._wrapInspectable(COMReference(addingRef: inspectablePointer)) as? StaticBinding.SwiftObject { - return wrapper - } - else { - return StaticBinding._wrap(consume reference) + let inspectableTypeBinding = inspectableTypeBindingResolver.resolve(typeName: typeName) { + if let wrapper = try? inspectableTypeBinding._wrapInspectable(COMReference(addingRef: inspectablePointer)) as? StaticBinding.SwiftObject { + return wrapper + } + + assertionFailure("\(inspectableTypeBinding) failed to wrap a COM object to \(StaticBinding.SwiftObject.self)") } + + return StaticBinding._wrap(consume reference) } \ No newline at end of file From 92b1a7abae33ae75fe9f21fab5f0f450eae714f8 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Sun, 5 Jan 2025 15:12:50 -0500 Subject: [PATCH 4/8] Remove AnyObject requirement --- Support/Sources/WindowsRuntime/BindingProtocols.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Support/Sources/WindowsRuntime/BindingProtocols.swift b/Support/Sources/WindowsRuntime/BindingProtocols.swift index cd9a9f96..e0317195 100644 --- a/Support/Sources/WindowsRuntime/BindingProtocols.swift +++ b/Support/Sources/WindowsRuntime/BindingProtocols.swift @@ -38,8 +38,7 @@ public protocol DelegateBinding: ReferenceTypeBinding, IReferenceableBinding, CO /// Protocol for bindings of WinRT types implementing IInspectable into Swift (interfaces and runtime classes). /// 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 InspectableTypeBinding: ReferenceTypeBinding, AnyObject { // where SwiftObject: IInspectable +public protocol InspectableTypeBinding: ReferenceTypeBinding { // where SwiftObject: IInspectable static func _wrapInspectable(_ reference: consuming IInspectableReference) throws -> IInspectable } @@ -47,8 +46,6 @@ public protocol InspectableTypeBinding: ReferenceTypeBinding, AnyObject { // whe public protocol InterfaceBinding: InspectableTypeBinding, COMTwoWayBinding {} // where SwiftObject: any 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: InspectableTypeBinding {} /// Protocol for bindings of WinRT composable classes into Swift. From 3c28fe831591aa4a6e495923aa1f01820cfaaa02 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Sun, 5 Jan 2025 15:22:28 -0500 Subject: [PATCH 5/8] Fixes --- .../Sources/SwiftWinRT/Writing/ABIBinding.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift b/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift index 6677a279..92891f90 100644 --- a/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift +++ b/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift @@ -49,7 +49,7 @@ internal func writeABIBindingConformance(_ typeDefinition: TypeDefinition, gener // Non-generic type, create a standard projection type. // enum IVectorBinding: WinRTBinding... {} try writeInterfaceOrDelegateBindingType( - typeDefinition.bindType(genericArgs: genericArgs), + typeDefinition.bindType(), name: try projection.toBindingTypeName(typeDefinition), projection: projection, to: writer) } @@ -374,12 +374,12 @@ fileprivate func writeInterfaceOrDelegateBindingType( if type.definition is InterfaceDefinition { // Implement binding as a class so it can be looked up using NSClassFromString. try writer.writeClass( - attributes: [ Projection.getAvailableAttribute(typeDefinition) ].compactMap { $0 }, - visibility: Projection.toVisibility(typeDefinition.visibility), + attributes: [ Projection.getAvailableAttribute(type.definition) ].compactMap { $0 }, + visibility: Projection.toVisibility(type.definition.visibility), name: name, protocolConformances: [ SupportModules.WindowsRuntime.interfaceBinding ]) { writer throws in try writeInterfaceOrDelegateBindingMembers( - typeDefinition.bindType(), bindingType: .named(name), + type, bindingType: .named(name), projection: projection, to: writer) } } @@ -390,7 +390,7 @@ fileprivate func writeInterfaceOrDelegateBindingType( name: name, protocolConformances: [ SupportModules.WindowsRuntime.delegateBinding ]) { writer throws in try writeInterfaceOrDelegateBindingMembers( - typeDefinition.bindType(), bindingType: .named(name), + type, bindingType: .named(name), projection: projection, to: writer) } } @@ -400,7 +400,7 @@ fileprivate func writeInterfaceOrDelegateBindingMembers( _ type: BoundType, bindingType: SwiftType, projection: Projection, - to writer: some SwiftDeclarationWriter) throws { + to writer: some SwiftTypeDefinitionWriter) throws { precondition(type.definition is InterfaceDefinition || type.definition is DelegateDefinition) let importClassName = "Import" From e67519da7afb1938e08347e5fd47f8e76e669812 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Sun, 5 Jan 2025 15:59:11 -0500 Subject: [PATCH 6/8] Fix build errors --- .../SwiftWinRT/Writing/ABIBinding.swift | 18 +++++++++--------- .../BindingProtocols+extensions.swift | 10 ++++++---- ...DefaultInspectableTypeBindingResolver.swift | 10 ++++++---- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift b/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift index 92891f90..8c3b20de 100644 --- a/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift +++ b/Generator/Sources/SwiftWinRT/Writing/ABIBinding.swift @@ -371,13 +371,15 @@ fileprivate func writeInterfaceOrDelegateBindingType( name: String, projection: Projection, to writer: some SwiftDeclarationWriter) throws { + let attributes = try [ Projection.getAvailableAttribute(type.definition) ].compactMap { $0 } + // Generic specializations can exist in multiple modules, so they must be internal. + let visibility = type.genericArgs.isEmpty ? Projection.toVisibility(type.definition.visibility) : SwiftVisibility.internal + if type.definition is InterfaceDefinition { // Implement binding as a class so it can be looked up using NSClassFromString. try writer.writeClass( - attributes: [ Projection.getAvailableAttribute(type.definition) ].compactMap { $0 }, - visibility: Projection.toVisibility(type.definition.visibility), - name: name, - protocolConformances: [ SupportModules.WindowsRuntime.interfaceBinding ]) { writer throws in + attributes: attributes, visibility: visibility, name: name, + protocolConformances: [ SupportModules.WinRT.interfaceBinding ]) { writer throws in try writeInterfaceOrDelegateBindingMembers( type, bindingType: .named(name), projection: projection, to: writer) @@ -385,10 +387,8 @@ fileprivate func writeInterfaceOrDelegateBindingType( } else { try writer.writeEnum( - attributes: [ Projection.getAvailableAttribute(type.definition) ].compactMap { $0 }, - visibility: Projection.toVisibility(type.definition.visibility), - name: name, - protocolConformances: [ SupportModules.WindowsRuntime.delegateBinding ]) { writer throws in + attributes: attributes, visibility: visibility, name: name, + protocolConformances: [ SupportModules.WinRT.delegateBinding ]) { writer throws in try writeInterfaceOrDelegateBindingMembers( type, bindingType: .named(name), projection: projection, to: writer) @@ -400,7 +400,7 @@ fileprivate func writeInterfaceOrDelegateBindingMembers( _ type: BoundType, bindingType: SwiftType, projection: Projection, - to writer: some SwiftTypeDefinitionWriter) throws { + to writer: SwiftTypeDefinitionWriter) throws { precondition(type.definition is InterfaceDefinition || type.definition is DelegateDefinition) let importClassName = "Import" diff --git a/Support/Sources/WindowsRuntime/BindingProtocols+extensions.swift b/Support/Sources/WindowsRuntime/BindingProtocols+extensions.swift index f9056225..02ebcdfa 100644 --- a/Support/Sources/WindowsRuntime/BindingProtocols+extensions.swift +++ b/Support/Sources/WindowsRuntime/BindingProtocols+extensions.swift @@ -35,6 +35,12 @@ extension ReferenceTypeBinding { } } +extension InspectableTypeBinding { + public static func _wrapInspectable(_ reference: consuming IInspectableReference) throws -> IInspectable { + try _wrap(reference.queryInterface(interfaceID)) as! IInspectable + } +} + extension InterfaceBinding { public static func fromABI(consuming value: inout ABIValue) -> SwiftValue { guard let pointer = value else { return nil } @@ -75,10 +81,6 @@ extension DelegateBinding { } 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: UnsafeMutablePointer?, _ body: (SwiftObject) throws -> Void) -> SWRT_HResult { guard let this else { return WinRTError.toABI(hresult: HResult.pointer, message: "WinRT 'this' pointer was null") } diff --git a/Support/Sources/WindowsRuntime/DefaultInspectableTypeBindingResolver.swift b/Support/Sources/WindowsRuntime/DefaultInspectableTypeBindingResolver.swift index a4b2b57c..eb47688b 100644 --- a/Support/Sources/WindowsRuntime/DefaultInspectableTypeBindingResolver.swift +++ b/Support/Sources/WindowsRuntime/DefaultInspectableTypeBindingResolver.swift @@ -1,13 +1,15 @@ +import func Foundation.NSClassFromString + /// Creates Swift wrappers for COM objects based on runtime type information, /// which allows for instantiating subclass wrappers and upcasting. public class DefaultInspectableTypeBindingResolver: InspectableTypeBindingResolver { private let namespacesToModuleNames: [Substring: String] // Store keys as substrings for lookup by substring - private var bindingTypeCache: [String: any InspectableTypeBinding.Type?] = [:] + private var bindingTypeCache: [String: (any InspectableTypeBinding.Type)?] = [:] private let cacheFailedLookups: Bool public init(namespacesToModuleNames: [String: String], cacheFailedLookups: Bool = false) { // Convert keys to substrings for lookup by substring (won't leak a larger string) - var namespaceSubstringsToModuleNames = .init(minimumCapacity: namespacesToModuleNames.count) + var namespaceSubstringsToModuleNames = [Substring: String](minimumCapacity: namespacesToModuleNames.count) for (namespace, module) in namespacesToModuleNames { namespaceSubstringsToModuleNames[namespace[...]] = module } @@ -31,7 +33,7 @@ public class DefaultInspectableTypeBindingResolver: InspectableTypeBindingResolv guard let lastDotIndex = typeName.lastIndex(of: ".") else { return nil } guard var bindingClassName = toModuleName(namespace: typeName[.. Date: Sun, 5 Jan 2025 16:26:55 -0500 Subject: [PATCH 7/8] Fix typo --- InteropTests/Tests/ClassInheritanceTests.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/InteropTests/Tests/ClassInheritanceTests.swift b/InteropTests/Tests/ClassInheritanceTests.swift index db03c4fc..099324d2 100644 --- a/InteropTests/Tests/ClassInheritanceTests.swift +++ b/InteropTests/Tests/ClassInheritanceTests.swift @@ -78,8 +78,7 @@ class ClassInheritanceTests : XCTestCase { public func testWithUpcasting() throws { let originalBindingResolver = WindowsRuntime.inspectableTypeBindingResolver WindowsRuntime.inspectableTypeBindingResolver = DefaultInspectableTypeBindingResolver( - namespacesToModuleNames: ["WinRTComponent": "WinRTComponent"], - ) + namespacesToModuleNames: ["WinRTComponent": "WinRTComponent"]) defer { WindowsRuntime.inspectableTypeBindingResolver = originalBindingResolver } XCTAssertNotNil(try WinRTComponent_MinimalBaseClassHierarchy.createUnsealedDerivedAsBase() as? WinRTComponent_MinimalUnsealedDerivedClass) From 8a44808c78a481a77f07c080b561a8309c953f46 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Sun, 5 Jan 2025 20:33:14 -0500 Subject: [PATCH 8/8] Fix binding type lookup --- .../DefaultInspectableTypeBindingResolver.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Support/Sources/WindowsRuntime/DefaultInspectableTypeBindingResolver.swift b/Support/Sources/WindowsRuntime/DefaultInspectableTypeBindingResolver.swift index eb47688b..8d55d30e 100644 --- a/Support/Sources/WindowsRuntime/DefaultInspectableTypeBindingResolver.swift +++ b/Support/Sources/WindowsRuntime/DefaultInspectableTypeBindingResolver.swift @@ -31,10 +31,19 @@ public class DefaultInspectableTypeBindingResolver: InspectableTypeBindingResolv private func lookup(typeName: String) -> (any InspectableTypeBinding.Type)? { guard let lastDotIndex = typeName.lastIndex(of: ".") else { return nil } - guard var bindingClassName = toModuleName(namespace: typeName[..