Skip to content

Commit

Permalink
Added WeakReference<T> to ease consuming IWeakReferenceSource.
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle committed Apr 1, 2024
1 parent 0562eed commit 4780bdc
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 12 deletions.
38 changes: 38 additions & 0 deletions InteropTests/Tests/WeakReferenceTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import XCTest
import WindowsRuntime
import WinRTComponent

class WeakReferenceTests: WinRTTestCase {
func testNulledWhenUnreferencedFromSwift() throws {
var target: MinimalClass! = try MinimalClass()
let weakReference = try WeakReference<MinimalClassProjection>(target)
XCTAssertNotNil(try weakReference.resolve())
target = nil
XCTAssertNil(try weakReference.resolve())
}

func testNulledWhenUnreferencedFromWinRT() throws {
var target: MinimalClass! = try MinimalClass()
let strongReferencer = try StrongReferencer(target)
let weakReference = try WeakReference<MinimalClassProjection>(target)
target = nil

XCTAssertNotNil(try strongReferencer._target())
XCTAssertNotNil(try weakReference.resolve())

try strongReferencer.clear()

XCTAssertNil(try NullResult.catch(strongReferencer._target()))
XCTAssertNil(try weakReference.resolve())
}

func testThroughIWeakReferenceSource() throws {
var target: MinimalClass! = try MinimalClass()
var weakReferenceSource: IWeakReferenceSource! = try target.queryInterface(IWeakReferenceSourceProjection.self)
let weakReference = try weakReferenceSource.getWeakReference()
XCTAssertNotNil(try weakReference.resolve())
target = nil
weakReferenceSource = nil
XCTAssertNil(try weakReference.resolve())
}
}
19 changes: 19 additions & 0 deletions InteropTests/WinRTComponent/StrongReferencer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "pch.h"
#include "StrongReferencer.h"
#include "StrongReferencer.g.cpp"

namespace winrt::WinRTComponent::implementation
{
StrongReferencer::StrongReferencer(winrt::Windows::Foundation::IInspectable const& target)
{
this->target = target;
}
winrt::Windows::Foundation::IInspectable StrongReferencer::Target()
{
return target;
}
void StrongReferencer::Clear()
{
target = nullptr;
}
}
21 changes: 21 additions & 0 deletions InteropTests/WinRTComponent/StrongReferencer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once
#include "StrongReferencer.g.h"

namespace winrt::WinRTComponent::implementation
{
struct StrongReferencer : StrongReferencerT<StrongReferencer>
{
StrongReferencer(winrt::Windows::Foundation::IInspectable const& target);
winrt::Windows::Foundation::IInspectable Target();
void Clear();

private:
winrt::Windows::Foundation::IInspectable target;
};
}
namespace winrt::WinRTComponent::factory_implementation
{
struct StrongReferencer : StrongReferencerT<StrongReferencer, implementation::StrongReferencer>
{
};
}
9 changes: 9 additions & 0 deletions InteropTests/WinRTComponent/StrongReferencer.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace WinRTComponent
{
runtimeclass StrongReferencer
{
StrongReferencer(Object target);
Object Target { get; };
void Clear();
}
}
7 changes: 7 additions & 0 deletions InteropTests/WinRTComponent/WinRTComponent.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@
<ClInclude Include="Strings.h">
<DependentUpon>Strings.idl</DependentUpon>
</ClInclude>
<ClInclude Include="StrongReferencer.h">
<DependentUpon>StrongReferencer.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Structs.h">
<DependentUpon>Structs.idl</DependentUpon>
</ClInclude>
Expand Down Expand Up @@ -234,6 +237,9 @@
<ClCompile Include="Strings.cpp">
<DependentUpon>Strings.idl</DependentUpon>
</ClCompile>
<ClCompile Include="StrongReferencer.cpp">
<DependentUpon>StrongReferencer.idl</DependentUpon>
</ClCompile>
<ClCompile Include="Structs.cpp">
<DependentUpon>Structs.idl</DependentUpon>
</ClCompile>
Expand Down Expand Up @@ -261,6 +267,7 @@
<Midl Include="ReferenceBoxing.idl" />
<Midl Include="ReturnArgument.idl" />
<Midl Include="Strings.idl" />
<Midl Include="StrongReferencer.idl" />
<Midl Include="Structs.idl" />
<Midl Include="WeakReferencer.idl" />
</ItemGroup>
Expand Down
26 changes: 26 additions & 0 deletions Support/Sources/WindowsRuntime/WeakReference.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import COM
import WindowsRuntime_ABI

