Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Made struct/enum boxing ABI-compatible and added tests #89

Merged
merged 1 commit into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading