Skip to content

Commit

Permalink
Made struct/enum boxing ABI-compatible and added tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle committed Mar 27, 2024
1 parent bcc55e6 commit a4c5e95
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 24 deletions.
1 change: 1 addition & 0 deletions Generator/Sources/ProjectionModel/SupportModules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]) ])
Expand Down
37 changes: 31 additions & 6 deletions Generator/Sources/SwiftWinRT/Writing/ABIProjectionType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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<T> field types (non-inert)
// extension <struct>: ABIInertProjection
var protocolConformances = [SupportModules.WinRT.winRTBoxableProjection]
if isInert {
protocolConformances.append(SupportModules.COM.abiInertProjection)
}

// extension <struct>: 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())

Expand All @@ -114,6 +124,9 @@ fileprivate func writeStructProjectionExtension(
// public typealias ABIValue = <abi-type>
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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: UnsignedInteger & FixedWidthInteger>(
_ value: Value,
minimumLength: Int = MemoryLayout<Value>.size * 2) -> String {
Expand Down
36 changes: 36 additions & 0 deletions InteropTests/Tests/BoxingTests.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
39 changes: 39 additions & 0 deletions InteropTests/WinRTComponent/Boxing.cpp
Original file line number Diff line number Diff line change
@@ -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<int32_t>(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<hstring>(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<winrt::WinRTComponent::MinimalEnum>(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<winrt::WinRTComponent::MinimalStruct>(value);
}
}
25 changes: 25 additions & 0 deletions InteropTests/WinRTComponent/Boxing.h
Original file line number Diff line number Diff line change
@@ -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<Boxing, implementation::Boxing>
{
};
}
16 changes: 16 additions & 0 deletions InteropTests/WinRTComponent/Boxing.idl
Original file line number Diff line number Diff line change
@@ -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);
};
}
7 changes: 7 additions & 0 deletions InteropTests/WinRTComponent/WinRTComponent.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@
<ClInclude Include="Arrays.h">
<DependentUpon>Arrays.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Boxing.h">
<DependentUpon>Boxing.idl</DependentUpon>
</ClInclude>
<ClInclude Include="DateTimes.h">
<DependentUpon>DateTimes.idl</DependentUpon>
</ClInclude>
Expand Down Expand Up @@ -174,6 +177,9 @@
<ClCompile Include="Arrays.cpp">
<DependentUpon>Arrays.idl</DependentUpon>
</ClCompile>
<ClCompile Include="Boxing.cpp">
<DependentUpon>Boxing.idl</DependentUpon>
</ClCompile>
<ClCompile Include="DateTimes.cpp">
<DependentUpon>DateTimes.idl</DependentUpon>
</ClCompile>
Expand Down Expand Up @@ -225,6 +231,7 @@
</ItemGroup>
<ItemGroup>
<Midl Include="Arrays.idl" />
<Midl Include="Boxing.idl" />
<Midl Include="DateTimes.idl" />
<Midl Include="Enums.idl" />
<Midl Include="Errors.idl" />
Expand Down
28 changes: 14 additions & 14 deletions Support/Sources/WindowsRuntime/IInspectableBoxing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<BoxableValue: WinRTBoxableProjection>(_ 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<BoxableValue: WinRTBoxableProjection>(_ value: BoxableValue) throws -> IInspectable
where BoxableValue.SwiftValue == BoxableValue {
try BoxableValue.box(value)
}
Expand Down
3 changes: 3 additions & 0 deletions Support/Sources/WindowsRuntime/ReferenceBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ internal class ReferenceBox<BoxableProjection: WinRTBoxableProjection>
}

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)
}
}
35 changes: 32 additions & 3 deletions Support/Sources/WindowsRuntime/WindowsFoundation/IReference.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import WindowsRuntime_ABI
import struct Foundation.UUID
import class Foundation.NSLock

public typealias WindowsFoundation_IReference<T> = any WindowsFoundation_IReferenceProtocol<T>
public protocol WindowsFoundation_IReferenceProtocol<T>: WindowsFoundation_IPropertyValueProtocol {

/// Allows nongeneric uses of the protocol (see virtual table).
public protocol WindowsFoundation_IReferenceProtocolABI {
func _getABIValue(_ pointer: UnsafeMutableRawPointer) throws
}

public protocol WindowsFoundation_IReferenceProtocol<T>: WindowsFoundation_IPropertyValueProtocol, WindowsFoundation_IReferenceProtocolABI {
associatedtype T
func _value() throws -> T
}
Expand All @@ -12,13 +19,13 @@ extension WindowsFoundation_IReferenceProtocol {
}

public enum WindowsFoundation_IReferenceProjection<TProjection: WinRTBoxableProjection>: WinRTTwoWayProjection {
public typealias SwiftObject = IInspectable
public typealias SwiftObject = WindowsFoundation_IReference<TProjection.SwiftValue>
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<COMInterface>) -> SwiftObject {
Import.toSwift(consume reference)
Expand All @@ -40,9 +47,31 @@ public enum WindowsFoundation_IReferenceProjection<TProjection: WinRTBoxableProj
}
return TProjection.toSwift(consuming: &abiValue)
}

public func _getABIValue(_ pointer: UnsafeMutableRawPointer) throws {
try _interop.get_Value(pointer.bindMemory(to: TProjection.ABIValue.self, capacity: 1))
}
}
}

// A generic type cannot have stored properties,
// and closures converted to C function pointers cannot capture generic arguments.
fileprivate var virtualTable: WindowsRuntime_ABI.SWRT_WindowsFoundation_IReferenceVTable = .init(
QueryInterface: { COMExportedInterface.QueryInterface($0, $1, $2) },
AddRef: { COMExportedInterface.AddRef($0) },
Release: { COMExportedInterface.Release($0) },
GetIids: { WinRTExportedInterface.GetIids($0, $1, $2) },
GetRuntimeClassName: { WinRTExportedInterface.GetRuntimeClassName($0, $1) },
GetTrustLevel: { WinRTExportedInterface.GetTrustLevel($0, $1) },
get_Value: { this, value in
guard let this else { return HResult.pointer.value }
guard let this: any WindowsFoundation_IReferenceProtocolABI = COMExportBase.getImplementationUnsafe(this) else {
return HResult.fail.value
}
guard let value else { return HResult.pointer.value }
return HResult.catch { try this._getABIValue(value) }.value
})

#if swift(>=5.10)
extension WindowsRuntime_ABI.SWRT_WindowsFoundation_IReference: @retroactive COMIUnknownStruct {}
#endif
Expand Down

0 comments on commit a4c5e95

Please sign in to comment.