/// A weak reference to a WinRT object.
public final class WeakReference<Projection: WinRTReferenceTypeProjection> {
private var weakReference: COMReference<WindowsRuntime_ABI.SWRT_IWeakReference>

public init(_ target: Projection.SwiftObject) throws {
guard let targetInspectable = target as? IInspectable else { throw HResult.Error.invalidArg }
let source = try targetInspectable._queryInterface(WindowsRuntime_ABI.SWRT_IWeakReferenceSource.iid)
.reinterpret(to: WindowsRuntime_ABI.SWRT_IWeakReferenceSource.self)
var weakReference: UnsafeMutablePointer<WindowsRuntime_ABI.SWRT_IWeakReference>?
try WinRTError.throwIfFailed(source.pointer.pointee.VirtualTable.pointee.GetWeakReference(source.pointer, &weakReference))
guard let weakReference else { throw HResult.Error.fail }
self.weakReference = .init(transferringRef: weakReference)
}

public func resolve() throws -> Projection.SwiftObject? {
var inspectableTarget: UnsafeMutablePointer<WindowsRuntime_ABI.SWRT_IInspectable>? = nil
var iid = GUIDProjection.toABI(Projection.interfaceID)
try WinRTError.throwIfFailed(weakReference.pointer.pointee.VirtualTable.pointee.Resolve(
weakReference.pointer, &iid, &inspectableTarget))
var target = Projection.COMPointer(OpaquePointer(inspectableTarget))
return Projection.toSwift(consuming: &target)
}
}
12 changes: 6 additions & 6 deletions Support/Sources/WindowsRuntime/WinRTExport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ open class WinRTExport<Projection: WinRTInterfaceProjection>
case IWeakReferenceSourceProjection.interfaceID where Self.implementIWeakReferenceSource:
let export = createSecondaryExport(
projection: IWeakReferenceSourceProjection.self,
implementation: WeakReferenceSource(target: self))
implementation: ExportedWeakReferenceSource(target: self))
return .init(addingRef: export.unknownPointer)
case WindowsFoundation_IStringableProjection.interfaceID where Self.implementIStringable:
if let customStringConvertible = self as? any CustomStringConvertible {
let export = createSecondaryExport(
projection: WindowsFoundation_IStringableProjection.self,
implementation: Stringable(target: customStringConvertible))
implementation: ExportedStringable(target: customStringConvertible))
return .init(addingRef: export.unknownPointer)
}
break
Expand Down Expand Up @@ -68,23 +68,23 @@ fileprivate final class WinRTWrappingExport<Projection: COMTwoWayProjection>: CO
}
}

fileprivate class Stringable: WinRTExport<WindowsFoundation_IStringableProjection>, WindowsFoundation_IStringableProtocol {
fileprivate class ExportedStringable: WinRTExport<WindowsFoundation_IStringableProjection>, WindowsFoundation_IStringableProtocol {
private let target: any CustomStringConvertible
init(target: any CustomStringConvertible) { self.target = target }
func toString() throws -> String { target.description }
}

fileprivate class WeakReference: COMExport<IWeakReferenceProjection>, IWeakReferenceProtocol {
fileprivate class ExportedWeakReference: COMExport<IWeakReferenceProjection>, IWeakReferenceProtocol {
weak var target: IInspectable?
init(target: IInspectable) { self.target = target }
func resolve() throws -> IInspectable? { target }
}

fileprivate class WeakReferenceSource: IWeakReferenceSourceProtocol {
fileprivate class ExportedWeakReferenceSource: IWeakReferenceSourceProtocol {
public let target: IInspectable
init(target: IInspectable) { self.target = target }

func getWeakReference() throws -> IWeakReference { WeakReference(target: target) }
func getWeakReference() throws -> IWeakReference { ExportedWeakReference(target: target) }

func _queryInterface(_ id: COM.COMInterfaceID) throws -> COM.IUnknownReference {
try target._queryInterface(id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,10 @@ extension WinRTReferenceTypeProjection {
value.pointee = try code($0)
}
}
}

extension WinRTDelegateProjection {
public static func box(_ value: SwiftValue) throws -> IInspectable {
ReferenceBox<Self>(value)
}
}
6 changes: 0 additions & 6 deletions Support/Sources/WindowsRuntime/WinRTProjectionProtocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,5 @@ public protocol WinRTInterfaceProjection: WinRTReferenceTypeProjection, COMTwoWa
/// Convenience protocol for projections of WinRT delegates into Swift.
public protocol WinRTDelegateProjection: WinRTReferenceTypeProjection, WinRTBoxableProjection, COMTwoWayProjection {}

extension WinRTDelegateProjection {
public static func box(_ value: SwiftValue) throws -> IInspectable {
ReferenceBox<Self>(value)
}
}

/// Convenience protocol for projections of WinRT classes into Swift.
public protocol WinRTClassProjection: WinRTReferenceTypeProjection {}

0 comments on commit 4780bdc

Please sign in to comment.