Skip to content

Commit

Permalink
Implemented mapping of CustomStringConvertible to IStringable.
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle committed Feb 26, 2024
1 parent b2bad05 commit ab62565
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 11 deletions.
15 changes: 15 additions & 0 deletions Support/Sources/CWinRTCore/include/WinRT.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,21 @@ struct SWRT_IActivationFactoryVTable {
SWRT_HResult (__stdcall *ActivateInstance)(SWRT_IActivationFactory* _this, SWRT_IInspectable** instance);
};

// IStringable
typedef struct SWRT_IStringable {
struct SWRT_IStringableVTable* lpVtbl;
} SWRT_IStringable;

struct SWRT_IStringableVTable {
SWRT_HResult (__stdcall *QueryInterface)(SWRT_IStringable* _this, SWRT_Guid* riid, void** ppvObject);
uint32_t (__stdcall *AddRef)(SWRT_IStringable* _this);
uint32_t (__stdcall *Release)(SWRT_IStringable* _this);
SWRT_HResult (__stdcall *GetIids)(SWRT_IStringable* _this, uint32_t* iidCount, SWRT_Guid** iids);
SWRT_HResult (__stdcall *GetRuntimeClassName)(SWRT_IStringable* _this, SWRT_HString* className);
SWRT_HResult (__stdcall *GetTrustLevel)(SWRT_IStringable* _this, SWRT_TrustLevel* trustLevel);
SWRT_HResult (__stdcall *ToString)(SWRT_IStringable* _this, SWRT_HString* value);
};

// IWeakReference
typedef struct SWRT_IWeakReference {
struct SWRT_IWeakReferenceVTable* lpVtbl;
Expand Down
55 changes: 55 additions & 0 deletions Support/Sources/WindowsRuntime/IStringable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import CWinRTCore

public protocol IStringableProtocol: IUnknownProtocol {
func toString() throws -> String
}

public typealias IStringable = any IStringableProtocol

public enum IStringableProjection: COMTwoWayProjection {
public typealias SwiftObject = IStringable
public typealias COMInterface = CWinRTCore.SWRT_IStringable
public typealias COMVirtualTable = CWinRTCore.SWRT_IStringableVTable

public static var interfaceID: COMInterfaceID { COMInterface.iid }
public static var virtualTablePointer: COMVirtualTablePointer { withUnsafePointer(to: &virtualTable) { $0 } }

public static func toSwift(transferringRef comPointer: COMPointer) -> SwiftObject {
Import.toSwift(transferringRef: comPointer)
}

public static func toCOM(_ object: SwiftObject) throws -> COMPointer {
try Import.toCOM(object)
}

private final class Import: COMImport<IStringableProjection>, IStringableProtocol {
public func toString() throws -> String {
try _interop.toString()
}
}

private static var virtualTable: COMVirtualTable = .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) },
ToString: { this, value in _getter(this, value) { this in try HStringProjection.toABI(this.toString()) } })
}

#if swift(>=5.10)
extension CWinRTCore.SWRT_IStringable: @retroactive COMIUnknownStruct {}
#endif

extension CWinRTCore.SWRT_IStringable: /* @retroactive */ COMIInspectableStruct {
public static let iid = COMInterfaceID(0x96369F54, 0x8EB6, 0x48F0, 0xABCE, 0xC1B211E627C3);
}

extension COMInterop where Interface == CWinRTCore.SWRT_IStringable {
public func toString() throws -> String {
var value = HStringProjection.abiDefaultValue
try HResult.throwIfFailed(this.pointee.lpVtbl.pointee.ToString(this, &value))
return HStringProjection.toSwift(consuming: &value)
}
}
37 changes: 26 additions & 11 deletions Support/Sources/WindowsRuntime/WinRTExport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ open class WinRTExport<Projection: WinRTTwoWayProjection>
: COMExport<Projection>, IInspectableProtocol {
open class var _runtimeClassName: String { String(describing: Self.self) }
open class var _trustLevel: TrustLevel { .base }
open class var autoStringable: Bool { true }
open class var agile: Bool { true }
open class var weakReferenceSource: Bool { true }

Expand All @@ -13,18 +14,25 @@ open class WinRTExport<Projection: WinRTTwoWayProjection>
}

open override func _queryInterfacePointer(_ id: COMInterfaceID) throws -> IUnknownPointer {
// QI for IInspectable should return the identity interface just like IUnknown.
if id == IInspectableProjection.interfaceID { return unknownPointer.addingRef() }

// Implement WinRT core interfaces
if Self.agile && id == IAgileObjectProjection.interfaceID { return unknownPointer.addingRef() }
if Self.weakReferenceSource && id == IWeakReferenceSourceProjection.interfaceID {
let export = createSecondaryExport(
projection: IWeakReferenceSourceProjection.self,
implementation: WeakReferenceSource(target: self))
return export.unknownPointer.addingRef()
switch id {
// QI for IInspectable should return the identity interface just like IUnknown.
case IInspectableProjection.interfaceID: return unknownPointer.addingRef()
case IAgileObjectProjection.interfaceID where Self.agile: return unknownPointer.addingRef()
case IWeakReferenceSourceProjection.interfaceID where Self.weakReferenceSource:
let export = createSecondaryExport(
projection: IWeakReferenceSourceProjection.self,
implementation: WeakReferenceSource(target: self))
return export.unknownPointer.addingRef()
case IStringableProjection.interfaceID where Self.autoStringable:
if let customStringConvertible = self as? any CustomStringConvertible {
let export = createSecondaryExport(
projection: IStringableProjection.self,
implementation: Stringable(target: customStringConvertible))
return export.unknownPointer.addingRef()
}
break
default: break
}

return try super._queryInterfacePointer(id)
}

Expand All @@ -38,6 +46,7 @@ open class WinRTExport<Projection: WinRTTwoWayProjection>
var iids = Self.implements.map { $0.id }
if Self.agile { iids.append(IAgileObjectProjection.interfaceID) }
if Self.weakReferenceSource { iids.append(IWeakReferenceSourceProjection.interfaceID) }
if Self.autoStringable, self is CustomStringConvertible { iids.append(IStringableProjection.interfaceID) }
return iids
}

Expand All @@ -59,6 +68,12 @@ fileprivate final class WinRTWrappingExport<Projection: COMTwoWayProjection>: CO
}
}

fileprivate class Stringable: COMExport<IStringableProjection>, 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 {
weak var target: IInspectable?
init(target: IInspectable) { self.target = target }
Expand Down
8 changes: 8 additions & 0 deletions Support/Tests/WinRTExportTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ internal final class WinRTExportTests: XCTestCase {
XCTAssertThrowsError(try NonAgileObject().queryInterface(IAgileObjectProjection.self))
}

func testIStringable() throws {
final class Stringable: WinRTExport<IInspectableProjection>, CustomStringConvertible {
var description: String { "hello" }
}

XCTAssertEqual(try Stringable().queryInterface(IStringableProjection.self).toString(), "hello")
}

func testIWeakReferenceSource() throws {
final class WeakReferenceSource: WinRTExport<IInspectableProjection> {
override class var weakReferenceSource: Bool { true }
Expand Down

0 comments on commit ab62565

Please sign in to comment.