diff --git a/Generator/Sources/ProjectionModel/SupportModules.swift b/Generator/Sources/ProjectionModel/SupportModules.swift index a163ce17..65b44a01 100644 --- a/Generator/Sources/ProjectionModel/SupportModules.swift +++ b/Generator/Sources/ProjectionModel/SupportModules.swift @@ -68,6 +68,7 @@ extension SupportModules.WinRT { public static var winRTProjection: SwiftType { .chain(moduleName, "WinRTProjection") } public static var winRTTwoWayProjection: SwiftType { .chain(moduleName, "WinRTTwoWayProjection") } + public static var winRTBoxableProjection: SwiftType { .chain(moduleName, "WinRTBoxableProjection") } public static func winRTArrayProjection(of type: SwiftType) -> SwiftType { .chain([ .init(moduleName), .init("WinRTArrayProjection", genericArgs: [type]) ]) diff --git a/Generator/Sources/SwiftWinRT/Writing/ABIProjectionType.swift b/Generator/Sources/SwiftWinRT/Writing/ABIProjectionType.swift index 2381e93c..8044e0e1 100644 --- a/Generator/Sources/SwiftWinRT/Writing/ABIProjectionType.swift +++ b/Generator/Sources/SwiftWinRT/Writing/ABIProjectionType.swift @@ -44,9 +44,16 @@ internal func writeABIProjectionConformance(_ typeDefinition: TypeDefinition, ge if let enumDefinition = typeDefinition as? EnumDefinition { assert(genericArgs == nil) + let protocolConformances = [ + SupportModules.WinRT.winRTBoxableProjection, + SwiftType.chain("WindowsRuntime", "IntegerEnumProjection") + ] try writer.writeExtension( - type: .identifier(projection.toTypeName(enumDefinition)), - protocolConformances: [SwiftType.chain("WindowsRuntime", "IntegerEnumProjection")]) { _ in } + type: .identifier(projection.toTypeName(enumDefinition)), + protocolConformances: protocolConformances) { writer in + // public static var ireferenceID: COM.COMInterfaceID { .init(...) } + try writeBoxableIReferenceID(boxableType: enumDefinition.bindType(), to: writer) + } return } @@ -98,13 +105,16 @@ fileprivate func writeStructProjectionExtension( projection: SwiftProjection, to writer: SwiftSourceFileWriter) throws { let isInert = try projection.isProjectionInert(structDefinition) - let abiProjectionProtocol = isInert ? SupportModules.COM.abiInertProjection : SupportModules.COM.abiProjection - // TODO: Support strings and IReference field types (non-inert) - // extension : ABIInertProjection + var protocolConformances = [SupportModules.WinRT.winRTBoxableProjection] + if isInert { + protocolConformances.append(SupportModules.COM.abiInertProjection) + } + + // extension : WinRTBoxableProjection[, ABIInertProjection] try writer.writeExtension( type: .identifier(projection.toTypeName(structDefinition)), - protocolConformances: [abiProjectionProtocol]) { writer in + protocolConformances: protocolConformances) { writer in let abiType = try projection.toABIType(structDefinition.bindType()) @@ -114,6 +124,9 @@ fileprivate func writeStructProjectionExtension( // public typealias ABIValue = writer.writeTypeAlias(visibility: .public, name: "ABIValue", target: abiType) + // public static var ireferenceID: COM.COMInterfaceID { .init(...) } + try writeBoxableIReferenceID(boxableType: structDefinition.bindType(), to: writer) + // public static var abiDefaultValue: ABIValue { .init() } writer.writeComputedProperty( visibility: .public, static: true, name: "abiDefaultValue", type: abiType) { writer in @@ -226,6 +239,18 @@ fileprivate func writeStructSwiftToABIInitializerParam( } } +fileprivate func writeBoxableIReferenceID(boxableType: BoundType, to writer: SwiftTypeDefinitionWriter) throws { + let ireferenceParameterizedInterfaceID = UUID(uuidString: "61c17706-2d65-11e0-9ae8-d48564015472")! + + // public static var ireferenceID: COM.COMInterfaceID { UUID(...) } + try writer.writeComputedProperty(visibility: .public, static: true, name: "ireferenceID", type: SupportModules.COM.comInterfaceID) { writer in + let typeSignature = try WinRTTypeSignature.interface( + id: ireferenceParameterizedInterfaceID, + args: [ WinRTTypeSignature(boxableType) ]) + writer.writeStatement(try toIIDExpression(typeSignature.parameterizedID)) + } +} + /// Writes a type providing the ABIProjection conformance for a WinRT class. fileprivate func writeClassProjectionType( _ classDefinition: ClassDefinition, diff --git a/Generator/Sources/SwiftWinRT/Writing/COMInteropExtension.swift b/Generator/Sources/SwiftWinRT/Writing/COMInteropExtension.swift index 0282f74a..cc7f6f47 100644 --- a/Generator/Sources/SwiftWinRT/Writing/COMInteropExtension.swift +++ b/Generator/Sources/SwiftWinRT/Writing/COMInteropExtension.swift @@ -98,7 +98,7 @@ fileprivate func writeCOMInteropExtension(abiType: BoundType, projection: SwiftP } } -fileprivate func toIIDExpression(_ uuid: UUID) throws -> String { +internal func toIIDExpression(_ uuid: UUID) throws -> String { func toPrefixedPaddedHex( _ value: Value, minimumLength: Int = MemoryLayout.size * 2) -> String { diff --git a/InteropTests/Tests/BoxingTests.swift b/InteropTests/Tests/BoxingTests.swift new file mode 100644 index 00000000..470f7427 --- /dev/null +++ b/InteropTests/Tests/BoxingTests.swift @@ -0,0 +1,36 @@ +import WindowsRuntime +import WinRTComponent +import XCTest + +class BoxingTests: WinRTTestCase { + typealias CppBoxing = WinRTComponent.Boxing + typealias SwiftBoxing = WindowsRuntime.IInspectableBoxing + + func testRoundTripOfPrimitiveTypeWithIdentityProjection() throws { + let original = Int32(42) + XCTAssertEqual(try SwiftBoxing.unboxInt32(SwiftBoxing.box(original)), original) + XCTAssertEqual(try SwiftBoxing.unboxInt32(CppBoxing.boxInt32(original)), original) + XCTAssertEqual(try CppBoxing.unboxInt32(SwiftBoxing.box(original)), original) + } + + func testRoundTripOfPrimitiveTypeWithAllocatingProjection() throws { + let original = "Hello" + XCTAssertEqual(try SwiftBoxing.unboxString(SwiftBoxing.box(original)), original) + XCTAssertEqual(try SwiftBoxing.unboxString(CppBoxing.boxString(original)), original) + XCTAssertEqual(try CppBoxing.unboxString(SwiftBoxing.box(original)), original) + } + + func testRoundTripOfEnumType() throws { + let original = MinimalEnum.one + XCTAssertEqual(try SwiftBoxing.unbox(SwiftBoxing.box(original), projection: MinimalEnum.self), original) + XCTAssertEqual(try SwiftBoxing.unbox(CppBoxing.boxMinimalEnum(original), projection: MinimalEnum.self), original) + XCTAssertEqual(try CppBoxing.unboxMinimalEnum(SwiftBoxing.box(original)), original) + } + + func testRoundTripOfStructType() throws { + let original = MinimalStruct(field: 42) + XCTAssertEqual(try SwiftBoxing.unbox(SwiftBoxing.box(original), projection: MinimalStruct.self), original) + XCTAssertEqual(try SwiftBoxing.unbox(CppBoxing.boxMinimalStruct(original), projection: MinimalStruct.self), original) + XCTAssertEqual(try CppBoxing.unboxMinimalStruct(SwiftBoxing.box(original)), original) + } +} diff --git a/InteropTests/WinRTComponent/Boxing.cpp b/InteropTests/WinRTComponent/Boxing.cpp new file mode 100644 index 00000000..ad3a9fd6 --- /dev/null +++ b/InteropTests/WinRTComponent/Boxing.cpp @@ -0,0 +1,39 @@ +#include "pch.h" +#include "Boxing.h" +#include "Boxing.g.cpp" + +namespace winrt::WinRTComponent::implementation +{ + winrt::Windows::Foundation::IInspectable Boxing::BoxInt32(int32_t value) + { + return winrt::box_value(value); + } + int32_t Boxing::UnboxInt32(winrt::Windows::Foundation::IInspectable const& value) + { + return winrt::unbox_value(value); + } + winrt::Windows::Foundation::IInspectable Boxing::BoxString(hstring const& value) + { + return winrt::box_value(value); + } + hstring Boxing::UnboxString(winrt::Windows::Foundation::IInspectable const& value) + { + return winrt::unbox_value(value); + } + winrt::Windows::Foundation::IInspectable Boxing::BoxMinimalEnum(winrt::WinRTComponent::MinimalEnum const& value) + { + return winrt::box_value(value); + } + winrt::WinRTComponent::MinimalEnum Boxing::UnboxMinimalEnum(winrt::Windows::Foundation::IInspectable const& value) + { + return winrt::unbox_value(value); + } + winrt::Windows::Foundation::IInspectable Boxing::BoxMinimalStruct(winrt::WinRTComponent::MinimalStruct const& value) + { + return winrt::box_value(value); + } + winrt::WinRTComponent::MinimalStruct Boxing::UnboxMinimalStruct(winrt::Windows::Foundation::IInspectable const& value) + { + return winrt::unbox_value(value); + } +} diff --git a/InteropTests/WinRTComponent/Boxing.h b/InteropTests/WinRTComponent/Boxing.h new file mode 100644 index 00000000..78b82dd3 --- /dev/null +++ b/InteropTests/WinRTComponent/Boxing.h @@ -0,0 +1,25 @@ +#pragma once +#include "Boxing.g.h" + +namespace winrt::WinRTComponent::implementation +{ + struct Boxing + { + Boxing() = default; + + static winrt::Windows::Foundation::IInspectable BoxInt32(int32_t value); + static int32_t UnboxInt32(winrt::Windows::Foundation::IInspectable const& value); + static winrt::Windows::Foundation::IInspectable BoxString(hstring const& value); + static hstring UnboxString(winrt::Windows::Foundation::IInspectable const& value); + static winrt::Windows::Foundation::IInspectable BoxMinimalEnum(winrt::WinRTComponent::MinimalEnum const& value); + static winrt::WinRTComponent::MinimalEnum UnboxMinimalEnum(winrt::Windows::Foundation::IInspectable const& value); + static winrt::Windows::Foundation::IInspectable BoxMinimalStruct(winrt::WinRTComponent::MinimalStruct const& value); + static winrt::WinRTComponent::MinimalStruct UnboxMinimalStruct(winrt::Windows::Foundation::IInspectable const& value); + }; +} +namespace winrt::WinRTComponent::factory_implementation +{ + struct Boxing : BoxingT + { + }; +} diff --git a/InteropTests/WinRTComponent/Boxing.idl b/InteropTests/WinRTComponent/Boxing.idl new file mode 100644 index 00000000..f691a77e --- /dev/null +++ b/InteropTests/WinRTComponent/Boxing.idl @@ -0,0 +1,16 @@ +import "MinimalTypes.idl"; + +namespace WinRTComponent +{ + static runtimeclass Boxing + { + static IInspectable BoxInt32(Int32 value); + static Int32 UnboxInt32(IInspectable value); + static IInspectable BoxString(String value); + static String UnboxString(IInspectable value); + static IInspectable BoxMinimalEnum(MinimalEnum value); + static MinimalEnum UnboxMinimalEnum(IInspectable value); + static IInspectable BoxMinimalStruct(MinimalStruct value); + static MinimalStruct UnboxMinimalStruct(IInspectable value); + }; +} \ No newline at end of file diff --git a/InteropTests/WinRTComponent/WinRTComponent.vcxproj b/InteropTests/WinRTComponent/WinRTComponent.vcxproj index ad87486c..74f13424 100644 --- a/InteropTests/WinRTComponent/WinRTComponent.vcxproj +++ b/InteropTests/WinRTComponent/WinRTComponent.vcxproj @@ -115,6 +115,9 @@ Arrays.idl + + Boxing.idl + DateTimes.idl @@ -174,6 +177,9 @@ Arrays.idl + + Boxing.idl + DateTimes.idl @@ -225,6 +231,7 @@ + diff --git a/Support/Sources/WindowsRuntime/IInspectableBoxing.swift b/Support/Sources/WindowsRuntime/IInspectableBoxing.swift index 6c0d0333..b6b44bbe 100644 --- a/Support/Sources/WindowsRuntime/IInspectableBoxing.swift +++ b/Support/Sources/WindowsRuntime/IInspectableBoxing.swift @@ -2,20 +2,20 @@ import WindowsRuntime_ABI import struct Foundation.UUID public enum IInspectableBoxing { - public func box(_ value: Bool) throws -> IInspectable { try WinRTPrimitiveProjection.Boolean.box(value) } - public func box(_ value: UInt8) throws -> IInspectable { try WinRTPrimitiveProjection.UInt8.box(value) } - public func box(_ value: Int16) throws -> IInspectable { try WinRTPrimitiveProjection.Int16.box(value) } - public func box(_ value: UInt16) throws -> IInspectable { try WinRTPrimitiveProjection.UInt16.box(value) } - public func box(_ value: Int32) throws -> IInspectable { try WinRTPrimitiveProjection.Int32.box(value) } - public func box(_ value: UInt32) throws -> IInspectable { try WinRTPrimitiveProjection.UInt32.box(value) } - public func box(_ value: Int64) throws -> IInspectable { try WinRTPrimitiveProjection.Int64.box(value) } - public func box(_ value: UInt64) throws -> IInspectable { try WinRTPrimitiveProjection.UInt64.box(value) } - public func box(_ value: Float) throws -> IInspectable { try WinRTPrimitiveProjection.Single.box(value) } - public func box(_ value: Double) throws -> IInspectable { try WinRTPrimitiveProjection.Double.box(value) } - public func box(_ value: Char16) throws -> IInspectable { try WinRTPrimitiveProjection.Char16.box(value) } - public func box(_ value: String) throws -> IInspectable { try WinRTPrimitiveProjection.String.box(value) } - public func box(_ value: UUID) throws -> IInspectable { try WinRTPrimitiveProjection.Guid.box(value) } - public func box(_ value: BoxableValue) throws -> IInspectable + public static func box(_ value: Bool) throws -> IInspectable { try WinRTPrimitiveProjection.Boolean.box(value) } + public static func box(_ value: UInt8) throws -> IInspectable { try WinRTPrimitiveProjection.UInt8.box(value) } + public static func box(_ value: Int16) throws -> IInspectable { try WinRTPrimitiveProjection.Int16.box(value) } + public static func box(_ value: UInt16) throws -> IInspectable { try WinRTPrimitiveProjection.UInt16.box(value) } + public static func box(_ value: Int32) throws -> IInspectable { try WinRTPrimitiveProjection.Int32.box(value) } + public static func box(_ value: UInt32) throws -> IInspectable { try WinRTPrimitiveProjection.UInt32.box(value) } + public static func box(_ value: Int64) throws -> IInspectable { try WinRTPrimitiveProjection.Int64.box(value) } + public static func box(_ value: UInt64) throws -> IInspectable { try WinRTPrimitiveProjection.UInt64.box(value) } + public static func box(_ value: Float) throws -> IInspectable { try WinRTPrimitiveProjection.Single.box(value) } + public static func box(_ value: Double) throws -> IInspectable { try WinRTPrimitiveProjection.Double.box(value) } + public static func box(_ value: Char16) throws -> IInspectable { try WinRTPrimitiveProjection.Char16.box(value) } + public static func box(_ value: String) throws -> IInspectable { try WinRTPrimitiveProjection.String.box(value) } + public static func box(_ value: UUID) throws -> IInspectable { try WinRTPrimitiveProjection.Guid.box(value) } + public static func box(_ value: BoxableValue) throws -> IInspectable where BoxableValue.SwiftValue == BoxableValue { try BoxableValue.box(value) } diff --git a/Support/Sources/WindowsRuntime/ReferenceBox.swift b/Support/Sources/WindowsRuntime/ReferenceBox.swift index 710d24e5..bfa3aad4 100644 --- a/Support/Sources/WindowsRuntime/ReferenceBox.swift +++ b/Support/Sources/WindowsRuntime/ReferenceBox.swift @@ -13,4 +13,7 @@ internal class ReferenceBox } public func _value() throws -> T { value } + public func _getABIValue(_ pointer: UnsafeMutableRawPointer) throws { + pointer.bindMemory(to: BoxableProjection.ABIValue.self, capacity: 1).pointee = try BoxableProjection.toABI(value) + } } diff --git a/Support/Sources/WindowsRuntime/WindowsFoundation/IReference.swift b/Support/Sources/WindowsRuntime/WindowsFoundation/IReference.swift index de238d05..4d62143a 100644 --- a/Support/Sources/WindowsRuntime/WindowsFoundation/IReference.swift +++ b/Support/Sources/WindowsRuntime/WindowsFoundation/IReference.swift @@ -1,8 +1,15 @@ import WindowsRuntime_ABI import struct Foundation.UUID +import class Foundation.NSLock public typealias WindowsFoundation_IReference = any WindowsFoundation_IReferenceProtocol -public protocol WindowsFoundation_IReferenceProtocol: WindowsFoundation_IPropertyValueProtocol { + +/// Allows nongeneric uses of the protocol (see virtual table). +public protocol WindowsFoundation_IReferenceProtocolABI { + func _getABIValue(_ pointer: UnsafeMutableRawPointer) throws +} + +public protocol WindowsFoundation_IReferenceProtocol: WindowsFoundation_IPropertyValueProtocol, WindowsFoundation_IReferenceProtocolABI { associatedtype T func _value() throws -> T } @@ -12,13 +19,13 @@ extension WindowsFoundation_IReferenceProtocol { } public enum WindowsFoundation_IReferenceProjection: WinRTTwoWayProjection { - public typealias SwiftObject = IInspectable + public typealias SwiftObject = WindowsFoundation_IReference public typealias COMInterface = WindowsRuntime_ABI.SWRT_WindowsFoundation_IReference public typealias COMVirtualTable = WindowsRuntime_ABI.SWRT_WindowsFoundation_IReferenceVTable public static var runtimeClassName: String { fatalError("Not implemented: \(#function)") } public static var interfaceID: COMInterfaceID { TProjection.ireferenceID } - public static var virtualTablePointer: COMVirtualTablePointer { fatalError("Not implemented: \(#function)") } + public static var virtualTablePointer: COMVirtualTablePointer { withUnsafePointer(to: &virtualTable) { $0 } } public static func toSwift(_ reference: consuming COMReference) -> SwiftObject { Import.toSwift(consume reference) @@ -40,9 +47,31 @@ public enum WindowsFoundation_IReferenceProjection=5.10) extension WindowsRuntime_ABI.SWRT_WindowsFoundation_IReference: @retroactive COMIUnknownStruct {} #endif